@gearbox-protocol/sdk 13.0.0-next.21 → 13.0.0-next.22

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.
@@ -33,7 +33,6 @@ var import_v300 = require("../../abi/v300.js");
33
33
  var import_base = require("../base/index.js");
34
34
  var import_chains = require("../chain/chains.js");
35
35
  var import_constants = require("../constants/index.js");
36
- var import_market = require("../market/index.js");
37
36
  var import_router = require("../router/index.js");
38
37
  var import_sdk_legacy = require("../sdk-legacy/index.js");
39
38
  var import_utils = require("../utils/index.js");
@@ -42,6 +41,7 @@ const COMPRESSORS = {
42
41
  [import_chains.chains.Mainnet.id]: "0x36F3d0Bb73CBC2E94fE24dF0f26a689409cF9023",
43
42
  [import_chains.chains.Monad.id]: "0x36F3d0Bb73CBC2E94fE24dF0f26a689409cF9023"
44
43
  };
44
+ const INVESTORS = new import_utils.AddressMap([], "investors");
45
45
  function getWithdrawalCompressorAddress(chainId) {
46
46
  return COMPRESSORS[chainId];
47
47
  }
@@ -103,6 +103,23 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
103
103
  });
104
104
  return cad;
105
105
  }
106
+ /**
107
+ * Returns credit account data for a single account with the investor address resolved.
108
+ * Loads CA via getCreditAccountData; for KYC underlyings fetches the investor from the KYC factory's getInvestor(creditAccount), otherwise uses the account owner.
109
+ * @param account - Credit account address
110
+ * @param blockNumber - Optional block number for the read
111
+ * @returns CreditAccountDataWithInvestor (CA data + investor address), or undefined if the account is not found
112
+ */
113
+ async getCreditAccountDataWithInvestor(account, blockNumber) {
114
+ const ca = await this.getCreditAccountData(account, blockNumber);
115
+ if (!ca) return ca;
116
+ const marketSuite = this.sdk.marketRegister.findByCreditManager(
117
+ ca.creditManager
118
+ );
119
+ const factory = await marketSuite.getKYCFactory();
120
+ const investor = factory ? await factory.getInvestor(ca.creditAccount, true) : void 0;
121
+ return { ...ca, investor: investor ?? ca.owner };
122
+ }
106
123
  /**
107
124
  * Methods to get all credit accounts with some optional filtering
108
125
  * Performs all necessary price feed updates under the hood
@@ -111,7 +128,7 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
111
128
  * @param blockNumber
112
129
  * @returns returned credit accounts are sorted by health factor in ascending order
113
130
  */
114
- async getCreditAccounts(options, blockNumber) {
131
+ async getCreditAccounts(options, blockNumber, priceUpdate) {
115
132
  const {
116
133
  creditManager,
117
134
  includeZeroDebt = false,
@@ -133,7 +150,7 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
133
150
  maxHealthFactor,
134
151
  reverting: false
135
152
  };
136
- const { txs: priceUpdateTxs } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(
153
+ const { txs: priceUpdateTxs } = priceUpdate ?? await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(
137
154
  ignoreReservePrices ? { main: true } : void 0
138
155
  );
139
156
  const allCAs = [];
@@ -162,6 +179,75 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
162
179
  );
163
180
  return allCAs.sort((a, b) => Number(a.healthFactor - b.healthFactor));
164
181
  }
182
+ /**
183
+ * Returns all credit accounts matching the filter, with investor set on each item.
184
+ * Delegates to getCreditAccounts; when options.owner is set, also loads KYC credit accounts for that owner and merges them into the list. Result is sorted by health factor ascending.
185
+ * @param options - Filter options (owner, creditManager, health factor, etc.)
186
+ * @param blockNumber - Optional block number for the read
187
+ * @returns Array of credit accounts (with investor field), sorted by health factor ascending
188
+ */
189
+ async getCreditAccountsWithInvestor(options, blockNumber) {
190
+ const { owner, ignoreReservePrices = false } = options ?? {};
191
+ const priceUpdate = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(
192
+ ignoreReservePrices ? { main: true } : void 0
193
+ );
194
+ const { txs: priceUpdateTxs } = priceUpdate;
195
+ const [common, kyc] = await Promise.all([
196
+ this.getCreditAccounts(options, blockNumber),
197
+ owner ? this.getKYCCreditAccountsOfOwner(owner, priceUpdateTxs, blockNumber) : void 0
198
+ ]);
199
+ const allCAs = common.map(
200
+ (ca) => ({
201
+ ...ca,
202
+ investor: owner || ca.owner
203
+ })
204
+ );
205
+ allCAs.push(...kyc || []);
206
+ return allCAs.sort((a, b) => Number(a.healthFactor - b.healthFactor));
207
+ }
208
+ async getKYCCreditAccountsOfOwner(owner, priceUpdateTxs, blockNumber) {
209
+ const suites = this.marketConfigurators.map((mc) => {
210
+ const suite = this.sdk.marketRegister.markets.find(
211
+ (m) => m.configurator.address === mc
212
+ );
213
+ return suite;
214
+ });
215
+ const kycCAAddresses = await this.getKYCCaOfInvestor(owner, suites);
216
+ const kycCAs = await this.loadSpecifiedAccounts(
217
+ kycCAAddresses,
218
+ priceUpdateTxs,
219
+ blockNumber
220
+ );
221
+ return kycCAs.map((ca) => ({
222
+ ...ca,
223
+ investor: owner
224
+ }));
225
+ }
226
+ /**
227
+ * Loads credit account data for the given addresses using simulateWithPriceUpdates.
228
+ * Applies the provided price update txs before reading, so returned data is consistent with up-to-date prices.
229
+ * @param accounts - Credit account addresses to load
230
+ * @param priceUpdateTxs - Price feed update txs to simulate before the read (e.g. from generatePriceFeedsUpdateTxs)
231
+ * @param blockNumber - Optional block number for the read
232
+ * @returns Array of CreditAccountData in the same order as accounts (throws if any getCreditAccountData call reverts)
233
+ */
234
+ async loadSpecifiedAccounts(accounts, priceUpdateTxs, blockNumber) {
235
+ if (accounts.length === 0) return [];
236
+ const list = await (0, import_viem2.simulateWithPriceUpdates)(this.client, {
237
+ priceUpdates: priceUpdateTxs,
238
+ contracts: accounts.map(
239
+ (account) => ({
240
+ abi: import_creditAccountCompressor.creditAccountCompressorAbi,
241
+ address: this.#compressor,
242
+ functionName: "getCreditAccountData",
243
+ args: [account]
244
+ })
245
+ ),
246
+ blockNumber,
247
+ gas: this.sdk.gasLimit
248
+ });
249
+ return list;
250
+ }
165
251
  /**
166
252
  * Method to get all claimable rewards for credit account (ex. stkUSDS SKY rewards)
167
253
  Assosiates rewards by adapter + stakedPhantomToken
@@ -394,6 +480,13 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
394
480
  closePath
395
481
  }) {
396
482
  const cm = this.sdk.marketRegister.findCreditManager(ca.creditManager);
483
+ await this.sdk.tokensMeta.loadTokenData(cm.underlying);
484
+ const underlying = this.sdk.tokensMeta.mustGet(cm.underlying);
485
+ if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
486
+ throw new Error(
487
+ "closeCreditAccount is not supported for KYC underlying credit accounts"
488
+ );
489
+ }
397
490
  const routerCloseResult = closePath || await this.sdk.routerFor(ca).findBestClosePath({
398
491
  creditAccount: ca,
399
492
  creditManager: cm.creditManager,
@@ -796,10 +889,9 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
796
889
  async getApprovalAddress(options) {
797
890
  const { creditManager } = options;
798
891
  const suite = this.sdk.marketRegister.findCreditManager(creditManager);
799
- await this.sdk.tokensMeta.loadTokenData(suite.underlying);
800
- const underlying = this.sdk.tokensMeta.mustGet(suite.underlying);
801
- if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
802
- const factory = new import_market.SecuritizeKYCFactory(this.sdk, underlying.kycFactory);
892
+ const marketSuite = this.sdk.marketRegister.findByPool(suite.pool);
893
+ const factory = await marketSuite.getKYCFactory();
894
+ if (factory) {
803
895
  if ("creditAccount" in options) {
804
896
  return factory.getWallet(options.creditAccount);
805
897
  }
@@ -955,6 +1047,99 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
955
1047
  );
956
1048
  return resp;
957
1049
  }
1050
+ /**
1051
+ * Returns multicall entries to redeem (unwrap) KYC ERC-4626 vault shares into underlying for the given credit manager.
1052
+ * Used when withdrawing debt from a KYC market: redeems adapter vault shares so the underlying can be withdrawn.
1053
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
1054
+ * @param amount - Number of vault shares (adapter tokens) to redeem
1055
+ * @param creditManager - Credit manager address
1056
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
1057
+ */
1058
+ async getKYCUnwrapCalls(amount, creditManager) {
1059
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
1060
+ const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
1061
+ if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
1062
+ return void 0;
1063
+ }
1064
+ const adapter = suite.creditManager.adapters.get(meta.addr);
1065
+ const adapterAddress = adapter?.address;
1066
+ if (!adapterAddress) {
1067
+ return void 0;
1068
+ }
1069
+ const mc = [
1070
+ {
1071
+ target: adapterAddress,
1072
+ callData: (0, import_viem.encodeFunctionData)({
1073
+ abi: ierc4626AdapterAbi,
1074
+ functionName: "redeem",
1075
+ args: [amount, import_constants.ADDRESS_0X0, import_constants.ADDRESS_0X0]
1076
+ })
1077
+ }
1078
+ ];
1079
+ return mc;
1080
+ }
1081
+ /**
1082
+ * Returns multicall entries to deposit (wrap) underlying into KYC ERC-4626 vault shares for the given credit manager.
1083
+ * Used when adding debt on a KYC market: deposits underlying into the adapter vault so shares are minted on the account.
1084
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
1085
+ * @param amount - Amount of underlying assets to deposit into the vault (in underlying decimals)
1086
+ * @param creditManager - Credit manager address
1087
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
1088
+ */
1089
+ async getKYCWrapCalls(amount, creditManager) {
1090
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
1091
+ const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
1092
+ if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
1093
+ return void 0;
1094
+ }
1095
+ const adapter = suite.creditManager.adapters.get(meta.addr);
1096
+ const adapterAddress = adapter?.address;
1097
+ if (!adapterAddress) {
1098
+ return void 0;
1099
+ }
1100
+ const mc = [
1101
+ {
1102
+ target: adapterAddress,
1103
+ callData: (0, import_viem.encodeFunctionData)({
1104
+ abi: ierc4626AdapterAbi,
1105
+ functionName: "deposit",
1106
+ args: [amount, import_constants.ADDRESS_0X0]
1107
+ })
1108
+ }
1109
+ ];
1110
+ return mc;
1111
+ }
1112
+ /**
1113
+ * Returns multicall entries to call redeemDiff on the KYC ERC-4626 adapter for the given credit manager.
1114
+ * Redeems the leftover vault shares (e.g. after repaying debt) so the account does not hold excess KYC vault tokens.
1115
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
1116
+ * @param amount - Leftover vault share amount to redeem (in adapter/vault decimals)
1117
+ * @param creditManager - Credit manager address
1118
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
1119
+ */
1120
+ async getRedeemDiffCalls(amount, creditManager) {
1121
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
1122
+ const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
1123
+ if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
1124
+ return void 0;
1125
+ }
1126
+ const adapter = suite.creditManager.adapters.get(meta.addr);
1127
+ const adapterAddress = adapter?.address;
1128
+ if (!adapterAddress) {
1129
+ return void 0;
1130
+ }
1131
+ const mc = [
1132
+ {
1133
+ target: adapterAddress,
1134
+ callData: (0, import_viem.encodeFunctionData)({
1135
+ abi: ierc4626AdapterAbi,
1136
+ functionName: "redeemDiff",
1137
+ args: [amount]
1138
+ })
1139
+ }
1140
+ ];
1141
+ return mc;
1142
+ }
958
1143
  /**
959
1144
  * Returns raw txs that are needed to update all price feeds so that all credit accounts (possibly from different markets) compute
960
1145
  *
@@ -1221,10 +1406,9 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
1221
1406
  * @returns
1222
1407
  */
1223
1408
  async openCreditAccountTx(suite, to, calls, referralCode) {
1224
- await this.sdk.tokensMeta.loadTokenData(suite.underlying);
1225
- const underlying = this.sdk.tokensMeta.mustGet(suite.underlying);
1226
- if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
1227
- const factory = new import_market.SecuritizeKYCFactory(this.sdk, underlying.kycFactory);
1409
+ const marketSuite = this.sdk.marketRegister.findByPool(suite.pool);
1410
+ const factory = await marketSuite.getKYCFactory();
1411
+ if (factory) {
1228
1412
  const tokensToRegister = await factory.getDSTokens();
1229
1413
  return factory.openCreditAccount(
1230
1414
  suite.creditManager.address,
@@ -1242,11 +1426,12 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
1242
1426
  * @returns
1243
1427
  */
1244
1428
  async multicallTx(suite, creditAccount, calls) {
1245
- await this.sdk.tokensMeta.loadTokenData(suite.underlying);
1246
- const underlying = this.sdk.tokensMeta.mustGet(suite.underlying);
1247
- if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
1429
+ const marketSuite = this.sdk.marketRegister.findByCreditManager(
1430
+ suite.creditManager.address
1431
+ );
1432
+ const factory = await marketSuite.getKYCFactory();
1433
+ if (factory) {
1248
1434
  const tokensToRegister = [];
1249
- const factory = new import_market.SecuritizeKYCFactory(this.sdk, underlying.kycFactory);
1250
1435
  return factory.multicall(creditAccount, calls, tokensToRegister);
1251
1436
  }
1252
1437
  return suite.creditFacade.multicall(creditAccount, calls);
@@ -1260,16 +1445,128 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
1260
1445
  * @returns
1261
1446
  */
1262
1447
  async closeCreditAccountTx(suite, creditAccount, calls, operation) {
1263
- await this.sdk.tokensMeta.loadTokenData(suite.underlying);
1264
- const underlying = this.sdk.tokensMeta.mustGet(suite.underlying);
1265
- if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
1266
- throw new Error(
1267
- "KYC underlying is not supported for close credit account"
1268
- );
1448
+ const marketSuite = this.sdk.marketRegister.findByCreditManager(
1449
+ suite.creditManager.address
1450
+ );
1451
+ const factory = await marketSuite.getKYCFactory();
1452
+ if (operation === "close") {
1453
+ if (factory) {
1454
+ throw new Error(
1455
+ "CloseOptions=close is not supported for KYC underlying credit accounts"
1456
+ );
1457
+ }
1458
+ return suite.creditFacade.closeCreditAccount(creditAccount, calls);
1269
1459
  }
1270
- return operation === "close" ? suite.creditFacade.closeCreditAccount(creditAccount, calls) : suite.creditFacade.multicall(creditAccount, calls);
1460
+ if (factory) {
1461
+ const tokensToRegister = [];
1462
+ return factory.multicall(creditAccount, calls, tokensToRegister);
1463
+ }
1464
+ return suite.creditFacade.multicall(creditAccount, calls);
1465
+ }
1466
+ /**
1467
+ * Returns all KYC credit account addresses for an investor across the given market suites.
1468
+ * Resolves KYC factory per suite, then multicalls each factory's getCreditAccounts(investor).
1469
+ * @param investor - Owner address to query
1470
+ * @param suites - Market suites (KYC factories are resolved for each; undefined entries are skipped)
1471
+ * @returns Flat array of credit account addresses from all KYC markets
1472
+ */
1473
+ async getKYCCaOfInvestor(investor, suites) {
1474
+ if (suites.length === 0 || investor === import_constants.ADDRESS_0X0) return [];
1475
+ const factories = await Promise.all(
1476
+ suites.map((suite) => suite ? suite.getKYCFactory() : void 0)
1477
+ );
1478
+ const safeFactories = factories.reduce(
1479
+ (acc, v) => {
1480
+ if (v) {
1481
+ acc.push(v);
1482
+ }
1483
+ return acc;
1484
+ },
1485
+ []
1486
+ );
1487
+ const allResp = await this.client.multicall({
1488
+ contracts: [
1489
+ ...safeFactories.map((factory) => {
1490
+ return {
1491
+ abi: factory.abi,
1492
+ address: factory.address,
1493
+ functionName: "getCreditAccounts",
1494
+ args: [investor]
1495
+ };
1496
+ })
1497
+ ],
1498
+ allowFailure: true,
1499
+ batchSize: 0
1500
+ });
1501
+ const caLists = safeFactories.reduce((acc, _, index) => {
1502
+ const response = allResp[index];
1503
+ acc.push(...response.result || []);
1504
+ return acc;
1505
+ }, []);
1506
+ return caLists;
1271
1507
  }
1272
1508
  }
1509
+ const ierc4626AdapterAbi = [
1510
+ {
1511
+ inputs: [
1512
+ { name: "assets", type: "uint256", internalType: "uint256" },
1513
+ { name: "receiver", type: "address", internalType: "address" }
1514
+ ],
1515
+ name: "deposit",
1516
+ outputs: [{ name: "useSafePrices", type: "bool", internalType: "bool" }],
1517
+ stateMutability: "nonpayable",
1518
+ type: "function"
1519
+ },
1520
+ {
1521
+ inputs: [
1522
+ { name: "shares", type: "uint256", internalType: "uint256" },
1523
+ { name: "receiver", type: "address", internalType: "address" },
1524
+ { name: "owner", type: "address", internalType: "address" }
1525
+ ],
1526
+ name: "redeem",
1527
+ outputs: [{ name: "useSafePrices", type: "bool", internalType: "bool" }],
1528
+ stateMutability: "nonpayable",
1529
+ type: "function"
1530
+ },
1531
+ {
1532
+ inputs: [
1533
+ {
1534
+ name: "leftoverAmount",
1535
+ type: "uint256",
1536
+ internalType: "uint256"
1537
+ }
1538
+ ],
1539
+ name: "redeemDiff",
1540
+ outputs: [
1541
+ {
1542
+ name: "useSafePrices",
1543
+ type: "bool",
1544
+ internalType: "bool"
1545
+ }
1546
+ ],
1547
+ stateMutability: "nonpayable",
1548
+ type: "function"
1549
+ },
1550
+ {
1551
+ inputs: [
1552
+ {
1553
+ name: "leftoverAmount",
1554
+ type: "uint256",
1555
+ internalType: "uint256"
1556
+ }
1557
+ ],
1558
+ name: "depositDiff",
1559
+ outputs: [
1560
+ {
1561
+ name: "useSafePrices",
1562
+ type: "bool",
1563
+ internalType: "bool"
1564
+ }
1565
+ ],
1566
+ stateMutability: "nonpayable",
1567
+ type: "function"
1568
+ }
1569
+ ];
1273
1570
  // Annotate the CommonJS export names for ESM import in node:
1274
1571
  0 && (module.exports = {
1275
1572
  AbstractCreditAccountService,
@@ -113,11 +113,13 @@ class CreditAccountServiceV310 extends import_AbstractCreditAccountsService.Abst
113
113
  creditAccount: ca,
114
114
  permits,
115
115
  to,
116
- tokensToClaim
116
+ tokensToClaim,
117
+ calls: wrapCalls = []
117
118
  }) {
118
119
  const cm = this.sdk.marketRegister.findCreditManager(ca.creditManager);
119
120
  const addCollateral = collateralAssets.filter((a) => a.balance > 0);
120
121
  const router = this.sdk.routerFor(ca);
122
+ const unwrapCalls = await this.getRedeemDiffCalls(1n, ca.creditManager) ?? [];
121
123
  const claimPath = await router.findClaimAllRewards({
122
124
  calls: [],
123
125
  tokensToClaim,
@@ -130,8 +132,10 @@ class CreditAccountServiceV310 extends import_AbstractCreditAccountsService.Abst
130
132
  const calls = [
131
133
  ...operation === "close" ? [] : priceUpdates,
132
134
  ...this.prepareAddCollateral(ca.creditFacade, addCollateral, permits),
135
+ ...wrapCalls,
133
136
  ...this.prepareDisableQuotas(ca),
134
137
  ...this.prepareDecreaseDebt(ca),
138
+ ...unwrapCalls,
135
139
  ...claimPath.calls,
136
140
  ...this.prepareDisableTokens(ca),
137
141
  ...assetsToWithdraw.map(
@@ -23,8 +23,10 @@ __export(SecuritizeKYCFactory_exports, {
23
23
  module.exports = __toCommonJS(SecuritizeKYCFactory_exports);
24
24
  var import_iSecuritizeKYCFactory = require("../../../abi/310/iSecuritizeKYCFactory.js");
25
25
  var import_base = require("../../base/index.js");
26
+ var import__ = require("../../index.js");
26
27
  const abi = import_iSecuritizeKYCFactory.iSecuritizeKYCFactoryAbi;
27
28
  class SecuritizeKYCFactory extends import_base.BaseContract {
29
+ investorCache;
28
30
  constructor(options, address) {
29
31
  super(options, {
30
32
  addr: address,
@@ -41,6 +43,24 @@ class SecuritizeKYCFactory extends import_base.BaseContract {
41
43
  async getWallet(creditAccount) {
42
44
  return this.contract.read.getWallet([creditAccount]);
43
45
  }
46
+ /**
47
+ * Returns the investor address for a credit account.
48
+ * @param creditAccount - Credit account address
49
+ * @param fromCache - If true, use and update an in-memory cache (creditAccount -> investor). On cache miss, loads from contract and stores the result for future calls.
50
+ */
51
+ async getInvestor(creditAccount, fromCache) {
52
+ if (fromCache && this.investorCache?.has(creditAccount)) {
53
+ return this.investorCache.get(creditAccount);
54
+ }
55
+ const investor = await this.contract.read.getInvestor([creditAccount]);
56
+ if (fromCache) {
57
+ if (!this.investorCache) {
58
+ this.investorCache = new import__.AddressMap();
59
+ }
60
+ this.investorCache.upsert(creditAccount, investor);
61
+ }
62
+ return investor;
63
+ }
44
64
  async getDSTokens() {
45
65
  const tokens = await this.contract.read.getDSTokens();
46
66
  return [...tokens];
@@ -38,6 +38,7 @@ class CreditAccountData_Legacy {
38
38
  underlying;
39
39
  expirationDate;
40
40
  version;
41
+ investor;
41
42
  enabledTokensMask;
42
43
  healthFactor;
43
44
  baseBorrowRateWithoutFee;
@@ -67,6 +68,7 @@ class CreditAccountData_Legacy {
67
68
  this.underlying = payload.underlying.toLowerCase();
68
69
  this.expirationDate = Number(payload.expirationDate);
69
70
  this.version = Number(payload.cfVersion);
71
+ this.investor = payload.investor.toLowerCase();
70
72
  this.healthFactor = Number(
71
73
  (payload.healthFactor || 0n) * import_constants.PERCENTAGE_FACTOR / import_constants.WAD
72
74
  );
@@ -23,9 +23,6 @@ import {
23
23
  RAY,
24
24
  VERSION_RANGE_310
25
25
  } from "../constants/index.js";
26
- import {
27
- SecuritizeKYCFactory
28
- } from "../market/index.js";
29
26
  import { assetsMap } from "../router/index.js";
30
27
  import { BigIntMath } from "../sdk-legacy/index.js";
31
28
  import { AddressMap } from "../utils/index.js";
@@ -34,6 +31,7 @@ const COMPRESSORS = {
34
31
  [chains.Mainnet.id]: "0x36F3d0Bb73CBC2E94fE24dF0f26a689409cF9023",
35
32
  [chains.Monad.id]: "0x36F3d0Bb73CBC2E94fE24dF0f26a689409cF9023"
36
33
  };
34
+ const INVESTORS = new AddressMap([], "investors");
37
35
  function getWithdrawalCompressorAddress(chainId) {
38
36
  return COMPRESSORS[chainId];
39
37
  }
@@ -95,6 +93,23 @@ class AbstractCreditAccountService extends SDKConstruct {
95
93
  });
96
94
  return cad;
97
95
  }
96
+ /**
97
+ * Returns credit account data for a single account with the investor address resolved.
98
+ * Loads CA via getCreditAccountData; for KYC underlyings fetches the investor from the KYC factory's getInvestor(creditAccount), otherwise uses the account owner.
99
+ * @param account - Credit account address
100
+ * @param blockNumber - Optional block number for the read
101
+ * @returns CreditAccountDataWithInvestor (CA data + investor address), or undefined if the account is not found
102
+ */
103
+ async getCreditAccountDataWithInvestor(account, blockNumber) {
104
+ const ca = await this.getCreditAccountData(account, blockNumber);
105
+ if (!ca) return ca;
106
+ const marketSuite = this.sdk.marketRegister.findByCreditManager(
107
+ ca.creditManager
108
+ );
109
+ const factory = await marketSuite.getKYCFactory();
110
+ const investor = factory ? await factory.getInvestor(ca.creditAccount, true) : void 0;
111
+ return { ...ca, investor: investor ?? ca.owner };
112
+ }
98
113
  /**
99
114
  * Methods to get all credit accounts with some optional filtering
100
115
  * Performs all necessary price feed updates under the hood
@@ -103,7 +118,7 @@ class AbstractCreditAccountService extends SDKConstruct {
103
118
  * @param blockNumber
104
119
  * @returns returned credit accounts are sorted by health factor in ascending order
105
120
  */
106
- async getCreditAccounts(options, blockNumber) {
121
+ async getCreditAccounts(options, blockNumber, priceUpdate) {
107
122
  const {
108
123
  creditManager,
109
124
  includeZeroDebt = false,
@@ -125,7 +140,7 @@ class AbstractCreditAccountService extends SDKConstruct {
125
140
  maxHealthFactor,
126
141
  reverting: false
127
142
  };
128
- const { txs: priceUpdateTxs } = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(
143
+ const { txs: priceUpdateTxs } = priceUpdate ?? await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(
129
144
  ignoreReservePrices ? { main: true } : void 0
130
145
  );
131
146
  const allCAs = [];
@@ -154,6 +169,75 @@ class AbstractCreditAccountService extends SDKConstruct {
154
169
  );
155
170
  return allCAs.sort((a, b) => Number(a.healthFactor - b.healthFactor));
156
171
  }
172
+ /**
173
+ * Returns all credit accounts matching the filter, with investor set on each item.
174
+ * Delegates to getCreditAccounts; when options.owner is set, also loads KYC credit accounts for that owner and merges them into the list. Result is sorted by health factor ascending.
175
+ * @param options - Filter options (owner, creditManager, health factor, etc.)
176
+ * @param blockNumber - Optional block number for the read
177
+ * @returns Array of credit accounts (with investor field), sorted by health factor ascending
178
+ */
179
+ async getCreditAccountsWithInvestor(options, blockNumber) {
180
+ const { owner, ignoreReservePrices = false } = options ?? {};
181
+ const priceUpdate = await this.sdk.priceFeeds.generatePriceFeedsUpdateTxs(
182
+ ignoreReservePrices ? { main: true } : void 0
183
+ );
184
+ const { txs: priceUpdateTxs } = priceUpdate;
185
+ const [common, kyc] = await Promise.all([
186
+ this.getCreditAccounts(options, blockNumber),
187
+ owner ? this.getKYCCreditAccountsOfOwner(owner, priceUpdateTxs, blockNumber) : void 0
188
+ ]);
189
+ const allCAs = common.map(
190
+ (ca) => ({
191
+ ...ca,
192
+ investor: owner || ca.owner
193
+ })
194
+ );
195
+ allCAs.push(...kyc || []);
196
+ return allCAs.sort((a, b) => Number(a.healthFactor - b.healthFactor));
197
+ }
198
+ async getKYCCreditAccountsOfOwner(owner, priceUpdateTxs, blockNumber) {
199
+ const suites = this.marketConfigurators.map((mc) => {
200
+ const suite = this.sdk.marketRegister.markets.find(
201
+ (m) => m.configurator.address === mc
202
+ );
203
+ return suite;
204
+ });
205
+ const kycCAAddresses = await this.getKYCCaOfInvestor(owner, suites);
206
+ const kycCAs = await this.loadSpecifiedAccounts(
207
+ kycCAAddresses,
208
+ priceUpdateTxs,
209
+ blockNumber
210
+ );
211
+ return kycCAs.map((ca) => ({
212
+ ...ca,
213
+ investor: owner
214
+ }));
215
+ }
216
+ /**
217
+ * Loads credit account data for the given addresses using simulateWithPriceUpdates.
218
+ * Applies the provided price update txs before reading, so returned data is consistent with up-to-date prices.
219
+ * @param accounts - Credit account addresses to load
220
+ * @param priceUpdateTxs - Price feed update txs to simulate before the read (e.g. from generatePriceFeedsUpdateTxs)
221
+ * @param blockNumber - Optional block number for the read
222
+ * @returns Array of CreditAccountData in the same order as accounts (throws if any getCreditAccountData call reverts)
223
+ */
224
+ async loadSpecifiedAccounts(accounts, priceUpdateTxs, blockNumber) {
225
+ if (accounts.length === 0) return [];
226
+ const list = await simulateWithPriceUpdates(this.client, {
227
+ priceUpdates: priceUpdateTxs,
228
+ contracts: accounts.map(
229
+ (account) => ({
230
+ abi: creditAccountCompressorAbi,
231
+ address: this.#compressor,
232
+ functionName: "getCreditAccountData",
233
+ args: [account]
234
+ })
235
+ ),
236
+ blockNumber,
237
+ gas: this.sdk.gasLimit
238
+ });
239
+ return list;
240
+ }
157
241
  /**
158
242
  * Method to get all claimable rewards for credit account (ex. stkUSDS SKY rewards)
159
243
  Assosiates rewards by adapter + stakedPhantomToken
@@ -386,6 +470,13 @@ class AbstractCreditAccountService extends SDKConstruct {
386
470
  closePath
387
471
  }) {
388
472
  const cm = this.sdk.marketRegister.findCreditManager(ca.creditManager);
473
+ await this.sdk.tokensMeta.loadTokenData(cm.underlying);
474
+ const underlying = this.sdk.tokensMeta.mustGet(cm.underlying);
475
+ if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
476
+ throw new Error(
477
+ "closeCreditAccount is not supported for KYC underlying credit accounts"
478
+ );
479
+ }
389
480
  const routerCloseResult = closePath || await this.sdk.routerFor(ca).findBestClosePath({
390
481
  creditAccount: ca,
391
482
  creditManager: cm.creditManager,
@@ -788,10 +879,9 @@ class AbstractCreditAccountService extends SDKConstruct {
788
879
  async getApprovalAddress(options) {
789
880
  const { creditManager } = options;
790
881
  const suite = this.sdk.marketRegister.findCreditManager(creditManager);
791
- await this.sdk.tokensMeta.loadTokenData(suite.underlying);
792
- const underlying = this.sdk.tokensMeta.mustGet(suite.underlying);
793
- if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
794
- const factory = new SecuritizeKYCFactory(this.sdk, underlying.kycFactory);
882
+ const marketSuite = this.sdk.marketRegister.findByPool(suite.pool);
883
+ const factory = await marketSuite.getKYCFactory();
884
+ if (factory) {
795
885
  if ("creditAccount" in options) {
796
886
  return factory.getWallet(options.creditAccount);
797
887
  }
@@ -947,6 +1037,99 @@ class AbstractCreditAccountService extends SDKConstruct {
947
1037
  );
948
1038
  return resp;
949
1039
  }
1040
+ /**
1041
+ * Returns multicall entries to redeem (unwrap) KYC ERC-4626 vault shares into underlying for the given credit manager.
1042
+ * Used when withdrawing debt from a KYC market: redeems adapter vault shares so the underlying can be withdrawn.
1043
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
1044
+ * @param amount - Number of vault shares (adapter tokens) to redeem
1045
+ * @param creditManager - Credit manager address
1046
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
1047
+ */
1048
+ async getKYCUnwrapCalls(amount, creditManager) {
1049
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
1050
+ const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
1051
+ if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
1052
+ return void 0;
1053
+ }
1054
+ const adapter = suite.creditManager.adapters.get(meta.addr);
1055
+ const adapterAddress = adapter?.address;
1056
+ if (!adapterAddress) {
1057
+ return void 0;
1058
+ }
1059
+ const mc = [
1060
+ {
1061
+ target: adapterAddress,
1062
+ callData: encodeFunctionData({
1063
+ abi: ierc4626AdapterAbi,
1064
+ functionName: "redeem",
1065
+ args: [amount, ADDRESS_0X0, ADDRESS_0X0]
1066
+ })
1067
+ }
1068
+ ];
1069
+ return mc;
1070
+ }
1071
+ /**
1072
+ * Returns multicall entries to deposit (wrap) underlying into KYC ERC-4626 vault shares for the given credit manager.
1073
+ * Used when adding debt on a KYC market: deposits underlying into the adapter vault so shares are minted on the account.
1074
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
1075
+ * @param amount - Amount of underlying assets to deposit into the vault (in underlying decimals)
1076
+ * @param creditManager - Credit manager address
1077
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
1078
+ */
1079
+ async getKYCWrapCalls(amount, creditManager) {
1080
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
1081
+ const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
1082
+ if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
1083
+ return void 0;
1084
+ }
1085
+ const adapter = suite.creditManager.adapters.get(meta.addr);
1086
+ const adapterAddress = adapter?.address;
1087
+ if (!adapterAddress) {
1088
+ return void 0;
1089
+ }
1090
+ const mc = [
1091
+ {
1092
+ target: adapterAddress,
1093
+ callData: encodeFunctionData({
1094
+ abi: ierc4626AdapterAbi,
1095
+ functionName: "deposit",
1096
+ args: [amount, ADDRESS_0X0]
1097
+ })
1098
+ }
1099
+ ];
1100
+ return mc;
1101
+ }
1102
+ /**
1103
+ * Returns multicall entries to call redeemDiff on the KYC ERC-4626 adapter for the given credit manager.
1104
+ * Redeems the leftover vault shares (e.g. after repaying debt) so the account does not hold excess KYC vault tokens.
1105
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
1106
+ * @param amount - Leftover vault share amount to redeem (in adapter/vault decimals)
1107
+ * @param creditManager - Credit manager address
1108
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
1109
+ */
1110
+ async getRedeemDiffCalls(amount, creditManager) {
1111
+ const suite = this.sdk.marketRegister.findCreditManager(creditManager);
1112
+ const meta = this.sdk.tokensMeta.mustGet(suite.underlying);
1113
+ if (!this.sdk.tokensMeta.isKYCUnderlying(meta)) {
1114
+ return void 0;
1115
+ }
1116
+ const adapter = suite.creditManager.adapters.get(meta.addr);
1117
+ const adapterAddress = adapter?.address;
1118
+ if (!adapterAddress) {
1119
+ return void 0;
1120
+ }
1121
+ const mc = [
1122
+ {
1123
+ target: adapterAddress,
1124
+ callData: encodeFunctionData({
1125
+ abi: ierc4626AdapterAbi,
1126
+ functionName: "redeemDiff",
1127
+ args: [amount]
1128
+ })
1129
+ }
1130
+ ];
1131
+ return mc;
1132
+ }
950
1133
  /**
951
1134
  * Returns raw txs that are needed to update all price feeds so that all credit accounts (possibly from different markets) compute
952
1135
  *
@@ -1213,10 +1396,9 @@ class AbstractCreditAccountService extends SDKConstruct {
1213
1396
  * @returns
1214
1397
  */
1215
1398
  async openCreditAccountTx(suite, to, calls, referralCode) {
1216
- await this.sdk.tokensMeta.loadTokenData(suite.underlying);
1217
- const underlying = this.sdk.tokensMeta.mustGet(suite.underlying);
1218
- if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
1219
- const factory = new SecuritizeKYCFactory(this.sdk, underlying.kycFactory);
1399
+ const marketSuite = this.sdk.marketRegister.findByPool(suite.pool);
1400
+ const factory = await marketSuite.getKYCFactory();
1401
+ if (factory) {
1220
1402
  const tokensToRegister = await factory.getDSTokens();
1221
1403
  return factory.openCreditAccount(
1222
1404
  suite.creditManager.address,
@@ -1234,11 +1416,12 @@ class AbstractCreditAccountService extends SDKConstruct {
1234
1416
  * @returns
1235
1417
  */
1236
1418
  async multicallTx(suite, creditAccount, calls) {
1237
- await this.sdk.tokensMeta.loadTokenData(suite.underlying);
1238
- const underlying = this.sdk.tokensMeta.mustGet(suite.underlying);
1239
- if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
1419
+ const marketSuite = this.sdk.marketRegister.findByCreditManager(
1420
+ suite.creditManager.address
1421
+ );
1422
+ const factory = await marketSuite.getKYCFactory();
1423
+ if (factory) {
1240
1424
  const tokensToRegister = [];
1241
- const factory = new SecuritizeKYCFactory(this.sdk, underlying.kycFactory);
1242
1425
  return factory.multicall(creditAccount, calls, tokensToRegister);
1243
1426
  }
1244
1427
  return suite.creditFacade.multicall(creditAccount, calls);
@@ -1252,16 +1435,128 @@ class AbstractCreditAccountService extends SDKConstruct {
1252
1435
  * @returns
1253
1436
  */
1254
1437
  async closeCreditAccountTx(suite, creditAccount, calls, operation) {
1255
- await this.sdk.tokensMeta.loadTokenData(suite.underlying);
1256
- const underlying = this.sdk.tokensMeta.mustGet(suite.underlying);
1257
- if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
1258
- throw new Error(
1259
- "KYC underlying is not supported for close credit account"
1260
- );
1438
+ const marketSuite = this.sdk.marketRegister.findByCreditManager(
1439
+ suite.creditManager.address
1440
+ );
1441
+ const factory = await marketSuite.getKYCFactory();
1442
+ if (operation === "close") {
1443
+ if (factory) {
1444
+ throw new Error(
1445
+ "CloseOptions=close is not supported for KYC underlying credit accounts"
1446
+ );
1447
+ }
1448
+ return suite.creditFacade.closeCreditAccount(creditAccount, calls);
1261
1449
  }
1262
- return operation === "close" ? suite.creditFacade.closeCreditAccount(creditAccount, calls) : suite.creditFacade.multicall(creditAccount, calls);
1450
+ if (factory) {
1451
+ const tokensToRegister = [];
1452
+ return factory.multicall(creditAccount, calls, tokensToRegister);
1453
+ }
1454
+ return suite.creditFacade.multicall(creditAccount, calls);
1455
+ }
1456
+ /**
1457
+ * Returns all KYC credit account addresses for an investor across the given market suites.
1458
+ * Resolves KYC factory per suite, then multicalls each factory's getCreditAccounts(investor).
1459
+ * @param investor - Owner address to query
1460
+ * @param suites - Market suites (KYC factories are resolved for each; undefined entries are skipped)
1461
+ * @returns Flat array of credit account addresses from all KYC markets
1462
+ */
1463
+ async getKYCCaOfInvestor(investor, suites) {
1464
+ if (suites.length === 0 || investor === ADDRESS_0X0) return [];
1465
+ const factories = await Promise.all(
1466
+ suites.map((suite) => suite ? suite.getKYCFactory() : void 0)
1467
+ );
1468
+ const safeFactories = factories.reduce(
1469
+ (acc, v) => {
1470
+ if (v) {
1471
+ acc.push(v);
1472
+ }
1473
+ return acc;
1474
+ },
1475
+ []
1476
+ );
1477
+ const allResp = await this.client.multicall({
1478
+ contracts: [
1479
+ ...safeFactories.map((factory) => {
1480
+ return {
1481
+ abi: factory.abi,
1482
+ address: factory.address,
1483
+ functionName: "getCreditAccounts",
1484
+ args: [investor]
1485
+ };
1486
+ })
1487
+ ],
1488
+ allowFailure: true,
1489
+ batchSize: 0
1490
+ });
1491
+ const caLists = safeFactories.reduce((acc, _, index) => {
1492
+ const response = allResp[index];
1493
+ acc.push(...response.result || []);
1494
+ return acc;
1495
+ }, []);
1496
+ return caLists;
1263
1497
  }
1264
1498
  }
1499
+ const ierc4626AdapterAbi = [
1500
+ {
1501
+ inputs: [
1502
+ { name: "assets", type: "uint256", internalType: "uint256" },
1503
+ { name: "receiver", type: "address", internalType: "address" }
1504
+ ],
1505
+ name: "deposit",
1506
+ outputs: [{ name: "useSafePrices", type: "bool", internalType: "bool" }],
1507
+ stateMutability: "nonpayable",
1508
+ type: "function"
1509
+ },
1510
+ {
1511
+ inputs: [
1512
+ { name: "shares", type: "uint256", internalType: "uint256" },
1513
+ { name: "receiver", type: "address", internalType: "address" },
1514
+ { name: "owner", type: "address", internalType: "address" }
1515
+ ],
1516
+ name: "redeem",
1517
+ outputs: [{ name: "useSafePrices", type: "bool", internalType: "bool" }],
1518
+ stateMutability: "nonpayable",
1519
+ type: "function"
1520
+ },
1521
+ {
1522
+ inputs: [
1523
+ {
1524
+ name: "leftoverAmount",
1525
+ type: "uint256",
1526
+ internalType: "uint256"
1527
+ }
1528
+ ],
1529
+ name: "redeemDiff",
1530
+ outputs: [
1531
+ {
1532
+ name: "useSafePrices",
1533
+ type: "bool",
1534
+ internalType: "bool"
1535
+ }
1536
+ ],
1537
+ stateMutability: "nonpayable",
1538
+ type: "function"
1539
+ },
1540
+ {
1541
+ inputs: [
1542
+ {
1543
+ name: "leftoverAmount",
1544
+ type: "uint256",
1545
+ internalType: "uint256"
1546
+ }
1547
+ ],
1548
+ name: "depositDiff",
1549
+ outputs: [
1550
+ {
1551
+ name: "useSafePrices",
1552
+ type: "bool",
1553
+ internalType: "bool"
1554
+ }
1555
+ ],
1556
+ stateMutability: "nonpayable",
1557
+ type: "function"
1558
+ }
1559
+ ];
1265
1560
  export {
1266
1561
  AbstractCreditAccountService,
1267
1562
  getWithdrawalCompressorAddress
@@ -90,11 +90,13 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
90
90
  creditAccount: ca,
91
91
  permits,
92
92
  to,
93
- tokensToClaim
93
+ tokensToClaim,
94
+ calls: wrapCalls = []
94
95
  }) {
95
96
  const cm = this.sdk.marketRegister.findCreditManager(ca.creditManager);
96
97
  const addCollateral = collateralAssets.filter((a) => a.balance > 0);
97
98
  const router = this.sdk.routerFor(ca);
99
+ const unwrapCalls = await this.getRedeemDiffCalls(1n, ca.creditManager) ?? [];
98
100
  const claimPath = await router.findClaimAllRewards({
99
101
  calls: [],
100
102
  tokensToClaim,
@@ -107,8 +109,10 @@ class CreditAccountServiceV310 extends AbstractCreditAccountService {
107
109
  const calls = [
108
110
  ...operation === "close" ? [] : priceUpdates,
109
111
  ...this.prepareAddCollateral(ca.creditFacade, addCollateral, permits),
112
+ ...wrapCalls,
110
113
  ...this.prepareDisableQuotas(ca),
111
114
  ...this.prepareDecreaseDebt(ca),
115
+ ...unwrapCalls,
112
116
  ...claimPath.calls,
113
117
  ...this.prepareDisableTokens(ca),
114
118
  ...assetsToWithdraw.map(
@@ -1,7 +1,9 @@
1
1
  import { iSecuritizeKYCFactoryAbi } from "../../../abi/310/iSecuritizeKYCFactory.js";
2
2
  import { BaseContract } from "../../base/index.js";
3
+ import { AddressMap } from "../../index.js";
3
4
  const abi = iSecuritizeKYCFactoryAbi;
4
5
  class SecuritizeKYCFactory extends BaseContract {
6
+ investorCache;
5
7
  constructor(options, address) {
6
8
  super(options, {
7
9
  addr: address,
@@ -18,6 +20,24 @@ class SecuritizeKYCFactory extends BaseContract {
18
20
  async getWallet(creditAccount) {
19
21
  return this.contract.read.getWallet([creditAccount]);
20
22
  }
23
+ /**
24
+ * Returns the investor address for a credit account.
25
+ * @param creditAccount - Credit account address
26
+ * @param fromCache - If true, use and update an in-memory cache (creditAccount -> investor). On cache miss, loads from contract and stores the result for future calls.
27
+ */
28
+ async getInvestor(creditAccount, fromCache) {
29
+ if (fromCache && this.investorCache?.has(creditAccount)) {
30
+ return this.investorCache.get(creditAccount);
31
+ }
32
+ const investor = await this.contract.read.getInvestor([creditAccount]);
33
+ if (fromCache) {
34
+ if (!this.investorCache) {
35
+ this.investorCache = new AddressMap();
36
+ }
37
+ this.investorCache.upsert(creditAccount, investor);
38
+ }
39
+ return investor;
40
+ }
21
41
  async getDSTokens() {
22
42
  const tokens = await this.contract.read.getDSTokens();
23
43
  return [...tokens];
@@ -24,6 +24,7 @@ class CreditAccountData_Legacy {
24
24
  underlying;
25
25
  expirationDate;
26
26
  version;
27
+ investor;
27
28
  enabledTokensMask;
28
29
  healthFactor;
29
30
  baseBorrowRateWithoutFee;
@@ -53,6 +54,7 @@ class CreditAccountData_Legacy {
53
54
  this.underlying = payload.underlying.toLowerCase();
54
55
  this.expirationDate = Number(payload.expirationDate);
55
56
  this.version = Number(payload.cfVersion);
57
+ this.investor = payload.investor.toLowerCase();
56
58
  this.healthFactor = Number(
57
59
  (payload.healthFactor || 0n) * PERCENTAGE_FACTOR / WAD
58
60
  );
@@ -2,10 +2,10 @@ import type { Address } from "viem";
2
2
  import type { CreditAccountData } from "../base/index.js";
3
3
  import { SDKConstruct } from "../base/index.js";
4
4
  import type { GearboxSDK } from "../GearboxSDK.js";
5
- import { type CreditSuite, type OnDemandPriceUpdates, type PriceUpdateV300, type PriceUpdateV310, type UpdatePriceFeedsResult } from "../market/index.js";
5
+ import type { CreditSuite, MarketSuite, OnDemandPriceUpdates, PriceUpdateV300, PriceUpdateV310, UpdatePriceFeedsResult } from "../market/index.js";
6
6
  import { type Asset, type RouterCASlice } from "../router/index.js";
7
- import type { MultiCall, RawTx } from "../types/index.js";
8
- import type { AccountToCheck, AddCollateralProps, ChangeDeptProps, ClaimDelayedProps, CloseCreditAccountProps, CloseCreditAccountResult, CloseOptions, CreditAccountOperationResult, EnableTokensProps, ExecuteSwapProps, FullyLiquidateProps, FullyLiquidateResult, GetApprovalAddressProps, GetConnectedBotsResult, GetConnectedMigrationBotsResult, GetCreditAccountsOptions, GetPendingWithdrawalsProps, GetPendingWithdrawalsResult, OpenCAProps, PermitResult, PrepareUpdateQuotasProps, PreviewDelayedWithdrawalProps, PreviewDelayedWithdrawalResult, PriceUpdatesOptions, Rewards, StartDelayedWithdrawalProps, UpdateQuotasProps } from "./types.js";
7
+ import type { IPriceUpdateTx, MultiCall, RawTx } from "../types/index.js";
8
+ import type { AccountToCheck, AddCollateralProps, ChangeDeptProps, ClaimDelayedProps, CloseCreditAccountProps, CloseCreditAccountResult, CloseOptions, CreditAccountDataWithInvestor, CreditAccountOperationResult, EnableTokensProps, ExecuteSwapProps, FullyLiquidateProps, FullyLiquidateResult, GetApprovalAddressProps, GetConnectedBotsResult, GetConnectedMigrationBotsResult, GetCreditAccountsOptions, GetPendingWithdrawalsProps, GetPendingWithdrawalsResult, OpenCAProps, PermitResult, PrepareUpdateQuotasProps, PreviewDelayedWithdrawalProps, PreviewDelayedWithdrawalResult, PriceUpdatesOptions, Rewards, StartDelayedWithdrawalProps, UpdateQuotasProps } from "./types.js";
9
9
  export interface CreditAccountServiceOptions {
10
10
  batchSize?: number;
11
11
  }
@@ -21,6 +21,14 @@ export declare abstract class AbstractCreditAccountService extends SDKConstruct
21
21
  * @returns
22
22
  */
23
23
  getCreditAccountData(account: Address, blockNumber?: bigint): Promise<CreditAccountData | undefined>;
24
+ /**
25
+ * Returns credit account data for a single account with the investor address resolved.
26
+ * Loads CA via getCreditAccountData; for KYC underlyings fetches the investor from the KYC factory's getInvestor(creditAccount), otherwise uses the account owner.
27
+ * @param account - Credit account address
28
+ * @param blockNumber - Optional block number for the read
29
+ * @returns CreditAccountDataWithInvestor (CA data + investor address), or undefined if the account is not found
30
+ */
31
+ getCreditAccountDataWithInvestor(account: Address, blockNumber?: bigint): Promise<CreditAccountDataWithInvestor | undefined>;
24
32
  /**
25
33
  * Methods to get all credit accounts with some optional filtering
26
34
  * Performs all necessary price feed updates under the hood
@@ -29,7 +37,31 @@ export declare abstract class AbstractCreditAccountService extends SDKConstruct
29
37
  * @param blockNumber
30
38
  * @returns returned credit accounts are sorted by health factor in ascending order
31
39
  */
32
- getCreditAccounts(options?: GetCreditAccountsOptions, blockNumber?: bigint): Promise<Array<CreditAccountData>>;
40
+ getCreditAccounts(options?: GetCreditAccountsOptions, blockNumber?: bigint, priceUpdate?: UpdatePriceFeedsResult): Promise<Array<CreditAccountData>>;
41
+ /**
42
+ * Returns all credit accounts matching the filter, with investor set on each item.
43
+ * Delegates to getCreditAccounts; when options.owner is set, also loads KYC credit accounts for that owner and merges them into the list. Result is sorted by health factor ascending.
44
+ * @param options - Filter options (owner, creditManager, health factor, etc.)
45
+ * @param blockNumber - Optional block number for the read
46
+ * @returns Array of credit accounts (with investor field), sorted by health factor ascending
47
+ */
48
+ getCreditAccountsWithInvestor(options?: GetCreditAccountsOptions, blockNumber?: bigint): Promise<Array<CreditAccountDataWithInvestor>>;
49
+ protected getKYCCreditAccountsOfOwner(owner: Address, priceUpdateTxs: IPriceUpdateTx<{
50
+ priceFeed: `0x${string}`;
51
+ timestamp: number;
52
+ }>[], blockNumber?: bigint): Promise<Array<CreditAccountDataWithInvestor>>;
53
+ /**
54
+ * Loads credit account data for the given addresses using simulateWithPriceUpdates.
55
+ * Applies the provided price update txs before reading, so returned data is consistent with up-to-date prices.
56
+ * @param accounts - Credit account addresses to load
57
+ * @param priceUpdateTxs - Price feed update txs to simulate before the read (e.g. from generatePriceFeedsUpdateTxs)
58
+ * @param blockNumber - Optional block number for the read
59
+ * @returns Array of CreditAccountData in the same order as accounts (throws if any getCreditAccountData call reverts)
60
+ */
61
+ loadSpecifiedAccounts(accounts: Address[], priceUpdateTxs: IPriceUpdateTx<{
62
+ priceFeed: `0x${string}`;
63
+ timestamp: number;
64
+ }>[], blockNumber?: bigint): Promise<Array<CreditAccountData>>;
33
65
  /**
34
66
  * Method to get all claimable rewards for credit account (ex. stkUSDS SKY rewards)
35
67
  Assosiates rewards by adapter + stakedPhantomToken
@@ -179,6 +211,33 @@ export declare abstract class AbstractCreditAccountService extends SDKConstruct
179
211
  * @param ca
180
212
  */
181
213
  getOptimalHFForPartialLiquidation(ca: CreditAccountData): bigint;
214
+ /**
215
+ * Returns multicall entries to redeem (unwrap) KYC ERC-4626 vault shares into underlying for the given credit manager.
216
+ * Used when withdrawing debt from a KYC market: redeems adapter vault shares so the underlying can be withdrawn.
217
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
218
+ * @param amount - Number of vault shares (adapter tokens) to redeem
219
+ * @param creditManager - Credit manager address
220
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
221
+ */
222
+ getKYCUnwrapCalls(amount: bigint, creditManager: Address): Promise<Array<MultiCall> | undefined>;
223
+ /**
224
+ * Returns multicall entries to deposit (wrap) underlying into KYC ERC-4626 vault shares for the given credit manager.
225
+ * Used when adding debt on a KYC market: deposits underlying into the adapter vault so shares are minted on the account.
226
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
227
+ * @param amount - Amount of underlying assets to deposit into the vault (in underlying decimals)
228
+ * @param creditManager - Credit manager address
229
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
230
+ */
231
+ getKYCWrapCalls(amount: bigint, creditManager: Address): Promise<Array<MultiCall> | undefined>;
232
+ /**
233
+ * Returns multicall entries to call redeemDiff on the KYC ERC-4626 adapter for the given credit manager.
234
+ * Redeems the leftover vault shares (e.g. after repaying debt) so the account does not hold excess KYC vault tokens.
235
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
236
+ * @param amount - Leftover vault share amount to redeem (in adapter/vault decimals)
237
+ * @param creditManager - Credit manager address
238
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
239
+ */
240
+ getRedeemDiffCalls(amount: bigint, creditManager: Address): Promise<Array<MultiCall> | undefined>;
182
241
  /**
183
242
  * Returns raw txs that are needed to update all price feeds so that all credit accounts (possibly from different markets) compute
184
243
  *
@@ -242,4 +301,12 @@ export declare abstract class AbstractCreditAccountService extends SDKConstruct
242
301
  * @returns
243
302
  */
244
303
  protected closeCreditAccountTx(suite: CreditSuite, creditAccount: Address, calls: MultiCall[], operation: CloseOptions): Promise<RawTx>;
304
+ /**
305
+ * Returns all KYC credit account addresses for an investor across the given market suites.
306
+ * Resolves KYC factory per suite, then multicalls each factory's getCreditAccounts(investor).
307
+ * @param investor - Owner address to query
308
+ * @param suites - Market suites (KYC factories are resolved for each; undefined entries are skipped)
309
+ * @returns Flat array of credit account addresses from all KYC markets
310
+ */
311
+ protected getKYCCaOfInvestor(investor: Address, suites: Array<MarketSuite | undefined>): Promise<`0x${string}`[]>;
245
312
  }
@@ -12,7 +12,7 @@ export declare class CreditAccountServiceV310 extends AbstractCreditAccountServi
12
12
  /**
13
13
  * Implements {@link ICreditAccountsService.repayCreditAccount}
14
14
  */
15
- repayCreditAccount({ operation, collateralAssets, assetsToWithdraw, creditAccount: ca, permits, to, tokensToClaim, }: RepayCreditAccountProps): Promise<CreditAccountOperationResult>;
15
+ repayCreditAccount({ operation, collateralAssets, assetsToWithdraw, creditAccount: ca, permits, to, tokensToClaim, calls: wrapCalls, }: RepayCreditAccountProps): Promise<CreditAccountOperationResult>;
16
16
  /**
17
17
  * Implements {@link ICreditAccountsService.repayAndLiquidateCreditAccount}
18
18
  */
@@ -5,7 +5,7 @@ import type { ConnectedBotData, Construct, CreditAccountData } from "../base/ind
5
5
  import type { GearboxSDK } from "../GearboxSDK.js";
6
6
  import type { CreditSuite, OnDemandPriceUpdates, PriceUpdateV300, PriceUpdateV310, UpdatePriceFeedsResult } from "../market/index.js";
7
7
  import type { Asset, CreditAccountTokensSlice, RouterCASlice, RouterCloseResult } from "../router/index.js";
8
- import type { MultiCall, RawTx } from "../types/index.js";
8
+ import type { IPriceUpdateTx, MultiCall, RawTx } from "../types/index.js";
9
9
  export type GetCreditAccountsArgs = ContractFunctionArgs<typeof creditAccountCompressorAbi, "pure" | "view", "getCreditAccounts">;
10
10
  export interface CreditAccountFilter {
11
11
  owner: Address;
@@ -83,6 +83,10 @@ export interface CloseCreditAccountProps {
83
83
  closePath?: RouterCloseResult;
84
84
  }
85
85
  export interface RepayCreditAccountProps extends RepayAndLiquidateCreditAccountProps {
86
+ /**
87
+ * Swap calls for repay
88
+ */
89
+ calls?: Array<MultiCall>;
86
90
  /**
87
91
  * close or zeroDebt
88
92
  */
@@ -454,6 +458,9 @@ export type GetApprovalAddressProps = {
454
458
  creditManager: Address;
455
459
  creditAccount: Address;
456
460
  };
461
+ export type CreditAccountDataWithInvestor = CreditAccountData & {
462
+ investor: Address;
463
+ };
457
464
  export interface ICreditAccountsService extends Construct {
458
465
  sdk: GearboxSDK;
459
466
  /**
@@ -464,15 +471,41 @@ export interface ICreditAccountsService extends Construct {
464
471
  * @returns
465
472
  */
466
473
  getCreditAccountData(account: Address, blockNumber?: bigint): Promise<CreditAccountData | undefined>;
474
+ /**
475
+ * Returns credit account data for a single account with the investor address resolved (from KYC factory when applicable).
476
+ * @param account - Credit account address
477
+ * @param blockNumber - Optional block number for the read
478
+ * @returns CreditAccountDataWithInvestor, or undefined if the account is not found
479
+ */
480
+ getCreditAccountDataWithInvestor(account: Address, blockNumber?: bigint): Promise<CreditAccountDataWithInvestor | undefined>;
467
481
  /**
468
482
  * Methods to get all credit accounts with some optional filtering
469
483
  * Performs all necessary price feed updates under the hood
470
484
  *
471
485
  * @param options
472
486
  * @param blockNumber
473
- * @returns returned credit accounts are sorted by health factor in ascending order
487
+ * @param priceUpdate - Optional pre-computed price feed update (e.g. from generatePriceFeedsUpdateTxs)
488
+ * @returns Credit accounts sorted by health factor ascending
489
+ */
490
+ getCreditAccounts(options?: GetCreditAccountsOptions, blockNumber?: bigint, priceUpdate?: UpdatePriceFeedsResult): Promise<Array<CreditAccountData>>;
491
+ /**
492
+ * Returns all credit accounts matching the filter with investor set on each; when options.owner is set, includes KYC CAs for that owner.
493
+ * @param options - Filter options (owner, creditManager, health factor, etc.)
494
+ * @param blockNumber - Optional block number for the read
495
+ * @returns Credit accounts (with investor) sorted by health factor ascending
496
+ */
497
+ getCreditAccountsWithInvestor(options?: GetCreditAccountsOptions, blockNumber?: bigint): Promise<Array<CreditAccountDataWithInvestor>>;
498
+ /**
499
+ * Loads credit account data for the given addresses using simulateWithPriceUpdates (with price updates applied before the read).
500
+ * @param accounts - Credit account addresses to load
501
+ * @param priceUpdateTxs - Price feed update txs to simulate before the read (e.g. from generatePriceFeedsUpdateTxs)
502
+ * @param blockNumber - Optional block number for the read
503
+ * @returns Array of CreditAccountData in the same order as accounts
474
504
  */
475
- getCreditAccounts(options?: GetCreditAccountsOptions, blockNumber?: bigint): Promise<Array<CreditAccountData>>;
505
+ loadSpecifiedAccounts(accounts: Address[], priceUpdateTxs: IPriceUpdateTx<{
506
+ priceFeed: `0x${string}`;
507
+ timestamp: number;
508
+ }>[], blockNumber?: bigint): Promise<Array<CreditAccountData>>;
476
509
  /**
477
510
  * Method to get all claimable rewards for credit account (ex. stkUSDS SKY rewards)
478
511
  * Assosiates rewards by adapter + stakedPhantomToken
@@ -501,9 +534,9 @@ export interface ICreditAccountsService extends Construct {
501
534
  /**
502
535
  * Generates transaction to liquidate credit account
503
536
  * @param props - {@link FullyLiquidateProps}
504
- * @returns
537
+ * @returns Transaction data and optional loss policy data
505
538
  */
506
- fullyLiquidate(props: FullyLiquidateProps): Promise<CloseCreditAccountResult>;
539
+ fullyLiquidate(props: FullyLiquidateProps): Promise<FullyLiquidateResult>;
507
540
  /**
508
541
  * Closes credit account or closes credit account and keeps it open with zero debt.
509
542
  * - Ca is closed in the following order: price update -> close path to swap all tokens into underlying ->
@@ -623,6 +656,33 @@ export interface ICreditAccountsService extends Construct {
623
656
  * @returns
624
657
  */
625
658
  getPriceUpdatesForFacade(options: PriceUpdatesOptions): Promise<Array<MultiCall>>;
659
+ /**
660
+ * Returns multicall entries to redeem (unwrap) KYC ERC-4626 vault shares into underlying for the given credit manager.
661
+ * Used when withdrawing debt from a KYC market: redeems adapter vault shares so the underlying can be withdrawn.
662
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
663
+ * @param amount - Number of vault shares (adapter tokens) to redeem
664
+ * @param creditManager - Credit manager address
665
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
666
+ */
667
+ getKYCUnwrapCalls(amount: bigint, creditManager: Address): Promise<Array<MultiCall> | undefined>;
668
+ /**
669
+ * Returns multicall entries to deposit (wrap) underlying into KYC ERC-4626 vault shares for the given credit manager.
670
+ * Used when adding debt on a KYC market: deposits underlying into the adapter vault so shares are minted on the account.
671
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
672
+ * @param amount - Amount of underlying assets to deposit into the vault (in underlying decimals)
673
+ * @param creditManager - Credit manager address
674
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
675
+ */
676
+ getKYCWrapCalls(amount: bigint, creditManager: Address): Promise<Array<MultiCall> | undefined>;
677
+ /**
678
+ * Returns multicall entries to call redeemDiff on the KYC ERC-4626 adapter for the given credit manager.
679
+ * Redeems the leftover vault shares (e.g. after repaying debt) so the account does not hold excess KYC vault tokens.
680
+ * Only applies when the credit manager's underlying is KYC-gated and has an ERC-4626 adapter configured.
681
+ * @param amount - Leftover vault share amount to redeem (in adapter/vault decimals)
682
+ * @param creditManager - Credit manager address
683
+ * @returns Array of MultiCall to pass to credit facade multicall, or undefined if underlying is not KYC or no adapter is configured
684
+ */
685
+ getRedeemDiffCalls(amount: bigint, creditManager: Address): Promise<Array<MultiCall> | undefined>;
626
686
  /**
627
687
  * Withdraws a single collateral from credit account to wallet to and updates quotas;
628
688
  * technically can withdraw several tokens at once
@@ -435,9 +435,16 @@ declare const abi: readonly [{
435
435
  }];
436
436
  type abi = typeof abi;
437
437
  export declare class SecuritizeKYCFactory extends BaseContract<abi> {
438
+ private investorCache;
438
439
  constructor(options: ConstructOptions, address: Address);
439
440
  precomputeWalletAddress(creditManager: Address, investor: Address): Promise<Address>;
440
441
  getWallet(creditAccount: Address): Promise<Address>;
442
+ /**
443
+ * Returns the investor address for a credit account.
444
+ * @param creditAccount - Credit account address
445
+ * @param fromCache - If true, use and update an in-memory cache (creditAccount -> investor). On cache miss, loads from contract and stores the result for future calls.
446
+ */
447
+ getInvestor(creditAccount: Address, fromCache?: boolean): Promise<Address>;
441
448
  getDSTokens(): Promise<Address[]>;
442
449
  multicall(creditAccount: Address, calls: MultiCall[], tokensToRegister: Address[]): RawTx;
443
450
  openCreditAccount(creditManager: Address, calls: MultiCall[], tokensToRegister: Address[]): RawTx;
@@ -103,6 +103,7 @@ export declare class CreditAccountData_Legacy {
103
103
  readonly underlying: Address;
104
104
  readonly expirationDate: number;
105
105
  readonly version: number;
106
+ readonly investor: Address;
106
107
  readonly enabledTokensMask: bigint;
107
108
  readonly healthFactor: number;
108
109
  readonly baseBorrowRateWithoutFee: number;
@@ -14,6 +14,7 @@ export interface CaTokenBalance {
14
14
  export interface CreditAccountDataPayload {
15
15
  isSuccessful: boolean;
16
16
  addr: Address;
17
+ investor: Address;
17
18
  borrower: Address;
18
19
  creditManager: Address;
19
20
  creditFacade: Address;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gearbox-protocol/sdk",
3
- "version": "13.0.0-next.21",
3
+ "version": "13.0.0-next.22",
4
4
  "description": "Gearbox SDK",
5
5
  "license": "MIT",
6
6
  "main": "./dist/cjs/sdk/index.js",