@gearbox-protocol/sdk 13.0.0-next.16 → 13.0.0-next.18

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.
@@ -50,6 +50,13 @@ const iSecuritizeKYCFactoryAbi = [
50
50
  outputs: [{ name: "", type: "address[]", internalType: "address[]" }],
51
51
  stateMutability: "view"
52
52
  },
53
+ {
54
+ type: "function",
55
+ name: "getDSTokens",
56
+ inputs: [],
57
+ outputs: [{ name: "", type: "address[]", internalType: "address[]" }],
58
+ stateMutability: "view"
59
+ },
53
60
  {
54
61
  type: "function",
55
62
  name: "getInvestor",
@@ -787,43 +787,52 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
787
787
  const tx = await this.multicallTx(cm, ca.creditAccount, calls);
788
788
  return { tx, calls, creditFacade: cm.creditFacade };
789
789
  }
790
+ /**
791
+ * Returns address to which approval should be given on collateral token
792
+ * It's credit manager for classical markets and special wallet for KYC markets
793
+ * @param options - {@link GetApprovalAddressProps}
794
+ * @returns
795
+ **/
796
+ async getApprovalAddress(options) {
797
+ const { creditManager } = options;
798
+ 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);
803
+ if ("creditAccount" in options) {
804
+ return factory.getWallet(options.creditAccount);
805
+ }
806
+ return factory.precomputeWalletAddress(creditManager, options.borrower);
807
+ }
808
+ return suite.creditManager.address;
809
+ }
790
810
  /**
791
811
  * Executes swap specified by given calls, update quotas of affected tokens
792
- - Open credit account is executed in the following order: price update -> increase debt -> add collateral ->
793
- -> update quotas -> (optionally: execute swap path for trading/strategy) ->
794
- -> (optionally: withdraw debt for lending)
795
- - Basic open credit account: price update -> increase debt -> add collateral -> update quotas
796
- - Lending: price update -> increase debt -> add collateral -> update quotas -> withdraw debt
797
- - Strategy/trading: price update -> increase debt -> add collateral -> update quotas -> execute swap path
798
- - In strategy is possible situation when collateral is added, but not swapped; the only swapped value in this case will be debt
799
- * @param {bigint} ethAmount - native token amount to attach to tx
800
- * @param {Address} creditManager - address of credit manager to open credit account on
801
- * @param {Array<Asset>} collateral - array of collateral which can be just directly added or swapped using the path {@link Asset}
802
- * @param {Record<Address, PermitResult>} permits - permits of collateral tokens (in any permittable token is present) {@link PermitResult}
803
- * @param {bigint} debt - debt to open credit account with
804
- * @param {boolean} withdrawDebt - flag to withdraw debt to wallet after opening credit account;
805
- used for borrowing functionality
806
- * @param {bigint} referralCode - referral code to open credit account with
807
- * @param {Address} to - wallet address to transfer credit account to\
808
- * @param {Array<MultiCall>} calls - array of MultiCall from router methods findOpenStrategyPath {@link MultiCall}.
809
- Used for trading and strategy functionality
810
- * @param {Array<Asset>} averageQuota - average quota for tokens after open {@link Asset}
811
- * @param {Array<Asset>} minQuota - minimum quota for tokens after open {@link Asset}
812
+ * - Open credit account is executed in the following order: price update -> increase debt -> add collateral ->
813
+ * -> update quotas -> (optionally: execute swap path for trading/strategy) ->
814
+ * -> (optionally: withdraw debt for lending)
815
+ *- Basic open credit account: price update -> increase debt -> add collateral -> update quotas
816
+ *- Lending: price update -> increase debt -> add collateral -> update quotas -> withdraw debt
817
+ *- Strategy/trading: price update -> increase debt -> add collateral -> update quotas -> execute swap path
818
+ *- In strategy is possible situation when collateral is added, but not swapped; the only swapped value in this case will be debt
812
819
  * @returns All necessary data to execute the transaction (call, credit facade)
813
- */
814
- async openCA({
815
- ethAmount,
816
- creditManager,
817
- collateral,
818
- permits,
819
- debt,
820
- withdrawToken,
821
- referralCode,
822
- to,
823
- calls: openPathCalls,
824
- minQuota,
825
- averageQuota
826
- }) {
820
+ **/
821
+ async openCA(props) {
822
+ const {
823
+ ethAmount,
824
+ creditManager,
825
+ reopenCreditAccount,
826
+ collateral,
827
+ permits,
828
+ debt,
829
+ withdrawToken,
830
+ referralCode,
831
+ to,
832
+ calls: openPathCalls,
833
+ minQuota,
834
+ averageQuota
835
+ } = props;
827
836
  const cmSuite = this.sdk.marketRegister.findCreditManager(creditManager);
828
837
  const cm = cmSuite.creditManager;
829
838
  let tokenToWithdraw;
@@ -855,7 +864,12 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
855
864
  averageQuota
856
865
  })
857
866
  ];
858
- const tx = await this.openCreditAccountTx(cmSuite, to, calls, referralCode);
867
+ let tx;
868
+ if (reopenCreditAccount) {
869
+ tx = await this.multicallTx(cmSuite, reopenCreditAccount, calls);
870
+ } else {
871
+ tx = await this.openCreditAccountTx(cmSuite, to, calls, referralCode);
872
+ }
859
873
  tx.value = ethAmount.toString(10);
860
874
  return { calls, tx, creditFacade: cmSuite.creditFacade };
861
875
  }
@@ -1208,8 +1222,8 @@ class AbstractCreditAccountService extends import_base.SDKConstruct {
1208
1222
  await this.sdk.tokensMeta.loadTokenData(suite.underlying);
1209
1223
  const underlying = this.sdk.tokensMeta.mustGet(suite.underlying);
1210
1224
  if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
1211
- const tokensToRegister = [];
1212
1225
  const factory = new import_market.SecuritizeKYCFactory(this.sdk, underlying.kycFactory);
1226
+ const tokensToRegister = await factory.getDSTokens();
1213
1227
  return factory.openCreditAccount(
1214
1228
  suite.creditManager.address,
1215
1229
  calls,
@@ -48,7 +48,7 @@ class ChainContractsRegister {
48
48
  logger;
49
49
  constructor(client, logger) {
50
50
  this.client = client;
51
- this.tokensMeta = new import_TokensMeta.TokensMeta(client);
51
+ this.tokensMeta = new import_TokensMeta.TokensMeta(client, logger);
52
52
  this.logger = logger;
53
53
  }
54
54
  resetContracts() {
@@ -22,16 +22,19 @@ __export(TokensMeta_exports, {
22
22
  });
23
23
  module.exports = __toCommonJS(TokensMeta_exports);
24
24
  var import_viem = require("viem");
25
+ var import_iSecuritizeKYCFactory = require("../../abi/310/iSecuritizeKYCFactory.js");
25
26
  var import_iStateSerializer = require("../../abi/iStateSerializer.js");
26
27
  var import_iVersion = require("../../abi/iVersion.js");
27
- var import__ = require("../index.js");
28
28
  var import_utils = require("../utils/index.js");
29
+ var import_token_types = require("./token-types.js");
29
30
  class TokensMeta extends import_utils.AddressMap {
30
31
  #client;
31
32
  #tokenDataLoaded = new import_utils.AddressSet();
32
- constructor(client) {
33
+ #logger;
34
+ constructor(client, logger) {
33
35
  super(void 0, "tokensMeta");
34
36
  this.#client = client;
37
+ this.#logger = logger?.child?.({ name: "TokensMeta" }) ?? logger;
35
38
  }
36
39
  reset() {
37
40
  this.clear();
@@ -43,6 +46,11 @@ class TokensMeta extends import_utils.AddressMap {
43
46
  decimals(token) {
44
47
  return this.mustGet(token).decimals;
45
48
  }
49
+ /**
50
+ * Returns true if the token is a phantom token, throws if the token data is not loaded
51
+ * @param t
52
+ * @returns
53
+ */
46
54
  isPhantomToken(t) {
47
55
  if (!this.#tokenDataLoaded.has(t.addr)) {
48
56
  throw new Error(
@@ -51,6 +59,11 @@ class TokensMeta extends import_utils.AddressMap {
51
59
  }
52
60
  return "contractType" in t && t.contractType.startsWith("PHANTOM_TOKEN::");
53
61
  }
62
+ /**
63
+ * Returns true if the token is a KYC underlying token, throws if the token data is not loaded
64
+ * @param t
65
+ * @returns
66
+ */
54
67
  isKYCUnderlying(t) {
55
68
  if (!this.#tokenDataLoaded.has(t.addr)) {
56
69
  throw new Error(
@@ -59,9 +72,22 @@ class TokensMeta extends import_utils.AddressMap {
59
72
  }
60
73
  return "contractType" in t && t.contractType.startsWith("KYC_UNDERLYING::");
61
74
  }
75
+ /**
76
+ * Returns true if the token is a DSToken, throws if the token data is not loaded
77
+ * @param t
78
+ * @returns
79
+ */
80
+ isDSToken(t) {
81
+ if (!this.#tokenDataLoaded.has(t.addr)) {
82
+ throw new Error(
83
+ `extended token data not loaded for ${t.symbol} (${t.addr})`
84
+ );
85
+ }
86
+ return !!t.isDSToken;
87
+ }
62
88
  /**
63
89
  * Returns a map of all phantom tokens
64
- * Throws if the phantom token data is not loaded
90
+ * Throws if token data is not loaded
65
91
  */
66
92
  get phantomTokens() {
67
93
  const result = new import_utils.AddressMap();
@@ -72,6 +98,10 @@ class TokensMeta extends import_utils.AddressMap {
72
98
  }
73
99
  return result;
74
100
  }
101
+ /**
102
+ * Returns a map of all KYC underlying tokens
103
+ * Throws if token data is not loaded
104
+ */
75
105
  get kycUnderlyings() {
76
106
  const result = new import_utils.AddressMap();
77
107
  for (const [token, meta] of this.entries()) {
@@ -81,6 +111,15 @@ class TokensMeta extends import_utils.AddressMap {
81
111
  }
82
112
  return result;
83
113
  }
114
+ get dsTokens() {
115
+ const result = new import_utils.AddressMap();
116
+ for (const [token, meta] of this.entries()) {
117
+ if (this.isDSToken(meta)) {
118
+ result.upsert(token, meta);
119
+ }
120
+ }
121
+ return result;
122
+ }
84
123
  formatBN(arg0, arg1, arg2) {
85
124
  const token = typeof arg0 === "object" ? arg0.token : arg0;
86
125
  const amount = typeof arg0 === "object" ? arg0.balance : arg1;
@@ -100,7 +139,7 @@ class TokensMeta extends import_utils.AddressMap {
100
139
  return meta;
101
140
  }
102
141
  /**
103
- * Loads token information about phantom token and KYC underlying tokens
142
+ * Loads token information about phantom tokens, KYC underlying tokens and DSTokens
104
143
  *
105
144
  * @param tokens - tokens to load data for, defaults to all tokens
106
145
  */
@@ -128,15 +167,26 @@ class TokensMeta extends import_utils.AddressMap {
128
167
  allowFailure: true,
129
168
  batchSize: 0
130
169
  });
170
+ this.#logger?.debug(`loaded ${resp.length} contract types`);
171
+ const kycFactories = new import_utils.AddressSet();
131
172
  for (let i = 0; i < tokensToLoad.length; i++) {
132
- this.#overrideTokenMeta(tokensToLoad[i], resp[2 * i], resp[2 * i + 1]);
173
+ const meta = this.#overrideTokenMeta(
174
+ tokensToLoad[i],
175
+ resp[2 * i],
176
+ resp[2 * i + 1]
177
+ );
133
178
  this.#tokenDataLoaded.add(tokensToLoad[i]);
179
+ if (this.isKYCUnderlying(meta)) {
180
+ kycFactories.add(meta.kycFactory);
181
+ }
134
182
  }
183
+ this.#logger?.debug(`found ${kycFactories.size} KYC factories`);
184
+ await this.#loadDSTokens(kycFactories);
135
185
  }
136
186
  #overrideTokenMeta(token, contractTypeResp, serializeResp) {
137
187
  const meta = this.mustGet(token);
138
188
  if (contractTypeResp.status === "success") {
139
- const contractType = (0, import__.bytes32ToString)(contractTypeResp.result);
189
+ const contractType = (0, import_utils.bytes32ToString)(contractTypeResp.result);
140
190
  if (contractType.startsWith("KYC_UNDERLYING::")) {
141
191
  if (serializeResp.status === "success") {
142
192
  this.#overrideKYCUnderlying(meta, contractType, serializeResp.result);
@@ -151,10 +201,12 @@ class TokensMeta extends import_utils.AddressMap {
151
201
  contractType
152
202
  });
153
203
  }
204
+ this.#logger?.debug(`token ${meta.symbol} is ${contractType}`);
154
205
  }
206
+ return this.mustGet(token);
155
207
  }
156
208
  #overrideKYCUnderlying(meta, contractType, serialized) {
157
- if (contractType === import__.KYC_UNDERLYING_DEFAULT) {
209
+ if (contractType === import_token_types.KYC_UNDERLYING_DEFAULT) {
158
210
  const decoded = (0, import_viem.decodeAbiParameters)(
159
211
  [
160
212
  { type: "address", name: "kycFactory" },
@@ -168,7 +220,7 @@ class TokensMeta extends import_utils.AddressMap {
168
220
  kycFactory: decoded[0],
169
221
  asset: decoded[1]
170
222
  });
171
- } else if (contractType === import__.KYC_UNDERLYING_ON_DEMAND) {
223
+ } else if (contractType === import_token_types.KYC_UNDERLYING_ON_DEMAND) {
172
224
  const decoded = (0, import_viem.decodeAbiParameters)(
173
225
  [
174
226
  { type: "address", name: "kycFactory" },
@@ -188,6 +240,72 @@ class TokensMeta extends import_utils.AddressMap {
188
240
  });
189
241
  }
190
242
  }
243
+ async #loadDSTokens(kycFactories) {
244
+ const resp = await this.#client.multicall({
245
+ contracts: kycFactories.map((address) => ({
246
+ address,
247
+ abi: import_iSecuritizeKYCFactory.iSecuritizeKYCFactoryAbi,
248
+ functionName: "getDSTokens"
249
+ })),
250
+ allowFailure: false,
251
+ batchSize: 0
252
+ });
253
+ const dsToken = new import_utils.AddressSet(resp.flat());
254
+ const tokensToLoad = dsToken.difference(new Set(this.keys()));
255
+ this.#logger?.debug(
256
+ `found ${dsToken.size} DSTokens in KYC factories, need to load ${tokensToLoad.size} basic metadata`
257
+ );
258
+ await this.#loadWithoutCompressor(tokensToLoad);
259
+ for (const token of dsToken) {
260
+ const meta = this.mustGet(token);
261
+ this.upsert(token, {
262
+ ...meta,
263
+ isDSToken: true
264
+ });
265
+ this.#tokenDataLoaded.add(token);
266
+ this.#logger?.debug(`token ${meta.symbol} (${token}) is a DSToken`);
267
+ }
268
+ }
269
+ async #loadWithoutCompressor(tokens_) {
270
+ if (tokens_.size === 0) {
271
+ return;
272
+ }
273
+ const tokens = Array.from(tokens_);
274
+ const resp = await this.#client.multicall({
275
+ contracts: tokens.flatMap(
276
+ (t) => [
277
+ {
278
+ address: t,
279
+ abi: import_viem.erc20Abi,
280
+ functionName: "symbol"
281
+ },
282
+ {
283
+ address: t,
284
+ abi: import_viem.erc20Abi,
285
+ functionName: "name"
286
+ },
287
+ {
288
+ address: t,
289
+ abi: import_viem.erc20Abi,
290
+ functionName: "decimals"
291
+ }
292
+ ]
293
+ ),
294
+ allowFailure: false,
295
+ batchSize: 0
296
+ });
297
+ this.#logger?.debug(
298
+ `loaded ${resp.length} basic metadata without compressor`
299
+ );
300
+ for (let i = 0; i < tokens.length; i++) {
301
+ this.upsert(tokens[i], {
302
+ addr: tokens[i],
303
+ symbol: resp[3 * i],
304
+ name: resp[3 * i + 1],
305
+ decimals: resp[3 * i + 2]
306
+ });
307
+ }
308
+ }
191
309
  }
192
310
  // Annotate the CommonJS export names for ESM import in node:
193
311
  0 && (module.exports = {
@@ -32,6 +32,19 @@ class SecuritizeKYCFactory extends import_base.BaseContract {
32
32
  abi
33
33
  });
34
34
  }
35
+ async precomputeWalletAddress(creditManager, investor) {
36
+ return this.contract.read.precomputeWalletAddress([
37
+ creditManager,
38
+ investor
39
+ ]);
40
+ }
41
+ async getWallet(creditAccount) {
42
+ return this.contract.read.getWallet([creditAccount]);
43
+ }
44
+ async getDSTokens() {
45
+ const tokens = await this.contract.read.getDSTokens();
46
+ return [...tokens];
47
+ }
35
48
  multicall(creditAccount, calls, tokensToRegister) {
36
49
  return this.createRawTx({
37
50
  functionName: "multicall",
@@ -27,6 +27,13 @@ const iSecuritizeKYCFactoryAbi = [
27
27
  outputs: [{ name: "", type: "address[]", internalType: "address[]" }],
28
28
  stateMutability: "view"
29
29
  },
30
+ {
31
+ type: "function",
32
+ name: "getDSTokens",
33
+ inputs: [],
34
+ outputs: [{ name: "", type: "address[]", internalType: "address[]" }],
35
+ stateMutability: "view"
36
+ },
30
37
  {
31
38
  type: "function",
32
39
  name: "getInvestor",
@@ -779,43 +779,52 @@ class AbstractCreditAccountService extends SDKConstruct {
779
779
  const tx = await this.multicallTx(cm, ca.creditAccount, calls);
780
780
  return { tx, calls, creditFacade: cm.creditFacade };
781
781
  }
782
+ /**
783
+ * Returns address to which approval should be given on collateral token
784
+ * It's credit manager for classical markets and special wallet for KYC markets
785
+ * @param options - {@link GetApprovalAddressProps}
786
+ * @returns
787
+ **/
788
+ async getApprovalAddress(options) {
789
+ const { creditManager } = options;
790
+ 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);
795
+ if ("creditAccount" in options) {
796
+ return factory.getWallet(options.creditAccount);
797
+ }
798
+ return factory.precomputeWalletAddress(creditManager, options.borrower);
799
+ }
800
+ return suite.creditManager.address;
801
+ }
782
802
  /**
783
803
  * Executes swap specified by given calls, update quotas of affected tokens
784
- - Open credit account is executed in the following order: price update -> increase debt -> add collateral ->
785
- -> update quotas -> (optionally: execute swap path for trading/strategy) ->
786
- -> (optionally: withdraw debt for lending)
787
- - Basic open credit account: price update -> increase debt -> add collateral -> update quotas
788
- - Lending: price update -> increase debt -> add collateral -> update quotas -> withdraw debt
789
- - Strategy/trading: price update -> increase debt -> add collateral -> update quotas -> execute swap path
790
- - In strategy is possible situation when collateral is added, but not swapped; the only swapped value in this case will be debt
791
- * @param {bigint} ethAmount - native token amount to attach to tx
792
- * @param {Address} creditManager - address of credit manager to open credit account on
793
- * @param {Array<Asset>} collateral - array of collateral which can be just directly added or swapped using the path {@link Asset}
794
- * @param {Record<Address, PermitResult>} permits - permits of collateral tokens (in any permittable token is present) {@link PermitResult}
795
- * @param {bigint} debt - debt to open credit account with
796
- * @param {boolean} withdrawDebt - flag to withdraw debt to wallet after opening credit account;
797
- used for borrowing functionality
798
- * @param {bigint} referralCode - referral code to open credit account with
799
- * @param {Address} to - wallet address to transfer credit account to\
800
- * @param {Array<MultiCall>} calls - array of MultiCall from router methods findOpenStrategyPath {@link MultiCall}.
801
- Used for trading and strategy functionality
802
- * @param {Array<Asset>} averageQuota - average quota for tokens after open {@link Asset}
803
- * @param {Array<Asset>} minQuota - minimum quota for tokens after open {@link Asset}
804
+ * - Open credit account is executed in the following order: price update -> increase debt -> add collateral ->
805
+ * -> update quotas -> (optionally: execute swap path for trading/strategy) ->
806
+ * -> (optionally: withdraw debt for lending)
807
+ *- Basic open credit account: price update -> increase debt -> add collateral -> update quotas
808
+ *- Lending: price update -> increase debt -> add collateral -> update quotas -> withdraw debt
809
+ *- Strategy/trading: price update -> increase debt -> add collateral -> update quotas -> execute swap path
810
+ *- In strategy is possible situation when collateral is added, but not swapped; the only swapped value in this case will be debt
804
811
  * @returns All necessary data to execute the transaction (call, credit facade)
805
- */
806
- async openCA({
807
- ethAmount,
808
- creditManager,
809
- collateral,
810
- permits,
811
- debt,
812
- withdrawToken,
813
- referralCode,
814
- to,
815
- calls: openPathCalls,
816
- minQuota,
817
- averageQuota
818
- }) {
812
+ **/
813
+ async openCA(props) {
814
+ const {
815
+ ethAmount,
816
+ creditManager,
817
+ reopenCreditAccount,
818
+ collateral,
819
+ permits,
820
+ debt,
821
+ withdrawToken,
822
+ referralCode,
823
+ to,
824
+ calls: openPathCalls,
825
+ minQuota,
826
+ averageQuota
827
+ } = props;
819
828
  const cmSuite = this.sdk.marketRegister.findCreditManager(creditManager);
820
829
  const cm = cmSuite.creditManager;
821
830
  let tokenToWithdraw;
@@ -847,7 +856,12 @@ class AbstractCreditAccountService extends SDKConstruct {
847
856
  averageQuota
848
857
  })
849
858
  ];
850
- const tx = await this.openCreditAccountTx(cmSuite, to, calls, referralCode);
859
+ let tx;
860
+ if (reopenCreditAccount) {
861
+ tx = await this.multicallTx(cmSuite, reopenCreditAccount, calls);
862
+ } else {
863
+ tx = await this.openCreditAccountTx(cmSuite, to, calls, referralCode);
864
+ }
851
865
  tx.value = ethAmount.toString(10);
852
866
  return { calls, tx, creditFacade: cmSuite.creditFacade };
853
867
  }
@@ -1200,8 +1214,8 @@ class AbstractCreditAccountService extends SDKConstruct {
1200
1214
  await this.sdk.tokensMeta.loadTokenData(suite.underlying);
1201
1215
  const underlying = this.sdk.tokensMeta.mustGet(suite.underlying);
1202
1216
  if (this.sdk.tokensMeta.isKYCUnderlying(underlying)) {
1203
- const tokensToRegister = [];
1204
1217
  const factory = new SecuritizeKYCFactory(this.sdk, underlying.kycFactory);
1218
+ const tokensToRegister = await factory.getDSTokens();
1205
1219
  return factory.openCreditAccount(
1206
1220
  suite.creditManager.address,
1207
1221
  calls,
@@ -25,7 +25,7 @@ class ChainContractsRegister {
25
25
  logger;
26
26
  constructor(client, logger) {
27
27
  this.client = client;
28
- this.tokensMeta = new TokensMeta(client);
28
+ this.tokensMeta = new TokensMeta(client, logger);
29
29
  this.logger = logger;
30
30
  }
31
31
  resetContracts() {
@@ -1,20 +1,28 @@
1
1
  import {
2
- decodeAbiParameters
2
+ decodeAbiParameters,
3
+ erc20Abi
3
4
  } from "viem";
5
+ import { iSecuritizeKYCFactoryAbi } from "../../abi/310/iSecuritizeKYCFactory.js";
4
6
  import { iStateSerializerAbi } from "../../abi/iStateSerializer.js";
5
7
  import { iVersionAbi } from "../../abi/iVersion.js";
6
8
  import {
9
+ AddressMap,
10
+ AddressSet,
7
11
  bytes32ToString,
12
+ formatBN
13
+ } from "../utils/index.js";
14
+ import {
8
15
  KYC_UNDERLYING_DEFAULT,
9
16
  KYC_UNDERLYING_ON_DEMAND
10
- } from "../index.js";
11
- import { AddressMap, AddressSet, formatBN } from "../utils/index.js";
17
+ } from "./token-types.js";
12
18
  class TokensMeta extends AddressMap {
13
19
  #client;
14
20
  #tokenDataLoaded = new AddressSet();
15
- constructor(client) {
21
+ #logger;
22
+ constructor(client, logger) {
16
23
  super(void 0, "tokensMeta");
17
24
  this.#client = client;
25
+ this.#logger = logger?.child?.({ name: "TokensMeta" }) ?? logger;
18
26
  }
19
27
  reset() {
20
28
  this.clear();
@@ -26,6 +34,11 @@ class TokensMeta extends AddressMap {
26
34
  decimals(token) {
27
35
  return this.mustGet(token).decimals;
28
36
  }
37
+ /**
38
+ * Returns true if the token is a phantom token, throws if the token data is not loaded
39
+ * @param t
40
+ * @returns
41
+ */
29
42
  isPhantomToken(t) {
30
43
  if (!this.#tokenDataLoaded.has(t.addr)) {
31
44
  throw new Error(
@@ -34,6 +47,11 @@ class TokensMeta extends AddressMap {
34
47
  }
35
48
  return "contractType" in t && t.contractType.startsWith("PHANTOM_TOKEN::");
36
49
  }
50
+ /**
51
+ * Returns true if the token is a KYC underlying token, throws if the token data is not loaded
52
+ * @param t
53
+ * @returns
54
+ */
37
55
  isKYCUnderlying(t) {
38
56
  if (!this.#tokenDataLoaded.has(t.addr)) {
39
57
  throw new Error(
@@ -42,9 +60,22 @@ class TokensMeta extends AddressMap {
42
60
  }
43
61
  return "contractType" in t && t.contractType.startsWith("KYC_UNDERLYING::");
44
62
  }
63
+ /**
64
+ * Returns true if the token is a DSToken, throws if the token data is not loaded
65
+ * @param t
66
+ * @returns
67
+ */
68
+ isDSToken(t) {
69
+ if (!this.#tokenDataLoaded.has(t.addr)) {
70
+ throw new Error(
71
+ `extended token data not loaded for ${t.symbol} (${t.addr})`
72
+ );
73
+ }
74
+ return !!t.isDSToken;
75
+ }
45
76
  /**
46
77
  * Returns a map of all phantom tokens
47
- * Throws if the phantom token data is not loaded
78
+ * Throws if token data is not loaded
48
79
  */
49
80
  get phantomTokens() {
50
81
  const result = new AddressMap();
@@ -55,6 +86,10 @@ class TokensMeta extends AddressMap {
55
86
  }
56
87
  return result;
57
88
  }
89
+ /**
90
+ * Returns a map of all KYC underlying tokens
91
+ * Throws if token data is not loaded
92
+ */
58
93
  get kycUnderlyings() {
59
94
  const result = new AddressMap();
60
95
  for (const [token, meta] of this.entries()) {
@@ -64,6 +99,15 @@ class TokensMeta extends AddressMap {
64
99
  }
65
100
  return result;
66
101
  }
102
+ get dsTokens() {
103
+ const result = new AddressMap();
104
+ for (const [token, meta] of this.entries()) {
105
+ if (this.isDSToken(meta)) {
106
+ result.upsert(token, meta);
107
+ }
108
+ }
109
+ return result;
110
+ }
67
111
  formatBN(arg0, arg1, arg2) {
68
112
  const token = typeof arg0 === "object" ? arg0.token : arg0;
69
113
  const amount = typeof arg0 === "object" ? arg0.balance : arg1;
@@ -83,7 +127,7 @@ class TokensMeta extends AddressMap {
83
127
  return meta;
84
128
  }
85
129
  /**
86
- * Loads token information about phantom token and KYC underlying tokens
130
+ * Loads token information about phantom tokens, KYC underlying tokens and DSTokens
87
131
  *
88
132
  * @param tokens - tokens to load data for, defaults to all tokens
89
133
  */
@@ -111,10 +155,21 @@ class TokensMeta extends AddressMap {
111
155
  allowFailure: true,
112
156
  batchSize: 0
113
157
  });
158
+ this.#logger?.debug(`loaded ${resp.length} contract types`);
159
+ const kycFactories = new AddressSet();
114
160
  for (let i = 0; i < tokensToLoad.length; i++) {
115
- this.#overrideTokenMeta(tokensToLoad[i], resp[2 * i], resp[2 * i + 1]);
161
+ const meta = this.#overrideTokenMeta(
162
+ tokensToLoad[i],
163
+ resp[2 * i],
164
+ resp[2 * i + 1]
165
+ );
116
166
  this.#tokenDataLoaded.add(tokensToLoad[i]);
167
+ if (this.isKYCUnderlying(meta)) {
168
+ kycFactories.add(meta.kycFactory);
169
+ }
117
170
  }
171
+ this.#logger?.debug(`found ${kycFactories.size} KYC factories`);
172
+ await this.#loadDSTokens(kycFactories);
118
173
  }
119
174
  #overrideTokenMeta(token, contractTypeResp, serializeResp) {
120
175
  const meta = this.mustGet(token);
@@ -134,7 +189,9 @@ class TokensMeta extends AddressMap {
134
189
  contractType
135
190
  });
136
191
  }
192
+ this.#logger?.debug(`token ${meta.symbol} is ${contractType}`);
137
193
  }
194
+ return this.mustGet(token);
138
195
  }
139
196
  #overrideKYCUnderlying(meta, contractType, serialized) {
140
197
  if (contractType === KYC_UNDERLYING_DEFAULT) {
@@ -171,6 +228,72 @@ class TokensMeta extends AddressMap {
171
228
  });
172
229
  }
173
230
  }
231
+ async #loadDSTokens(kycFactories) {
232
+ const resp = await this.#client.multicall({
233
+ contracts: kycFactories.map((address) => ({
234
+ address,
235
+ abi: iSecuritizeKYCFactoryAbi,
236
+ functionName: "getDSTokens"
237
+ })),
238
+ allowFailure: false,
239
+ batchSize: 0
240
+ });
241
+ const dsToken = new AddressSet(resp.flat());
242
+ const tokensToLoad = dsToken.difference(new Set(this.keys()));
243
+ this.#logger?.debug(
244
+ `found ${dsToken.size} DSTokens in KYC factories, need to load ${tokensToLoad.size} basic metadata`
245
+ );
246
+ await this.#loadWithoutCompressor(tokensToLoad);
247
+ for (const token of dsToken) {
248
+ const meta = this.mustGet(token);
249
+ this.upsert(token, {
250
+ ...meta,
251
+ isDSToken: true
252
+ });
253
+ this.#tokenDataLoaded.add(token);
254
+ this.#logger?.debug(`token ${meta.symbol} (${token}) is a DSToken`);
255
+ }
256
+ }
257
+ async #loadWithoutCompressor(tokens_) {
258
+ if (tokens_.size === 0) {
259
+ return;
260
+ }
261
+ const tokens = Array.from(tokens_);
262
+ const resp = await this.#client.multicall({
263
+ contracts: tokens.flatMap(
264
+ (t) => [
265
+ {
266
+ address: t,
267
+ abi: erc20Abi,
268
+ functionName: "symbol"
269
+ },
270
+ {
271
+ address: t,
272
+ abi: erc20Abi,
273
+ functionName: "name"
274
+ },
275
+ {
276
+ address: t,
277
+ abi: erc20Abi,
278
+ functionName: "decimals"
279
+ }
280
+ ]
281
+ ),
282
+ allowFailure: false,
283
+ batchSize: 0
284
+ });
285
+ this.#logger?.debug(
286
+ `loaded ${resp.length} basic metadata without compressor`
287
+ );
288
+ for (let i = 0; i < tokens.length; i++) {
289
+ this.upsert(tokens[i], {
290
+ addr: tokens[i],
291
+ symbol: resp[3 * i],
292
+ name: resp[3 * i + 1],
293
+ decimals: resp[3 * i + 2]
294
+ });
295
+ }
296
+ }
174
297
  }
175
298
  export {
176
299
  TokensMeta
@@ -9,6 +9,19 @@ class SecuritizeKYCFactory extends BaseContract {
9
9
  abi
10
10
  });
11
11
  }
12
+ async precomputeWalletAddress(creditManager, investor) {
13
+ return this.contract.read.precomputeWalletAddress([
14
+ creditManager,
15
+ investor
16
+ ]);
17
+ }
18
+ async getWallet(creditAccount) {
19
+ return this.contract.read.getWallet([creditAccount]);
20
+ }
21
+ async getDSTokens() {
22
+ const tokens = await this.contract.read.getDSTokens();
23
+ return [...tokens];
24
+ }
12
25
  multicall(creditAccount, calls, tokensToRegister) {
13
26
  return this.createRawTx({
14
27
  functionName: "multicall",
@@ -42,6 +42,16 @@ export declare const iSecuritizeKYCFactoryAbi: readonly [{
42
42
  readonly internalType: "address[]";
43
43
  }];
44
44
  readonly stateMutability: "view";
45
+ }, {
46
+ readonly type: "function";
47
+ readonly name: "getDSTokens";
48
+ readonly inputs: readonly [];
49
+ readonly outputs: readonly [{
50
+ readonly name: "";
51
+ readonly type: "address[]";
52
+ readonly internalType: "address[]";
53
+ }];
54
+ readonly stateMutability: "view";
45
55
  }, {
46
56
  readonly type: "function";
47
57
  readonly name: "getInvestor";
@@ -5,7 +5,7 @@ import type { GearboxSDK } from "../GearboxSDK.js";
5
5
  import { type CreditSuite, type OnDemandPriceUpdates, type PriceUpdateV300, type PriceUpdateV310, type UpdatePriceFeedsResult } from "../market/index.js";
6
6
  import { type Asset, type RouterCASlice } from "../router/index.js";
7
7
  import type { MultiCall, RawTx } from "../types/index.js";
8
- import type { AccountToCheck, AddCollateralProps, ChangeDeptProps, ClaimDelayedProps, CloseCreditAccountProps, CloseCreditAccountResult, CloseOptions, CreditAccountOperationResult, EnableTokensProps, ExecuteSwapProps, FullyLiquidateProps, FullyLiquidateResult, GetConnectedBotsResult, GetConnectedMigrationBotsResult, GetCreditAccountsOptions, GetPendingWithdrawalsProps, GetPendingWithdrawalsResult, OpenCAProps, PermitResult, PrepareUpdateQuotasProps, PreviewDelayedWithdrawalProps, PreviewDelayedWithdrawalResult, PriceUpdatesOptions, Rewards, StartDelayedWithdrawalProps, UpdateQuotasProps } from "./types.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";
9
9
  export interface CreditAccountServiceOptions {
10
10
  batchSize?: number;
11
11
  }
@@ -149,31 +149,25 @@ export declare abstract class AbstractCreditAccountService extends SDKConstruct
149
149
  * @returns All necessary data to execute the transaction (call, credit facade)
150
150
  */
151
151
  enableTokens({ enabledTokens, disabledTokens, creditAccount: ca, }: EnableTokensProps): Promise<CreditAccountOperationResult>;
152
+ /**
153
+ * Returns address to which approval should be given on collateral token
154
+ * It's credit manager for classical markets and special wallet for KYC markets
155
+ * @param options - {@link GetApprovalAddressProps}
156
+ * @returns
157
+ **/
158
+ getApprovalAddress(options: GetApprovalAddressProps): Promise<Address>;
152
159
  /**
153
160
  * Executes swap specified by given calls, update quotas of affected tokens
154
- - Open credit account is executed in the following order: price update -> increase debt -> add collateral ->
155
- -> update quotas -> (optionally: execute swap path for trading/strategy) ->
156
- -> (optionally: withdraw debt for lending)
157
- - Basic open credit account: price update -> increase debt -> add collateral -> update quotas
158
- - Lending: price update -> increase debt -> add collateral -> update quotas -> withdraw debt
159
- - Strategy/trading: price update -> increase debt -> add collateral -> update quotas -> execute swap path
160
- - In strategy is possible situation when collateral is added, but not swapped; the only swapped value in this case will be debt
161
- * @param {bigint} ethAmount - native token amount to attach to tx
162
- * @param {Address} creditManager - address of credit manager to open credit account on
163
- * @param {Array<Asset>} collateral - array of collateral which can be just directly added or swapped using the path {@link Asset}
164
- * @param {Record<Address, PermitResult>} permits - permits of collateral tokens (in any permittable token is present) {@link PermitResult}
165
- * @param {bigint} debt - debt to open credit account with
166
- * @param {boolean} withdrawDebt - flag to withdraw debt to wallet after opening credit account;
167
- used for borrowing functionality
168
- * @param {bigint} referralCode - referral code to open credit account with
169
- * @param {Address} to - wallet address to transfer credit account to\
170
- * @param {Array<MultiCall>} calls - array of MultiCall from router methods findOpenStrategyPath {@link MultiCall}.
171
- Used for trading and strategy functionality
172
- * @param {Array<Asset>} averageQuota - average quota for tokens after open {@link Asset}
173
- * @param {Array<Asset>} minQuota - minimum quota for tokens after open {@link Asset}
161
+ * - Open credit account is executed in the following order: price update -> increase debt -> add collateral ->
162
+ * -> update quotas -> (optionally: execute swap path for trading/strategy) ->
163
+ * -> (optionally: withdraw debt for lending)
164
+ *- Basic open credit account: price update -> increase debt -> add collateral -> update quotas
165
+ *- Lending: price update -> increase debt -> add collateral -> update quotas -> withdraw debt
166
+ *- Strategy/trading: price update -> increase debt -> add collateral -> update quotas -> execute swap path
167
+ *- In strategy is possible situation when collateral is added, but not swapped; the only swapped value in this case will be debt
174
168
  * @returns All necessary data to execute the transaction (call, credit facade)
175
- */
176
- openCA({ ethAmount, creditManager, collateral, permits, debt, withdrawToken, referralCode, to, calls: openPathCalls, minQuota, averageQuota, }: OpenCAProps): Promise<CreditAccountOperationResult>;
169
+ **/
170
+ openCA(props: OpenCAProps): Promise<CreditAccountOperationResult>;
177
171
  /**
178
172
  * Returns borrow rate with 4 digits precision (10000 = 100%)
179
173
  * @param ca
@@ -291,6 +291,10 @@ export interface OpenCAProps extends PrepareUpdateQuotasProps {
291
291
  * Address of credit manager to open credit account on
292
292
  */
293
293
  creditManager: Address;
294
+ /**
295
+ * Optional address of credit account to reopen
296
+ */
297
+ reopenCreditAccount?: Address;
294
298
  /**
295
299
  * Wallet address to transfer credit account to
296
300
  */
@@ -435,6 +439,16 @@ export interface LlamathenaProportionalWithdrawProps extends PrepareUpdateQuotas
435
439
  */
436
440
  creditAccount: RouterCASlice;
437
441
  }
442
+ /**
443
+ * Options to get approval address for collateral token
444
+ */
445
+ export type GetApprovalAddressProps = {
446
+ creditManager: Address;
447
+ borrower: Address;
448
+ } | {
449
+ creditManager: Address;
450
+ creditAccount: Address;
451
+ };
438
452
  export interface ICreditAccountsService extends Construct {
439
453
  sdk: GearboxSDK;
440
454
  /**
@@ -553,6 +567,13 @@ export interface ICreditAccountsService extends Construct {
553
567
  * @returns All necessary data to execute the transaction (call, credit facade)
554
568
  */
555
569
  enableTokens(props: EnableTokensProps): Promise<CreditAccountOperationResult>;
570
+ /**
571
+ * Returns address to which approval should be given on collateral token
572
+ * It's credit manager for classical markets and special wallet for KYC markets
573
+ * @param props - {@link GetApprovalAddressProps}
574
+ * @returns
575
+ */
576
+ getApprovalAddress(props: GetApprovalAddressProps): Promise<Address>;
556
577
  /**
557
578
  * Executes swap specified by given calls, update quotas of affected tokens
558
579
  * - Open credit account is executed in the following order: price update -> increase debt -> add collateral ->
@@ -1,31 +1,53 @@
1
1
  import { type Address, type Chain, type PublicClient, type Transport } from "viem";
2
2
  import type { Asset } from "../router/index.js";
3
+ import type { ILogger } from "../types/logger.js";
3
4
  import { AddressMap } from "../utils/index.js";
4
- import type { KYCTokenMeta, PhantomTokenMeta, TokenMetaData } from "./token-types.js";
5
+ import { type DSTokenMeta, type KYCTokenMeta, type PhantomTokenMeta, type TokenMetaData } from "./token-types.js";
5
6
  export interface FormatBNOptions {
6
7
  precision?: number;
7
8
  symbol?: boolean;
8
9
  }
9
10
  export declare class TokensMeta extends AddressMap<TokenMetaData> {
10
11
  #private;
11
- constructor(client: PublicClient<Transport, Chain>);
12
+ constructor(client: PublicClient<Transport, Chain>, logger?: ILogger);
12
13
  reset(): void;
13
14
  symbol(token: Address): string;
14
15
  decimals(token: Address): number;
16
+ /**
17
+ * Returns true if the token is a phantom token, throws if the token data is not loaded
18
+ * @param t
19
+ * @returns
20
+ */
15
21
  isPhantomToken(t: TokenMetaData): t is PhantomTokenMeta;
22
+ /**
23
+ * Returns true if the token is a KYC underlying token, throws if the token data is not loaded
24
+ * @param t
25
+ * @returns
26
+ */
16
27
  isKYCUnderlying(t: TokenMetaData): t is KYCTokenMeta;
28
+ /**
29
+ * Returns true if the token is a DSToken, throws if the token data is not loaded
30
+ * @param t
31
+ * @returns
32
+ */
33
+ isDSToken(t: TokenMetaData): t is DSTokenMeta;
17
34
  /**
18
35
  * Returns a map of all phantom tokens
19
- * Throws if the phantom token data is not loaded
36
+ * Throws if token data is not loaded
20
37
  */
21
38
  get phantomTokens(): AddressMap<PhantomTokenMeta>;
39
+ /**
40
+ * Returns a map of all KYC underlying tokens
41
+ * Throws if token data is not loaded
42
+ */
22
43
  get kycUnderlyings(): AddressMap<KYCTokenMeta>;
44
+ get dsTokens(): AddressMap<DSTokenMeta>;
23
45
  formatBN(asset: Asset, options?: FormatBNOptions): string;
24
46
  formatBN(token: Address, amount: number | bigint | string | undefined, options?: FormatBNOptions): string;
25
47
  findBySymbol(symbol: string): TokenMetaData | undefined;
26
48
  mustFindBySymbol(symbol: string): TokenMetaData;
27
49
  /**
28
- * Loads token information about phantom token and KYC underlying tokens
50
+ * Loads token information about phantom tokens, KYC underlying tokens and DSTokens
29
51
  *
30
52
  * @param tokens - tokens to load data for, defaults to all tokens
31
53
  */
@@ -1,11 +1,14 @@
1
1
  import type { Address } from "viem";
2
2
  import type { MarketData, Unarray } from "./types.js";
3
- export type SimpleTokenMeta = Unarray<MarketData["tokens"]>;
3
+ type TokenData = Unarray<MarketData["tokens"]>;
4
4
  export declare const PHANTOM_TOKEN_CONTRACT_TYPES: readonly ["PHANTOM_TOKEN::CONVEX", "PHANTOM_TOKEN::INFINIFI_UNWIND", "PHANTOM_TOKEN::INFRARED", "PHANTOM_TOKEN::MELLOW_WITHDRAWAL", "PHANTOM_TOKEN::MIDAS_REDEMPTION", "PHANTOM_TOKEN::STAKING_REWARDS", "PHANTOM_TOKEN::UPSHIFT_WITHDRAW"];
5
5
  export declare const KYC_UNDERLYING_DEFAULT = "KYC_UNDERLYING::DEFAULT";
6
6
  export declare const KYC_UNDERLYING_ON_DEMAND = "KYC_UNDERLYING::ON_DEMAND";
7
7
  export type KYCUnderlyingContractType = typeof KYC_UNDERLYING_DEFAULT | typeof KYC_UNDERLYING_ON_DEMAND;
8
8
  export type PhantomTokenContractType = (typeof PHANTOM_TOKEN_CONTRACT_TYPES)[number];
9
+ export type SimpleTokenMeta = TokenData & {
10
+ isDSToken?: boolean;
11
+ };
9
12
  export type PhantomTokenMeta = SimpleTokenMeta & {
10
13
  contractType: PhantomTokenContractType;
11
14
  };
@@ -21,5 +24,9 @@ export type KYCOnDemandTokenMeta = SimpleTokenMeta & {
21
24
  pool: Address;
22
25
  liquidityProvider: Address;
23
26
  };
27
+ export type DSTokenMeta = Omit<SimpleTokenMeta, "isDSToken"> & {
28
+ isDSToken: true;
29
+ };
24
30
  export type KYCTokenMeta = KYCDefaultTokenMeta | KYCOnDemandTokenMeta;
25
- export type TokenMetaData = SimpleTokenMeta | PhantomTokenMeta | KYCTokenMeta;
31
+ export type TokenMetaData = SimpleTokenMeta | PhantomTokenMeta | KYCTokenMeta | DSTokenMeta;
32
+ export {};
@@ -46,6 +46,16 @@ declare const abi: readonly [{
46
46
  readonly internalType: "address[]";
47
47
  }];
48
48
  readonly stateMutability: "view";
49
+ }, {
50
+ readonly type: "function";
51
+ readonly name: "getDSTokens";
52
+ readonly inputs: readonly [];
53
+ readonly outputs: readonly [{
54
+ readonly name: "";
55
+ readonly type: "address[]";
56
+ readonly internalType: "address[]";
57
+ }];
58
+ readonly stateMutability: "view";
49
59
  }, {
50
60
  readonly type: "function";
51
61
  readonly name: "getInvestor";
@@ -426,6 +436,9 @@ declare const abi: readonly [{
426
436
  type abi = typeof abi;
427
437
  export declare class SecuritizeKYCFactory extends BaseContract<abi> {
428
438
  constructor(options: ConstructOptions, address: Address);
439
+ precomputeWalletAddress(creditManager: Address, investor: Address): Promise<Address>;
440
+ getWallet(creditAccount: Address): Promise<Address>;
441
+ getDSTokens(): Promise<Address[]>;
429
442
  multicall(creditAccount: Address, calls: MultiCall[], tokensToRegister: Address[]): RawTx;
430
443
  openCreditAccount(creditManager: Address, calls: MultiCall[], tokensToRegister: Address[]): RawTx;
431
444
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gearbox-protocol/sdk",
3
- "version": "13.0.0-next.16",
3
+ "version": "13.0.0-next.18",
4
4
  "description": "Gearbox SDK",
5
5
  "license": "MIT",
6
6
  "main": "./dist/cjs/sdk/index.js",