@gearbox-protocol/sdk 16.6.0-next.4 → 16.6.0-next.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,6 +29,7 @@ const require_dev_withdrawalUtils = require("./withdrawalUtils.js");
29
29
  exports.AccountOpener = require_dev_AccountOpener.AccountOpener;
30
30
  exports.Create2Deployer = require_dev_create2.Create2Deployer;
31
31
  exports.DEFAULT_CREATE2_SALT = require_dev_create2.DEFAULT_CREATE2_SALT;
32
+ exports.DEFAULT_MIDAS_ADMIN = require_dev_kycUtils.DEFAULT_MIDAS_ADMIN;
32
33
  exports.EthCallSpy = require_dev_EthCallSpy.EthCallSpy;
33
34
  exports.NoAvailableTransportsError = require_dev_RevolverTransport.NoAvailableTransportsError;
34
35
  exports.ONCHAIN_EXECUTION_ID_ADDRESS = require_dev_verifyTestnet.ONCHAIN_EXECUTION_ID_ADDRESS;
@@ -42,12 +43,13 @@ exports.calcLiquidatableLTs = require_dev_calcLiquidatableLTs.calcLiquidatableLT
42
43
  exports.claimDSToken = require_dev_securitizeUtils.claimDSToken;
43
44
  exports.claimDSTokens = require_dev_securitizeUtils.claimDSTokens;
44
45
  exports.claimFromFaucet = require_dev_claimFromFaucet.claimFromFaucet;
45
- exports.collectMidasGateways = require_dev_midasUtils.collectMidasGateways;
46
- exports.collectMidasMTokens = require_dev_midasUtils.collectMidasMTokens;
47
46
  exports.createAnvilClient = require_dev_createAnvilClient.createAnvilClient;
48
47
  exports.createMinter = require_dev_mint_factory.createMinter;
49
48
  exports.deployUsingPublicCreate2 = require_dev_create2.deployUsingPublicCreate2;
50
49
  exports.detectChain = require_dev_detectChain.detectChain;
50
+ exports.discoverMidasCreditSuites = require_dev_midasUtils.discoverMidasCreditSuites;
51
+ exports.discoverMidasGateways = require_dev_midasUtils.discoverMidasGateways;
52
+ exports.discoverMidasMTokens = require_dev_midasUtils.discoverMidasMTokens;
51
53
  exports.enableDSTokenBackDating = require_dev_securitizeUtils.enableDSTokenBackDating;
52
54
  exports.evmMineDetailed = require_dev_createAnvilClient.evmMineDetailed;
53
55
  exports.extendAnvilClient = require_dev_createAnvilClient.extendAnvilClient;
@@ -57,6 +59,7 @@ exports.getDrpcUrl = require_dev_providers.getDrpcUrl;
57
59
  exports.getErpcKey = require_dev_providers.getErpcKey;
58
60
  exports.getPublicCreate2Address = require_dev_create2.getPublicCreate2Address;
59
61
  exports.getRpcProviderUrl = require_dev_providers.getRpcProviderUrl;
62
+ exports.getSecuritizeAdmin = require_dev_kycUtils.getSecuritizeAdmin;
60
63
  exports.getThirdWebUrl = require_dev_providers.getThirdWebUrl;
61
64
  exports.greenlistMidasGateway = require_dev_kycUtils.greenlistMidasGateway;
62
65
  exports.httpTransportOptionsSchema = require_dev_transports.httpTransportOptionsSchema;
@@ -8,19 +8,49 @@ const require_abi_rwa_iDSToken = require("../abi/rwa/iDSToken.js");
8
8
  const require_dev_midasUtils = require("./midasUtils.js");
9
9
  const require_dev_withdrawalAbi = require("./withdrawalAbi.js");
10
10
  let viem = require("viem");
11
- let viem_accounts = require("viem/accounts");
12
11
  //#region src/dev/kycUtils.ts
12
+ const iMockDSTokenAdminAbi = (0, viem.parseAbi)(["function admin() view returns (address)"]);
13
13
  /**
14
- * Resolves the account that performs the DS registry writes: either the admin
15
- * private key, or the admin address impersonated on the fork
14
+ * DS trust-service master of `token`: Ownable owner at storage slot 0 of the
15
+ * trust service, same as periphery-v3 SecuritizeAttachHelper. MockDSToken
16
+ * returns itself as the trust service, so `admin()` is used instead.
17
+ */
18
+ async function getSecuritizeAdmin(anvil, token, logger) {
19
+ const trustServiceId = await anvil.readContract({
20
+ address: token,
21
+ abi: require_abi_rwa_iDSToken.iDSTokenAbi,
22
+ functionName: "TRUST_SERVICE"
23
+ });
24
+ const trustService = await anvil.readContract({
25
+ address: token,
26
+ abi: require_abi_rwa_iDSToken.iDSTokenAbi,
27
+ functionName: "getDSService",
28
+ args: [trustServiceId]
29
+ });
30
+ if ((0, viem.isAddressEqual)(trustService, token)) {
31
+ const admin = await anvil.readContract({
32
+ address: token,
33
+ abi: iMockDSTokenAdminAbi,
34
+ functionName: "admin"
35
+ });
36
+ logger?.debug(`securitize: found admin ${admin} for ${token} (token.admin)`);
37
+ return admin;
38
+ }
39
+ const word = await anvil.getStorageAt({
40
+ address: trustService,
41
+ slot: (0, viem.toHex)(0, { size: 32 })
42
+ });
43
+ if (!word) throw new Error(`securitize: empty trust-service owner slot on ${trustService}`);
44
+ const admin = (0, viem.getAddress)((0, viem.slice)(word, 12));
45
+ logger?.debug(`securitize: found admin ${admin} for ${token} (trust service ${trustService})`);
46
+ return admin;
47
+ }
48
+ /**
49
+ * Impersonates the DS trust-service admin of `token` on the fork
16
50
  */
17
51
  async function useSecuritizeAdmin(props) {
18
- const { anvil, adminPrivateKey, admin, logger } = props;
19
- if (adminPrivateKey) return {
20
- account: (0, viem_accounts.privateKeyToAccount)(adminPrivateKey),
21
- release: async () => {}
22
- };
23
- if (!admin) throw new Error("securitize: either adminPrivateKey or admin address is required");
52
+ const { anvil, token, logger } = props;
53
+ const admin = await getSecuritizeAdmin(anvil, token, logger);
24
54
  await anvil.impersonateAccount({ address: admin });
25
55
  await anvil.setBalance({
26
56
  address: admin,
@@ -260,8 +290,7 @@ async function greenlistMidasGateway(props) {
260
290
  }
261
291
  }
262
292
  /**
263
- * Finds the Midas gateway that issues `token` by scanning the Midas gateway
264
- * adapters of all loaded credit managers and the gateways of Midas degen NFTs
293
+ * Finds the Midas gateway that issues `token` among the loaded credit managers.
265
294
  */
266
295
  async function findMidasGateway(props) {
267
296
  const { anvil, token, marketConfigurators, logger } = props;
@@ -273,21 +302,19 @@ async function findMidasGateway(props) {
273
302
  });
274
303
  await sdk.attach({ marketConfigurators });
275
304
  }
276
- const candidates = await require_dev_midasUtils.collectMidasGateways(sdk);
277
- if (candidates.length === 0) throw new Error("no midas gateway adapters or midas degen NFTs found in loaded markets");
278
- const index = (await anvil.multicall({
279
- allowFailure: false,
280
- contracts: candidates.map((address) => ({
281
- address,
282
- abi: require_dev_withdrawalAbi.midasGatewayAbi,
283
- functionName: "mToken"
284
- }))
285
- })).findIndex((mToken) => (0, viem.isAddressEqual)(mToken, token));
286
- if (index === -1) throw new Error(`no midas gateway found for token ${token}`);
287
- logger?.debug(`midas: gateway for ${token} is ${candidates[index]}`);
288
- return candidates[index];
305
+ const suites = await require_dev_midasUtils.discoverMidasCreditSuites(sdk);
306
+ if (suites.length === 0) throw new Error("no midas gateway adapters or midas degen NFTs found in loaded markets");
307
+ const suite = suites.find(({ mToken }) => (0, viem.isAddressEqual)(mToken, token));
308
+ if (!suite) throw new Error(`no midas gateway found for token ${token}`);
309
+ logger?.debug(`midas: gateway for ${token} is ${suite.gateway}`);
310
+ return suite.gateway;
289
311
  }
290
312
  /**
313
+ * Midas access control admin on mainnet. Holds the admin role of the greenlist
314
+ * operator role.
315
+ */
316
+ const DEFAULT_MIDAS_ADMIN = "0xd4195CF4df289a4748C1A7B6dDBE770e27bA1227";
317
+ /**
291
318
  * Registers a wallet as an investor (fake KYC) in every Securitize DSToken and
292
319
  * every Midas gateway of the markets loaded by `sdk`.
293
320
  *
@@ -295,17 +322,15 @@ async function findMidasGateway(props) {
295
322
  * result instead of aborting the whole run.
296
323
  */
297
324
  async function registerRWAInvestor(props) {
298
- const { anvil, sdk, investor, adminPrivateKey, securitizeAdmin, midasAdmin = "0xd4195CF4df289a4748C1A7B6dDBE770e27bA1227", logger } = props;
325
+ const { anvil, sdk, investor, midasAdmin = DEFAULT_MIDAS_ADMIN, logger } = props;
299
326
  const securitizeTokens = [];
300
327
  const midasGateways = [];
301
328
  const failed = [];
302
329
  const dsTokens = new require_onchain_utils_AddressSet.AddressSet(sdk.rwa.factories.flatMap((factory) => factory.getTokens()));
303
- if (adminPrivateKey || securitizeAdmin) for (const token of dsTokens) try {
330
+ for (const token of dsTokens) try {
304
331
  await registerSecuritizeInvestor({
305
332
  anvil,
306
333
  investor,
307
- adminPrivateKey,
308
- admin: securitizeAdmin,
309
334
  token,
310
335
  logger
311
336
  });
@@ -318,8 +343,7 @@ async function registerRWAInvestor(props) {
318
343
  error: e
319
344
  });
320
345
  }
321
- else if (dsTokens.size > 0) logger?.warn(`securitize: no registry admin, skipping ${dsTokens.size} DSToken(s)`);
322
- const gateways = await require_dev_midasUtils.collectMidasGateways(sdk);
346
+ const gateways = await require_dev_midasUtils.discoverMidasGateways(sdk);
323
347
  for (const gateway of gateways) try {
324
348
  await greenlistMidasGateway({
325
349
  anvil,
@@ -345,6 +369,8 @@ async function registerRWAInvestor(props) {
345
369
  };
346
370
  }
347
371
  //#endregion
372
+ exports.DEFAULT_MIDAS_ADMIN = DEFAULT_MIDAS_ADMIN;
373
+ exports.getSecuritizeAdmin = getSecuritizeAdmin;
348
374
  exports.greenlistMidasGateway = greenlistMidasGateway;
349
375
  exports.registerMidasInvestor = registerMidasInvestor;
350
376
  exports.registerRWAInvestor = registerRWAInvestor;
@@ -147,29 +147,56 @@ async function unpauseMidasIssuanceVault(props) {
147
147
  }
148
148
  };
149
149
  }
150
- function* midasGatewayAdapters(sdk) {
151
- for (const cm of sdk.marketRegister.creditManagers) for (const adapter of cm.creditManager.adapters.values()) if (adapter instanceof require_onchain_market_adapters_contracts_MidasGatewayAdapterContract.MidasGatewayAdapterContract) yield adapter;
150
+ /**
151
+ * Midas credit suites of the loaded credit managers.
152
+ *
153
+ * A manager with a gateway adapter is a suite on its own, so its degen NFT
154
+ * is not consulted. Suites that only have a Midas degen NFT read `mToken`
155
+ * from the gateway.
156
+ */
157
+ async function discoverMidasCreditSuites(sdk) {
158
+ const suites = [];
159
+ for (const cm of sdk.marketRegister.creditManagers) {
160
+ const creditManager = cm.creditManager.address;
161
+ let hasAdapter = false;
162
+ for (const adapter of cm.creditManager.adapters.values()) if (adapter instanceof require_onchain_market_adapters_contracts_MidasGatewayAdapterContract.MidasGatewayAdapterContract) {
163
+ hasAdapter = true;
164
+ suites.push({
165
+ creditManager,
166
+ gateway: adapter.gateway,
167
+ mToken: adapter.mToken
168
+ });
169
+ }
170
+ if (hasAdapter) continue;
171
+ const nft = await cm.degenNFT();
172
+ if (nft instanceof require_onchain_market_rwa_midas_MidasDegenNFT.MidasDegenNFT) {
173
+ const [mToken] = await nft.getTokens();
174
+ suites.push({
175
+ creditManager,
176
+ gateway: nft.gateway,
177
+ mToken
178
+ });
179
+ }
180
+ }
181
+ return suites;
152
182
  }
153
183
  /**
154
- * Target contracts of all Midas gateway adapters of the loaded credit managers,
155
- * plus gateways of Midas degen NFTs (permissioned-mode suites with no gateway adapter).
184
+ * Gateways of the Midas credit suites of the loaded credit managers.
156
185
  */
157
- async function collectMidasGateways(sdk) {
158
- const gateways = new require_onchain_utils_AddressSet.AddressSet();
159
- for (const adapter of midasGatewayAdapters(sdk)) gateways.add(adapter.targetContract);
160
- const nfts = await Promise.all(sdk.marketRegister.creditManagers.map((cm) => cm.degenNFT()));
161
- for (const nft of nfts) if (nft instanceof require_onchain_market_rwa_midas_MidasDegenNFT.MidasDegenNFT) gateways.add(nft.gateway);
162
- return gateways.asArray();
186
+ async function discoverMidasGateways(sdk) {
187
+ const suites = await discoverMidasCreditSuites(sdk);
188
+ return new require_onchain_utils_AddressSet.AddressSet(suites.map((suite) => suite.gateway)).asArray();
163
189
  }
164
190
  /**
165
191
  * mTokens of all Midas gateway adapters of the loaded credit managers
166
192
  */
167
- function collectMidasMTokens(sdk) {
193
+ function discoverMidasMTokens(sdk) {
168
194
  const mTokens = new require_onchain_utils_AddressSet.AddressSet();
169
- for (const adapter of midasGatewayAdapters(sdk)) mTokens.add(adapter.mToken);
195
+ for (const cm of sdk.marketRegister.creditManagers) for (const adapter of cm.creditManager.adapters.values()) if (adapter instanceof require_onchain_market_adapters_contracts_MidasGatewayAdapterContract.MidasGatewayAdapterContract) mTokens.add(adapter.mToken);
170
196
  return mTokens.asArray();
171
197
  }
172
198
  //#endregion
173
- exports.collectMidasGateways = collectMidasGateways;
174
- exports.collectMidasMTokens = collectMidasMTokens;
199
+ exports.discoverMidasCreditSuites = discoverMidasCreditSuites;
200
+ exports.discoverMidasGateways = discoverMidasGateways;
201
+ exports.discoverMidasMTokens = discoverMidasMTokens;
175
202
  exports.unpauseMidasIssuanceVault = unpauseMidasIssuanceVault;
@@ -5,7 +5,6 @@ require("../onchain/index.js");
5
5
  const require_abi_rwa_iDSToken = require("../abi/rwa/iDSToken.js");
6
6
  const require_dev_kycUtils = require("./kycUtils.js");
7
7
  let viem = require("viem");
8
- let viem_accounts = require("viem/accounts");
9
8
  //#region src/dev/securitizeUtils.ts
10
9
  /**
11
10
  * `COMPLIANCE_CONFIGURATION_SERVICE` id in the DS protocol service registry
@@ -111,7 +110,7 @@ async function issueDSTokens(props) {
111
110
  * Tokens without one (e.g. MockDSToken) are skipped
112
111
  */
113
112
  async function getComplianceConfigurationServices({ anvil, tokens, logger }) {
114
- const services = new require_onchain_utils_AddressSet.AddressSet();
113
+ const services = /* @__PURE__ */ new Map();
115
114
  for (const token of new require_onchain_utils_AddressSet.AddressSet(tokens)) try {
116
115
  const service = await anvil.readContract({
117
116
  address: token,
@@ -123,11 +122,16 @@ async function getComplianceConfigurationServices({ anvil, tokens, logger }) {
123
122
  logger?.debug(`${token} has no compliance configuration service`);
124
123
  continue;
125
124
  }
126
- services.add(service);
125
+ const checksummed = (0, viem.getAddress)(service);
126
+ if (services.has(checksummed)) continue;
127
+ services.set(checksummed, await require_dev_kycUtils.getSecuritizeAdmin(anvil, token, logger));
127
128
  } catch (e) {
128
129
  logger?.debug(`Failed to get compliance configuration service of ${token}: ${e}`);
129
130
  }
130
- return [...services];
131
+ return [...services].map(([service, admin]) => ({
132
+ service,
133
+ admin
134
+ }));
131
135
  }
132
136
  /**
133
137
  * Sets `disallowBackDating` to false on compliance configuration services of
@@ -135,18 +139,17 @@ async function getComplianceConfigurationServices({ anvil, tokens, logger }) {
135
139
  * honoured: DS protocol silently replaces it with `block.timestamp` otherwise,
136
140
  * and freshly minted tokens stay under lock-up.
137
141
  *
138
- * Must be signed by a DS admin with sufficient trust (same key as
139
- * `issueTokens` / registerInvestor); Ownable `owner()` alone is not enough.
142
+ * Impersonates the DS trust-service admin of each token. Ownable `owner()` of
143
+ * the compliance configuration service alone is not enough.
140
144
  *
141
145
  * The flag is only read while tokens are issued, so restoring it does not
142
146
  * re-lock tokens minted in the meantime.
143
147
  */
144
148
  async function enableDSTokenBackDating(props) {
145
- const { anvil, adminPrivateKey, logger } = props;
146
- const account = (0, viem_accounts.privateKeyToAccount)(adminPrivateKey);
149
+ const { anvil, logger } = props;
147
150
  const services = await getComplianceConfigurationServices(props);
148
151
  let toRestore = [];
149
- for (const service of services) {
152
+ for (const { service, admin } of services) {
150
153
  let disallowBackDating;
151
154
  try {
152
155
  disallowBackDating = await anvil.readContract({
@@ -163,32 +166,53 @@ async function enableDSTokenBackDating(props) {
163
166
  continue;
164
167
  }
165
168
  logger?.info(`Allowing back-dating on ${service}`);
166
- await anvil.writeContractSync({
167
- account,
168
- chain: anvil.chain,
169
- address: service,
170
- abi: iDSComplianceConfigurationServiceAbi,
171
- functionName: "setDisallowBackDating",
172
- args: [false],
173
- throwOnReceiptRevert: true
169
+ await anvil.impersonateAccount({ address: admin });
170
+ try {
171
+ await anvil.setBalance({
172
+ address: admin,
173
+ value: (0, viem.parseEther)("100")
174
+ });
175
+ await anvil.writeContractSync({
176
+ account: admin,
177
+ chain: anvil.chain,
178
+ address: service,
179
+ abi: iDSComplianceConfigurationServiceAbi,
180
+ functionName: "setDisallowBackDating",
181
+ args: [false],
182
+ throwOnReceiptRevert: true
183
+ });
184
+ } finally {
185
+ await anvil.stopImpersonatingAccount({ address: admin });
186
+ }
187
+ toRestore.push({
188
+ service,
189
+ admin
174
190
  });
175
- toRestore.push(service);
176
191
  }
177
192
  return async () => {
178
193
  const services = toRestore;
179
194
  toRestore = [];
180
- for (const service of services) {
195
+ for (const { service, admin } of services) {
181
196
  logger?.info(`Disallowing back-dating on ${service}`);
182
197
  try {
183
- await anvil.writeContractSync({
184
- account,
185
- chain: anvil.chain,
186
- address: service,
187
- abi: iDSComplianceConfigurationServiceAbi,
188
- functionName: "setDisallowBackDating",
189
- args: [true],
190
- throwOnReceiptRevert: true
191
- });
198
+ await anvil.impersonateAccount({ address: admin });
199
+ try {
200
+ await anvil.setBalance({
201
+ address: admin,
202
+ value: (0, viem.parseEther)("100")
203
+ });
204
+ await anvil.writeContractSync({
205
+ account: admin,
206
+ chain: anvil.chain,
207
+ address: service,
208
+ abi: iDSComplianceConfigurationServiceAbi,
209
+ functionName: "setDisallowBackDating",
210
+ args: [true],
211
+ throwOnReceiptRevert: true
212
+ });
213
+ } finally {
214
+ await anvil.stopImpersonatingAccount({ address: admin });
215
+ }
192
216
  } catch (e) {
193
217
  logger?.warn(`Failed to restore disallowBackDating on ${service}: ${e}`);
194
218
  }
@@ -196,8 +220,7 @@ async function enableDSTokenBackDating(props) {
196
220
  };
197
221
  }
198
222
  async function claimDSToken(props) {
199
- const { anvil, investor, adminPrivateKey, token, marketConfigurators, rwaFactories, usdAmount: usdAmountProp = "100000" } = props;
200
- const account = (0, viem_accounts.privateKeyToAccount)(adminPrivateKey);
223
+ const { anvil, investor, token, marketConfigurators, rwaFactories, usdAmount: usdAmountProp = "100000" } = props;
201
224
  const symbol = await anvil.readContract({
202
225
  address: token,
203
226
  abi: viem.erc20Abi,
@@ -221,18 +244,31 @@ async function claimDSToken(props) {
221
244
  if (amount === 0n) throw new Error(`No market found for token ${token}`);
222
245
  logger?.debug(`${usdAmountProp} USD === ${amount} ${symbol}`);
223
246
  await require_dev_kycUtils.registerSecuritizeInvestor({
224
- ...props,
225
- logger
226
- });
227
- logger?.debug(`Issuing ${amount} tokens to ${investor}...`);
228
- const mintHash = await issueDSTokens({
229
247
  anvil,
230
- account,
231
- token,
232
248
  investor,
233
- amount,
249
+ token,
234
250
  logger
235
251
  });
252
+ const admin = await require_dev_kycUtils.getSecuritizeAdmin(anvil, token, logger);
253
+ logger?.debug(`Issuing ${amount} tokens to ${investor}...`);
254
+ await anvil.impersonateAccount({ address: admin });
255
+ let mintHash;
256
+ try {
257
+ await anvil.setBalance({
258
+ address: admin,
259
+ value: (0, viem.parseEther)("100")
260
+ });
261
+ mintHash = await issueDSTokens({
262
+ anvil,
263
+ account: admin,
264
+ token,
265
+ investor,
266
+ amount,
267
+ logger
268
+ });
269
+ } finally {
270
+ await anvil.stopImpersonatingAccount({ address: admin });
271
+ }
236
272
  logger?.debug(`Done! tx: ${mintHash}`);
237
273
  const balance = await anvil.readContract({
238
274
  address: token,
@@ -13,8 +13,8 @@ import { detectChain } from "./detectChain.js";
13
13
  import { isOutOfSyncError } from "./isOutOfSyncError.js";
14
14
  import { isRateLimitError } from "./isRateLimitError.js";
15
15
  import { isTransientError } from "./isTransientError.js";
16
- import { collectMidasGateways, collectMidasMTokens, unpauseMidasIssuanceVault } from "./midasUtils.js";
17
- import { greenlistMidasGateway, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor } from "./kycUtils.js";
16
+ import { discoverMidasCreditSuites, discoverMidasGateways, discoverMidasMTokens, unpauseMidasIssuanceVault } from "./midasUtils.js";
17
+ import { DEFAULT_MIDAS_ADMIN, getSecuritizeAdmin, greenlistMidasGateway, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor } from "./kycUtils.js";
18
18
  import { isRangeError, logSplitterTransport } from "./logSplitterTransport.js";
19
19
  import { setLTZero, setLTs } from "./ltUtils.js";
20
20
  import { migrateFaucet } from "./migrateFaucet.js";
@@ -25,4 +25,4 @@ import { claimDSToken, claimDSTokens, enableDSTokenBackDating } from "./securiti
25
25
  import "./types.js";
26
26
  import { ONCHAIN_EXECUTION_ID_ADDRESS, verifyTestnet } from "./verifyTestnet.js";
27
27
  import { makePendingWithdrawalsClaimable } from "./withdrawalUtils.js";
28
- export { AccountOpener, Create2Deployer, DEFAULT_CREATE2_SALT, EthCallSpy, NoAvailableTransportsError, ONCHAIN_EXECUTION_ID_ADDRESS, OpenTxRevertedError, PUBLIC_CREATE2_FACTORY, RevolverTransport, SUPPORTED_RPC_PROVIDERS, SelectionStrategy, anvilNodeInfo, calcLiquidatableLTs, claimDSToken, claimDSTokens, claimFromFaucet, collectMidasGateways, collectMidasMTokens, createAnvilClient, createMinter, deployUsingPublicCreate2, detectChain, enableDSTokenBackDating, evmMineDetailed, extendAnvilClient, getAlchemyUrl, getAnkrUrl, getDrpcUrl, getErpcKey, getPublicCreate2Address, getRpcProviderUrl, getThirdWebUrl, greenlistMidasGateway, httpTransportOptionsSchema, iDegenNftv2Abi, iFaucetAbi, iOnchainExecutionIdAbi, iOwnableAbi, iaclTraitAbi, isAnvil, isDeployedUsingPublicCreate2, isOutOfSyncError, isRangeError, isRateLimitError, isTransientError, logSplitterTransport, makePendingWithdrawalsClaimable, migrateFaucet, providerConfigSchema, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor, replaceStorage, resilientTransport, resilientTransportOptionsSchema, revolverTransportConfigBaseSchema, revolverTransportConfigSchema, rpcProvidersSchema, setLTZero, setLTs, unpauseMidasIssuanceVault, verifyTestnet, writeContractSync };
28
+ export { AccountOpener, Create2Deployer, DEFAULT_CREATE2_SALT, DEFAULT_MIDAS_ADMIN, EthCallSpy, NoAvailableTransportsError, ONCHAIN_EXECUTION_ID_ADDRESS, OpenTxRevertedError, PUBLIC_CREATE2_FACTORY, RevolverTransport, SUPPORTED_RPC_PROVIDERS, SelectionStrategy, anvilNodeInfo, calcLiquidatableLTs, claimDSToken, claimDSTokens, claimFromFaucet, createAnvilClient, createMinter, deployUsingPublicCreate2, detectChain, discoverMidasCreditSuites, discoverMidasGateways, discoverMidasMTokens, enableDSTokenBackDating, evmMineDetailed, extendAnvilClient, getAlchemyUrl, getAnkrUrl, getDrpcUrl, getErpcKey, getPublicCreate2Address, getRpcProviderUrl, getSecuritizeAdmin, getThirdWebUrl, greenlistMidasGateway, httpTransportOptionsSchema, iDegenNftv2Abi, iFaucetAbi, iOnchainExecutionIdAbi, iOwnableAbi, iaclTraitAbi, isAnvil, isDeployedUsingPublicCreate2, isOutOfSyncError, isRangeError, isRateLimitError, isTransientError, logSplitterTransport, makePendingWithdrawalsClaimable, migrateFaucet, providerConfigSchema, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor, replaceStorage, resilientTransport, resilientTransportOptionsSchema, revolverTransportConfigBaseSchema, revolverTransportConfigSchema, rpcProvidersSchema, setLTZero, setLTs, unpauseMidasIssuanceVault, verifyTestnet, writeContractSync };
@@ -4,22 +4,52 @@ import { OnchainSDK } from "../onchain/OnchainSDK.js";
4
4
  import "../onchain/index.js";
5
5
  import { iDSRegistryServiceAbi } from "../abi/rwa/iDSRegistryService.js";
6
6
  import { iDSTokenAbi } from "../abi/rwa/iDSToken.js";
7
- import { collectMidasGateways } from "./midasUtils.js";
7
+ import { discoverMidasCreditSuites, discoverMidasGateways } from "./midasUtils.js";
8
8
  import { midasGatewayAbi } from "./withdrawalAbi.js";
9
- import { isAddressEqual, parseAbi, parseEther, zeroAddress } from "viem";
10
- import { privateKeyToAccount } from "viem/accounts";
9
+ import { getAddress, isAddressEqual, parseAbi, parseEther, slice, toHex, zeroAddress } from "viem";
11
10
  //#region src/dev/kycUtils.ts
11
+ const iMockDSTokenAdminAbi = parseAbi(["function admin() view returns (address)"]);
12
12
  /**
13
- * Resolves the account that performs the DS registry writes: either the admin
14
- * private key, or the admin address impersonated on the fork
13
+ * DS trust-service master of `token`: Ownable owner at storage slot 0 of the
14
+ * trust service, same as periphery-v3 SecuritizeAttachHelper. MockDSToken
15
+ * returns itself as the trust service, so `admin()` is used instead.
16
+ */
17
+ async function getSecuritizeAdmin(anvil, token, logger) {
18
+ const trustServiceId = await anvil.readContract({
19
+ address: token,
20
+ abi: iDSTokenAbi,
21
+ functionName: "TRUST_SERVICE"
22
+ });
23
+ const trustService = await anvil.readContract({
24
+ address: token,
25
+ abi: iDSTokenAbi,
26
+ functionName: "getDSService",
27
+ args: [trustServiceId]
28
+ });
29
+ if (isAddressEqual(trustService, token)) {
30
+ const admin = await anvil.readContract({
31
+ address: token,
32
+ abi: iMockDSTokenAdminAbi,
33
+ functionName: "admin"
34
+ });
35
+ logger?.debug(`securitize: found admin ${admin} for ${token} (token.admin)`);
36
+ return admin;
37
+ }
38
+ const word = await anvil.getStorageAt({
39
+ address: trustService,
40
+ slot: toHex(0, { size: 32 })
41
+ });
42
+ if (!word) throw new Error(`securitize: empty trust-service owner slot on ${trustService}`);
43
+ const admin = getAddress(slice(word, 12));
44
+ logger?.debug(`securitize: found admin ${admin} for ${token} (trust service ${trustService})`);
45
+ return admin;
46
+ }
47
+ /**
48
+ * Impersonates the DS trust-service admin of `token` on the fork
15
49
  */
16
50
  async function useSecuritizeAdmin(props) {
17
- const { anvil, adminPrivateKey, admin, logger } = props;
18
- if (adminPrivateKey) return {
19
- account: privateKeyToAccount(adminPrivateKey),
20
- release: async () => {}
21
- };
22
- if (!admin) throw new Error("securitize: either adminPrivateKey or admin address is required");
51
+ const { anvil, token, logger } = props;
52
+ const admin = await getSecuritizeAdmin(anvil, token, logger);
23
53
  await anvil.impersonateAccount({ address: admin });
24
54
  await anvil.setBalance({
25
55
  address: admin,
@@ -259,8 +289,7 @@ async function greenlistMidasGateway(props) {
259
289
  }
260
290
  }
261
291
  /**
262
- * Finds the Midas gateway that issues `token` by scanning the Midas gateway
263
- * adapters of all loaded credit managers and the gateways of Midas degen NFTs
292
+ * Finds the Midas gateway that issues `token` among the loaded credit managers.
264
293
  */
265
294
  async function findMidasGateway(props) {
266
295
  const { anvil, token, marketConfigurators, logger } = props;
@@ -272,21 +301,19 @@ async function findMidasGateway(props) {
272
301
  });
273
302
  await sdk.attach({ marketConfigurators });
274
303
  }
275
- const candidates = await collectMidasGateways(sdk);
276
- if (candidates.length === 0) throw new Error("no midas gateway adapters or midas degen NFTs found in loaded markets");
277
- const index = (await anvil.multicall({
278
- allowFailure: false,
279
- contracts: candidates.map((address) => ({
280
- address,
281
- abi: midasGatewayAbi,
282
- functionName: "mToken"
283
- }))
284
- })).findIndex((mToken) => isAddressEqual(mToken, token));
285
- if (index === -1) throw new Error(`no midas gateway found for token ${token}`);
286
- logger?.debug(`midas: gateway for ${token} is ${candidates[index]}`);
287
- return candidates[index];
304
+ const suites = await discoverMidasCreditSuites(sdk);
305
+ if (suites.length === 0) throw new Error("no midas gateway adapters or midas degen NFTs found in loaded markets");
306
+ const suite = suites.find(({ mToken }) => isAddressEqual(mToken, token));
307
+ if (!suite) throw new Error(`no midas gateway found for token ${token}`);
308
+ logger?.debug(`midas: gateway for ${token} is ${suite.gateway}`);
309
+ return suite.gateway;
288
310
  }
289
311
  /**
312
+ * Midas access control admin on mainnet. Holds the admin role of the greenlist
313
+ * operator role.
314
+ */
315
+ const DEFAULT_MIDAS_ADMIN = "0xd4195CF4df289a4748C1A7B6dDBE770e27bA1227";
316
+ /**
290
317
  * Registers a wallet as an investor (fake KYC) in every Securitize DSToken and
291
318
  * every Midas gateway of the markets loaded by `sdk`.
292
319
  *
@@ -294,17 +321,15 @@ async function findMidasGateway(props) {
294
321
  * result instead of aborting the whole run.
295
322
  */
296
323
  async function registerRWAInvestor(props) {
297
- const { anvil, sdk, investor, adminPrivateKey, securitizeAdmin, midasAdmin = "0xd4195CF4df289a4748C1A7B6dDBE770e27bA1227", logger } = props;
324
+ const { anvil, sdk, investor, midasAdmin = DEFAULT_MIDAS_ADMIN, logger } = props;
298
325
  const securitizeTokens = [];
299
326
  const midasGateways = [];
300
327
  const failed = [];
301
328
  const dsTokens = new AddressSet(sdk.rwa.factories.flatMap((factory) => factory.getTokens()));
302
- if (adminPrivateKey || securitizeAdmin) for (const token of dsTokens) try {
329
+ for (const token of dsTokens) try {
303
330
  await registerSecuritizeInvestor({
304
331
  anvil,
305
332
  investor,
306
- adminPrivateKey,
307
- admin: securitizeAdmin,
308
333
  token,
309
334
  logger
310
335
  });
@@ -317,8 +342,7 @@ async function registerRWAInvestor(props) {
317
342
  error: e
318
343
  });
319
344
  }
320
- else if (dsTokens.size > 0) logger?.warn(`securitize: no registry admin, skipping ${dsTokens.size} DSToken(s)`);
321
- const gateways = await collectMidasGateways(sdk);
345
+ const gateways = await discoverMidasGateways(sdk);
322
346
  for (const gateway of gateways) try {
323
347
  await greenlistMidasGateway({
324
348
  anvil,
@@ -344,4 +368,4 @@ async function registerRWAInvestor(props) {
344
368
  };
345
369
  }
346
370
  //#endregion
347
- export { greenlistMidasGateway, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor };
371
+ export { DEFAULT_MIDAS_ADMIN, getSecuritizeAdmin, greenlistMidasGateway, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor };
@@ -146,27 +146,53 @@ async function unpauseMidasIssuanceVault(props) {
146
146
  }
147
147
  };
148
148
  }
149
- function* midasGatewayAdapters(sdk) {
150
- for (const cm of sdk.marketRegister.creditManagers) for (const adapter of cm.creditManager.adapters.values()) if (adapter instanceof MidasGatewayAdapterContract) yield adapter;
149
+ /**
150
+ * Midas credit suites of the loaded credit managers.
151
+ *
152
+ * A manager with a gateway adapter is a suite on its own, so its degen NFT
153
+ * is not consulted. Suites that only have a Midas degen NFT read `mToken`
154
+ * from the gateway.
155
+ */
156
+ async function discoverMidasCreditSuites(sdk) {
157
+ const suites = [];
158
+ for (const cm of sdk.marketRegister.creditManagers) {
159
+ const creditManager = cm.creditManager.address;
160
+ let hasAdapter = false;
161
+ for (const adapter of cm.creditManager.adapters.values()) if (adapter instanceof MidasGatewayAdapterContract) {
162
+ hasAdapter = true;
163
+ suites.push({
164
+ creditManager,
165
+ gateway: adapter.gateway,
166
+ mToken: adapter.mToken
167
+ });
168
+ }
169
+ if (hasAdapter) continue;
170
+ const nft = await cm.degenNFT();
171
+ if (nft instanceof MidasDegenNFT) {
172
+ const [mToken] = await nft.getTokens();
173
+ suites.push({
174
+ creditManager,
175
+ gateway: nft.gateway,
176
+ mToken
177
+ });
178
+ }
179
+ }
180
+ return suites;
151
181
  }
152
182
  /**
153
- * Target contracts of all Midas gateway adapters of the loaded credit managers,
154
- * plus gateways of Midas degen NFTs (permissioned-mode suites with no gateway adapter).
183
+ * Gateways of the Midas credit suites of the loaded credit managers.
155
184
  */
156
- async function collectMidasGateways(sdk) {
157
- const gateways = new AddressSet();
158
- for (const adapter of midasGatewayAdapters(sdk)) gateways.add(adapter.targetContract);
159
- const nfts = await Promise.all(sdk.marketRegister.creditManagers.map((cm) => cm.degenNFT()));
160
- for (const nft of nfts) if (nft instanceof MidasDegenNFT) gateways.add(nft.gateway);
161
- return gateways.asArray();
185
+ async function discoverMidasGateways(sdk) {
186
+ const suites = await discoverMidasCreditSuites(sdk);
187
+ return new AddressSet(suites.map((suite) => suite.gateway)).asArray();
162
188
  }
163
189
  /**
164
190
  * mTokens of all Midas gateway adapters of the loaded credit managers
165
191
  */
166
- function collectMidasMTokens(sdk) {
192
+ function discoverMidasMTokens(sdk) {
167
193
  const mTokens = new AddressSet();
168
- for (const adapter of midasGatewayAdapters(sdk)) mTokens.add(adapter.mToken);
194
+ for (const cm of sdk.marketRegister.creditManagers) for (const adapter of cm.creditManager.adapters.values()) if (adapter instanceof MidasGatewayAdapterContract) mTokens.add(adapter.mToken);
169
195
  return mTokens.asArray();
170
196
  }
171
197
  //#endregion
172
- export { collectMidasGateways, collectMidasMTokens, unpauseMidasIssuanceVault };
198
+ export { discoverMidasCreditSuites, discoverMidasGateways, discoverMidasMTokens, unpauseMidasIssuanceVault };
@@ -2,9 +2,8 @@ import { AddressSet } from "../onchain/utils/AddressSet.js";
2
2
  import { OnchainSDK } from "../onchain/OnchainSDK.js";
3
3
  import "../onchain/index.js";
4
4
  import { iDSTokenAbi } from "../abi/rwa/iDSToken.js";
5
- import { registerSecuritizeInvestor } from "./kycUtils.js";
6
- import { erc20Abi, isAddressEqual, parseAbi, zeroAddress } from "viem";
7
- import { privateKeyToAccount } from "viem/accounts";
5
+ import { getSecuritizeAdmin, registerSecuritizeInvestor } from "./kycUtils.js";
6
+ import { erc20Abi, getAddress, isAddressEqual, parseAbi, parseEther, zeroAddress } from "viem";
8
7
  //#region src/dev/securitizeUtils.ts
9
8
  /**
10
9
  * `COMPLIANCE_CONFIGURATION_SERVICE` id in the DS protocol service registry
@@ -110,7 +109,7 @@ async function issueDSTokens(props) {
110
109
  * Tokens without one (e.g. MockDSToken) are skipped
111
110
  */
112
111
  async function getComplianceConfigurationServices({ anvil, tokens, logger }) {
113
- const services = new AddressSet();
112
+ const services = /* @__PURE__ */ new Map();
114
113
  for (const token of new AddressSet(tokens)) try {
115
114
  const service = await anvil.readContract({
116
115
  address: token,
@@ -122,11 +121,16 @@ async function getComplianceConfigurationServices({ anvil, tokens, logger }) {
122
121
  logger?.debug(`${token} has no compliance configuration service`);
123
122
  continue;
124
123
  }
125
- services.add(service);
124
+ const checksummed = getAddress(service);
125
+ if (services.has(checksummed)) continue;
126
+ services.set(checksummed, await getSecuritizeAdmin(anvil, token, logger));
126
127
  } catch (e) {
127
128
  logger?.debug(`Failed to get compliance configuration service of ${token}: ${e}`);
128
129
  }
129
- return [...services];
130
+ return [...services].map(([service, admin]) => ({
131
+ service,
132
+ admin
133
+ }));
130
134
  }
131
135
  /**
132
136
  * Sets `disallowBackDating` to false on compliance configuration services of
@@ -134,18 +138,17 @@ async function getComplianceConfigurationServices({ anvil, tokens, logger }) {
134
138
  * honoured: DS protocol silently replaces it with `block.timestamp` otherwise,
135
139
  * and freshly minted tokens stay under lock-up.
136
140
  *
137
- * Must be signed by a DS admin with sufficient trust (same key as
138
- * `issueTokens` / registerInvestor); Ownable `owner()` alone is not enough.
141
+ * Impersonates the DS trust-service admin of each token. Ownable `owner()` of
142
+ * the compliance configuration service alone is not enough.
139
143
  *
140
144
  * The flag is only read while tokens are issued, so restoring it does not
141
145
  * re-lock tokens minted in the meantime.
142
146
  */
143
147
  async function enableDSTokenBackDating(props) {
144
- const { anvil, adminPrivateKey, logger } = props;
145
- const account = privateKeyToAccount(adminPrivateKey);
148
+ const { anvil, logger } = props;
146
149
  const services = await getComplianceConfigurationServices(props);
147
150
  let toRestore = [];
148
- for (const service of services) {
151
+ for (const { service, admin } of services) {
149
152
  let disallowBackDating;
150
153
  try {
151
154
  disallowBackDating = await anvil.readContract({
@@ -162,32 +165,53 @@ async function enableDSTokenBackDating(props) {
162
165
  continue;
163
166
  }
164
167
  logger?.info(`Allowing back-dating on ${service}`);
165
- await anvil.writeContractSync({
166
- account,
167
- chain: anvil.chain,
168
- address: service,
169
- abi: iDSComplianceConfigurationServiceAbi,
170
- functionName: "setDisallowBackDating",
171
- args: [false],
172
- throwOnReceiptRevert: true
168
+ await anvil.impersonateAccount({ address: admin });
169
+ try {
170
+ await anvil.setBalance({
171
+ address: admin,
172
+ value: parseEther("100")
173
+ });
174
+ await anvil.writeContractSync({
175
+ account: admin,
176
+ chain: anvil.chain,
177
+ address: service,
178
+ abi: iDSComplianceConfigurationServiceAbi,
179
+ functionName: "setDisallowBackDating",
180
+ args: [false],
181
+ throwOnReceiptRevert: true
182
+ });
183
+ } finally {
184
+ await anvil.stopImpersonatingAccount({ address: admin });
185
+ }
186
+ toRestore.push({
187
+ service,
188
+ admin
173
189
  });
174
- toRestore.push(service);
175
190
  }
176
191
  return async () => {
177
192
  const services = toRestore;
178
193
  toRestore = [];
179
- for (const service of services) {
194
+ for (const { service, admin } of services) {
180
195
  logger?.info(`Disallowing back-dating on ${service}`);
181
196
  try {
182
- await anvil.writeContractSync({
183
- account,
184
- chain: anvil.chain,
185
- address: service,
186
- abi: iDSComplianceConfigurationServiceAbi,
187
- functionName: "setDisallowBackDating",
188
- args: [true],
189
- throwOnReceiptRevert: true
190
- });
197
+ await anvil.impersonateAccount({ address: admin });
198
+ try {
199
+ await anvil.setBalance({
200
+ address: admin,
201
+ value: parseEther("100")
202
+ });
203
+ await anvil.writeContractSync({
204
+ account: admin,
205
+ chain: anvil.chain,
206
+ address: service,
207
+ abi: iDSComplianceConfigurationServiceAbi,
208
+ functionName: "setDisallowBackDating",
209
+ args: [true],
210
+ throwOnReceiptRevert: true
211
+ });
212
+ } finally {
213
+ await anvil.stopImpersonatingAccount({ address: admin });
214
+ }
191
215
  } catch (e) {
192
216
  logger?.warn(`Failed to restore disallowBackDating on ${service}: ${e}`);
193
217
  }
@@ -195,8 +219,7 @@ async function enableDSTokenBackDating(props) {
195
219
  };
196
220
  }
197
221
  async function claimDSToken(props) {
198
- const { anvil, investor, adminPrivateKey, token, marketConfigurators, rwaFactories, usdAmount: usdAmountProp = "100000" } = props;
199
- const account = privateKeyToAccount(adminPrivateKey);
222
+ const { anvil, investor, token, marketConfigurators, rwaFactories, usdAmount: usdAmountProp = "100000" } = props;
200
223
  const symbol = await anvil.readContract({
201
224
  address: token,
202
225
  abi: erc20Abi,
@@ -220,18 +243,31 @@ async function claimDSToken(props) {
220
243
  if (amount === 0n) throw new Error(`No market found for token ${token}`);
221
244
  logger?.debug(`${usdAmountProp} USD === ${amount} ${symbol}`);
222
245
  await registerSecuritizeInvestor({
223
- ...props,
224
- logger
225
- });
226
- logger?.debug(`Issuing ${amount} tokens to ${investor}...`);
227
- const mintHash = await issueDSTokens({
228
246
  anvil,
229
- account,
230
- token,
231
247
  investor,
232
- amount,
248
+ token,
233
249
  logger
234
250
  });
251
+ const admin = await getSecuritizeAdmin(anvil, token, logger);
252
+ logger?.debug(`Issuing ${amount} tokens to ${investor}...`);
253
+ await anvil.impersonateAccount({ address: admin });
254
+ let mintHash;
255
+ try {
256
+ await anvil.setBalance({
257
+ address: admin,
258
+ value: parseEther("100")
259
+ });
260
+ mintHash = await issueDSTokens({
261
+ anvil,
262
+ account: admin,
263
+ token,
264
+ investor,
265
+ amount,
266
+ logger
267
+ });
268
+ } finally {
269
+ await anvil.stopImpersonatingAccount({ address: admin });
270
+ }
235
271
  logger?.debug(`Done! tx: ${mintHash}`);
236
272
  const balance = await anvil.readContract({
237
273
  address: token,
@@ -55,7 +55,7 @@ var PrepareApi = class extends MultichainConstruct {
55
55
  const at = stateBlock(sdk);
56
56
  const intent = resumable(params.intent ?? params.claimable.intent);
57
57
  if (!intent) return sdkErr(noRecordedIntent());
58
- const creditAccount = await slice(sdk, position.creditAccount);
58
+ const creditAccount = await slice$1(sdk, position.creditAccount);
59
59
  if (!creditAccount) return sdkErr(creditAccountNotFound(position.creditAccount));
60
60
  return finalized(await service(sdk).finishIntent({
61
61
  intent,
@@ -404,7 +404,7 @@ var PrepareApi = class extends MultichainConstruct {
404
404
  * described, see {@link IOpportunitiesPrepare.maxWithdraw}.
405
405
  **/
406
406
  async #account(sdk, position) {
407
- const creditAccount = await slice(sdk, position.creditAccount);
407
+ const creditAccount = await slice$1(sdk, position.creditAccount);
408
408
  if (!creditAccount) throw new Error(creditAccountNotFound(position.creditAccount).message);
409
409
  return creditAccount;
410
410
  }
@@ -421,7 +421,7 @@ var PrepareApi = class extends MultichainConstruct {
421
421
  try {
422
422
  const sdk = await this.#chain(position.chainId);
423
423
  const at = stateBlock(sdk);
424
- const creditAccount = await slice(sdk, position.creditAccount);
424
+ const creditAccount = await slice$1(sdk, position.creditAccount);
425
425
  if (!creditAccount) return neitherRoute(creditAccountNotFound(position.creditAccount));
426
426
  return routed(await service(sdk).intentRoutes({
427
427
  intent,
@@ -447,7 +447,7 @@ var PrepareApi = class extends MultichainConstruct {
447
447
  try {
448
448
  const sdk = await this.#chain(position.chainId);
449
449
  const at = stateBlock(sdk);
450
- const creditAccount = await slice(sdk, position.creditAccount);
450
+ const creditAccount = await slice$1(sdk, position.creditAccount);
451
451
  if (!creditAccount) return sdkErr(creditAccountNotFound(position.creditAccount));
452
452
  return planned(await service(sdk).startIntent({
453
453
  intent,
@@ -479,7 +479,7 @@ function stateBlock(sdk) {
479
479
  * connected to hold no such account — closed since it was listed, or named on
480
480
  * the wrong chain. Read rather than thrown, so the caller gets a code for it.
481
481
  **/
482
- async function slice(sdk, creditAccount) {
482
+ async function slice$1(sdk, creditAccount) {
483
483
  const data = await sdk.accounts.getCreditAccountData(creditAccount);
484
484
  return data && toCreditAccountSlice(data);
485
485
  }
@@ -497,7 +497,7 @@ async function slice(sdk, creditAccount) {
497
497
  **/
498
498
  async function reusable(sdk, strategy, creditAccount) {
499
499
  if (!creditAccount) return;
500
- const account = await slice(sdk, creditAccount);
500
+ const account = await slice$1(sdk, creditAccount);
501
501
  if (!account || !isAddressEqual(account.creditManager, strategy.creditManager)) return sdkErr(creditAccountNotFound(creditAccount));
502
502
  if (account.totalDebt > 0n || account.tokens.some((t) => t.quota > 0n)) return sdkErr(creditAccountNotEmpty(creditAccount));
503
503
  return { account };
@@ -11,10 +11,10 @@ import { detectChain } from "./detectChain.js";
11
11
  import { isOutOfSyncError } from "./isOutOfSyncError.js";
12
12
  import { isRateLimitError } from "./isRateLimitError.js";
13
13
  import { isTransientError } from "./isTransientError.js";
14
- import { GreenlistMidasGatewayProps, RWAKycFailure, RegisterMidasInvestorProps, RegisterRWAInvestorProps, RegisterRWAInvestorResult, RegisterSecuritizeInvestorProps, greenlistMidasGateway, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor } from "./kycUtils.js";
14
+ import { DEFAULT_MIDAS_ADMIN, GreenlistMidasGatewayProps, RWAKycFailure, RegisterMidasInvestorProps, RegisterRWAInvestorProps, RegisterRWAInvestorResult, RegisterSecuritizeInvestorProps, getSecuritizeAdmin, greenlistMidasGateway, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor } from "./kycUtils.js";
15
15
  import { LogSplitterTransportOptions, isRangeError, logSplitterTransport } from "./logSplitterTransport.js";
16
16
  import { setLTZero, setLTs } from "./ltUtils.js";
17
- import { RestoreMidasIssuanceVaultPause, UnpauseMidasIssuanceVaultProps, collectMidasGateways, collectMidasMTokens, unpauseMidasIssuanceVault } from "./midasUtils.js";
17
+ import { MidasCreditSuite, RestoreMidasIssuanceVaultPause, UnpauseMidasIssuanceVaultProps, discoverMidasCreditSuites, discoverMidasGateways, discoverMidasMTokens, unpauseMidasIssuanceVault } from "./midasUtils.js";
18
18
  import { migrateFaucet } from "./migrateFaucet.js";
19
19
  import { IMinter } from "./mint/types.js";
20
20
  import { createMinter } from "./mint/factory.js";
@@ -26,4 +26,4 @@ import { EnableDSTokenBackDatingProps, RestoreDSTokenBackDating, claimDSToken, c
26
26
  import { httpTransportOptionsSchema } from "./transports.js";
27
27
  import { ONCHAIN_EXECUTION_ID_ADDRESS, VerifyTestnetParams, verifyTestnet } from "./verifyTestnet.js";
28
28
  import { MakePendingWithdrawalsClaimableOptions, makePendingWithdrawalsClaimable } from "./withdrawalUtils.js";
29
- export { AccountOpener, AccountOpenerOptions, AnvilActions, AnvilClient, AnvilClientConfig, AnvilDealParameters, AnvilNodeInfo, AnvilRPCSchema, CheckMulticallFn, Create2Deployer, Create2Parameters, DEFAULT_CREATE2_SALT, DetectedCall, EnableDSTokenBackDatingProps, EnsureExistsUsingPublicCreate2ReturnType, EthCallMethod, EthCallRequest, EthCallSpy, GetCreate2AddressParameters, GreenlistMidasGatewayProps, IMinter, type LogSplitterTransportOptions, MakePendingWithdrawalsClaimableOptions, NoAvailableTransportsError, ONCHAIN_EXECUTION_ID_ADDRESS, OpenAccountHumanizedPreview, OpenAccountResult, OpenAccountsResult, OpenTxRevertedError, PUBLIC_CREATE2_FACTORY, PoolDepositResult, ProviderConfig, ProviderStatus, RWAKycFailure, RegisterMidasInvestorProps, RegisterRWAInvestorProps, RegisterRWAInvestorResult, RegisterSecuritizeInvestorProps, ReplaceStorageParams, ResilientTransportOptions, RestoreDSTokenBackDating, RestoreMidasIssuanceVaultPause, RevolverTransport, RevolverTransportConfig, RevolverTransportValue, RpcErrorResult, RpcProvider, RpcResponse, RpcSuccessResult, SUPPORTED_RPC_PROVIDERS, SelectionStrategy, TargetAccount, UnpauseMidasIssuanceVaultProps, VerifyTestnetParams, anvilNodeInfo, calcLiquidatableLTs, claimDSToken, claimDSTokens, claimFromFaucet, collectMidasGateways, collectMidasMTokens, createAnvilClient, createMinter, deployUsingPublicCreate2, detectChain, enableDSTokenBackDating, evmMineDetailed, extendAnvilClient, getAlchemyUrl, getAnkrUrl, getDrpcUrl, getErpcKey, getPublicCreate2Address, getRpcProviderUrl, getThirdWebUrl, greenlistMidasGateway, httpTransportOptionsSchema, iDegenNftv2Abi, iFaucetAbi, iOnchainExecutionIdAbi, iOwnableAbi, iaclTraitAbi, isAnvil, isDeployedUsingPublicCreate2, isOutOfSyncError, isRangeError, isRateLimitError, isTransientError, logSplitterTransport, makePendingWithdrawalsClaimable, migrateFaucet, providerConfigSchema, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor, replaceStorage, resilientTransport, resilientTransportOptionsSchema, revolverTransportConfigBaseSchema, revolverTransportConfigSchema, rpcProvidersSchema, setLTZero, setLTs, unpauseMidasIssuanceVault, verifyTestnet, writeContractSync };
29
+ export { AccountOpener, AccountOpenerOptions, AnvilActions, AnvilClient, AnvilClientConfig, AnvilDealParameters, AnvilNodeInfo, AnvilRPCSchema, CheckMulticallFn, Create2Deployer, Create2Parameters, DEFAULT_CREATE2_SALT, DEFAULT_MIDAS_ADMIN, DetectedCall, EnableDSTokenBackDatingProps, EnsureExistsUsingPublicCreate2ReturnType, EthCallMethod, EthCallRequest, EthCallSpy, GetCreate2AddressParameters, GreenlistMidasGatewayProps, IMinter, type LogSplitterTransportOptions, MakePendingWithdrawalsClaimableOptions, MidasCreditSuite, NoAvailableTransportsError, ONCHAIN_EXECUTION_ID_ADDRESS, OpenAccountHumanizedPreview, OpenAccountResult, OpenAccountsResult, OpenTxRevertedError, PUBLIC_CREATE2_FACTORY, PoolDepositResult, ProviderConfig, ProviderStatus, RWAKycFailure, RegisterMidasInvestorProps, RegisterRWAInvestorProps, RegisterRWAInvestorResult, RegisterSecuritizeInvestorProps, ReplaceStorageParams, ResilientTransportOptions, RestoreDSTokenBackDating, RestoreMidasIssuanceVaultPause, RevolverTransport, RevolverTransportConfig, RevolverTransportValue, RpcErrorResult, RpcProvider, RpcResponse, RpcSuccessResult, SUPPORTED_RPC_PROVIDERS, SelectionStrategy, TargetAccount, UnpauseMidasIssuanceVaultProps, VerifyTestnetParams, anvilNodeInfo, calcLiquidatableLTs, claimDSToken, claimDSTokens, claimFromFaucet, createAnvilClient, createMinter, deployUsingPublicCreate2, detectChain, discoverMidasCreditSuites, discoverMidasGateways, discoverMidasMTokens, enableDSTokenBackDating, evmMineDetailed, extendAnvilClient, getAlchemyUrl, getAnkrUrl, getDrpcUrl, getErpcKey, getPublicCreate2Address, getRpcProviderUrl, getSecuritizeAdmin, getThirdWebUrl, greenlistMidasGateway, httpTransportOptionsSchema, iDegenNftv2Abi, iFaucetAbi, iOnchainExecutionIdAbi, iOwnableAbi, iaclTraitAbi, isAnvil, isDeployedUsingPublicCreate2, isOutOfSyncError, isRangeError, isRateLimitError, isTransientError, logSplitterTransport, makePendingWithdrawalsClaimable, migrateFaucet, providerConfigSchema, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor, replaceStorage, resilientTransport, resilientTransportOptionsSchema, revolverTransportConfigBaseSchema, revolverTransportConfigSchema, rpcProvidersSchema, setLTZero, setLTs, unpauseMidasIssuanceVault, verifyTestnet, writeContractSync };
@@ -2,7 +2,7 @@ import { ILogger } from "../onchain/types/logger.js";
2
2
  import { OnchainSDK } from "../onchain/OnchainSDK.js";
3
3
  import "../onchain/index.js";
4
4
  import { AnvilClient } from "./createAnvilClient.js";
5
- import { Address, Hex } from "viem";
5
+ import { Address } from "viem";
6
6
  //#region src/dev/kycUtils.d.ts
7
7
  interface RegisterSecuritizeInvestorProps {
8
8
  anvil: AnvilClient;
@@ -10,20 +10,18 @@ interface RegisterSecuritizeInvestorProps {
10
10
  * Wallet to register in the Securitize DS registry
11
11
  */
12
12
  investor: Address;
13
- /**
14
- * Private key of the DS registry admin, signs the registry writes
15
- */
16
- adminPrivateKey?: Hex;
17
- /**
18
- * DS registry admin, impersonated on the fork when no private key is given
19
- */
20
- admin?: Address;
21
13
  /**
22
14
  * DSToken address
23
15
  */
24
16
  token: Address;
25
17
  logger?: ILogger;
26
18
  }
19
+ /**
20
+ * DS trust-service master of `token`: Ownable owner at storage slot 0 of the
21
+ * trust service, same as periphery-v3 SecuritizeAttachHelper. MockDSToken
22
+ * returns itself as the trust service, so `admin()` is used instead.
23
+ */
24
+ declare function getSecuritizeAdmin(anvil: AnvilClient, token: Address, logger?: ILogger): Promise<Address>;
27
25
  /**
28
26
  * Registers a wallet as a Securitize investor (fake KYC) in the DS registry
29
27
  * service of a DSToken.
@@ -82,6 +80,11 @@ interface GreenlistMidasGatewayProps {
82
80
  * Greenlists an investor in the access control of a single Midas gateway
83
81
  */
84
82
  declare function greenlistMidasGateway(props: GreenlistMidasGatewayProps): Promise<void>;
83
+ /**
84
+ * Midas access control admin on mainnet. Holds the admin role of the greenlist
85
+ * operator role.
86
+ */
87
+ declare const DEFAULT_MIDAS_ADMIN: Address;
85
88
  interface RegisterRWAInvestorProps {
86
89
  anvil: AnvilClient;
87
90
  /**
@@ -93,16 +96,8 @@ interface RegisterRWAInvestorProps {
93
96
  */
94
97
  investor: Address;
95
98
  /**
96
- * Private key of the Securitize registry admin, DSTokens are skipped when
97
- * neither this nor `securitizeAdmin` is set
98
- */
99
- adminPrivateKey?: Hex;
100
- /**
101
- * Securitize registry admin, impersonated on the fork
102
- */
103
- securitizeAdmin?: Address;
104
- /**
105
- * Override midas access control admin address
99
+ * Midas access control admin, impersonated on the fork.
100
+ * Defaults to {@link DEFAULT_MIDAS_ADMIN}.
106
101
  */
107
102
  midasAdmin?: Address;
108
103
  logger?: ILogger;
@@ -134,4 +129,4 @@ interface RegisterRWAInvestorResult {
134
129
  */
135
130
  declare function registerRWAInvestor(props: RegisterRWAInvestorProps): Promise<RegisterRWAInvestorResult>;
136
131
  //#endregion
137
- export { GreenlistMidasGatewayProps, RWAKycFailure, RegisterMidasInvestorProps, RegisterRWAInvestorProps, RegisterRWAInvestorResult, RegisterSecuritizeInvestorProps, greenlistMidasGateway, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor };
132
+ export { DEFAULT_MIDAS_ADMIN, GreenlistMidasGatewayProps, RWAKycFailure, RegisterMidasInvestorProps, RegisterRWAInvestorProps, RegisterRWAInvestorResult, RegisterSecuritizeInvestorProps, getSecuritizeAdmin, greenlistMidasGateway, registerMidasInvestor, registerRWAInvestor, registerSecuritizeInvestor };
@@ -34,13 +34,29 @@ type RestoreMidasIssuanceVaultPause = () => Promise<void>;
34
34
  */
35
35
  declare function unpauseMidasIssuanceVault(props: UnpauseMidasIssuanceVaultProps): Promise<RestoreMidasIssuanceVaultPause>;
36
36
  /**
37
- * Target contracts of all Midas gateway adapters of the loaded credit managers,
38
- * plus gateways of Midas degen NFTs (permissioned-mode suites with no gateway adapter).
37
+ * One Midas credit suite: a credit manager with a Midas gateway adapter,
38
+ * or with a Midas degen NFT when it has no such adapter.
39
39
  */
40
- declare function collectMidasGateways(sdk: OnchainSDK): Promise<Address[]>;
40
+ interface MidasCreditSuite {
41
+ creditManager: Address;
42
+ gateway: Address;
43
+ mToken: Address;
44
+ }
45
+ /**
46
+ * Midas credit suites of the loaded credit managers.
47
+ *
48
+ * A manager with a gateway adapter is a suite on its own, so its degen NFT
49
+ * is not consulted. Suites that only have a Midas degen NFT read `mToken`
50
+ * from the gateway.
51
+ */
52
+ declare function discoverMidasCreditSuites(sdk: OnchainSDK): Promise<MidasCreditSuite[]>;
53
+ /**
54
+ * Gateways of the Midas credit suites of the loaded credit managers.
55
+ */
56
+ declare function discoverMidasGateways(sdk: OnchainSDK): Promise<Address[]>;
41
57
  /**
42
58
  * mTokens of all Midas gateway adapters of the loaded credit managers
43
59
  */
44
- declare function collectMidasMTokens(sdk: OnchainSDK): Address[];
60
+ declare function discoverMidasMTokens(sdk: OnchainSDK): Address[];
45
61
  //#endregion
46
- export { RestoreMidasIssuanceVaultPause, UnpauseMidasIssuanceVaultProps, collectMidasGateways, collectMidasMTokens, unpauseMidasIssuanceVault };
62
+ export { MidasCreditSuite, RestoreMidasIssuanceVaultPause, UnpauseMidasIssuanceVaultProps, discoverMidasCreditSuites, discoverMidasGateways, discoverMidasMTokens, unpauseMidasIssuanceVault };
@@ -1,12 +1,11 @@
1
1
  import { ILogger } from "../onchain/types/logger.js";
2
2
  import "../onchain/index.js";
3
3
  import { AnvilClient } from "./createAnvilClient.js";
4
- import { Address, Hex } from "viem";
4
+ import { Address } from "viem";
5
5
  //#region src/dev/securitizeUtils.d.ts
6
6
  interface ClaimDSTokenProps {
7
7
  anvil: AnvilClient;
8
8
  investor: Address;
9
- adminPrivateKey: Hex;
10
9
  /**
11
10
  * DSToken addresses
12
11
  */
@@ -24,11 +23,6 @@ type ClaimDSTokensProps = Omit<ClaimDSTokenProps, "token"> & {
24
23
  };
25
24
  interface EnableDSTokenBackDatingProps {
26
25
  anvil: AnvilClient;
27
- /**
28
- * Securitize DS admin with sufficient trust to call compliance setters
29
- * (same key used for issueTokens / registerInvestor)
30
- */
31
- adminPrivateKey: Hex;
32
26
  /**
33
27
  * DSToken addresses whose compliance configuration should allow back-dating
34
28
  */
@@ -46,8 +40,8 @@ type RestoreDSTokenBackDating = () => Promise<void>;
46
40
  * honoured: DS protocol silently replaces it with `block.timestamp` otherwise,
47
41
  * and freshly minted tokens stay under lock-up.
48
42
  *
49
- * Must be signed by a DS admin with sufficient trust (same key as
50
- * `issueTokens` / registerInvestor); Ownable `owner()` alone is not enough.
43
+ * Impersonates the DS trust-service admin of each token. Ownable `owner()` of
44
+ * the compliance configuration service alone is not enough.
51
45
  *
52
46
  * The flag is only read while tokens are issued, so restoring it does not
53
47
  * re-lock tokens minted in the meantime.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gearbox-protocol/sdk",
3
- "version": "16.6.0-next.4",
3
+ "version": "16.6.0-next.6",
4
4
  "description": "Gearbox SDK",
5
5
  "license": "MIT",
6
6
  "repository": {