@twin.org/dlt-iota 0.0.2-next.1 → 0.0.2-next.11
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.
- package/dist/cjs/index.cjs +490 -59
- package/dist/esm/index.mjs +490 -61
- package/dist/types/index.d.ts +7 -0
- package/dist/types/iota.d.ts +11 -7
- package/dist/types/iotaSmartContractUtils.d.ts +105 -0
- package/dist/types/models/IAdminCapFields.d.ts +14 -0
- package/dist/types/models/IContractData.d.ts +29 -0
- package/dist/types/models/IIotaConfig.d.ts +5 -0
- package/dist/types/models/IMigrationStateFields.d.ts +18 -0
- package/dist/types/models/ISmartContractDeployments.d.ts +8 -0
- package/dist/types/models/ISmartContractObject.d.ts +18 -0
- package/dist/types/models/networkTypes.d.ts +21 -0
- package/docs/changelog.md +71 -0
- package/docs/reference/classes/Iota.md +18 -10
- package/docs/reference/classes/IotaSmartContractUtils.md +473 -0
- package/docs/reference/index.md +14 -0
- package/docs/reference/interfaces/IAdminCapFields.md +17 -0
- package/docs/reference/interfaces/IContractData.md +51 -0
- package/docs/reference/interfaces/IIotaConfig.md +14 -0
- package/docs/reference/interfaces/IMigrationStateFields.md +25 -0
- package/docs/reference/interfaces/ISmartContractObject.md +25 -0
- package/docs/reference/type-aliases/ISmartContractDeployments.md +5 -0
- package/docs/reference/type-aliases/NetworkTypes.md +5 -0
- package/docs/reference/variables/NetworkTypes.md +25 -0
- package/locales/en.json +23 -2
- package/package.json +17 -4
package/dist/cjs/index.cjs
CHANGED
|
@@ -36,18 +36,17 @@ class Iota {
|
|
|
36
36
|
static DEFAULT_INCLUSION_TIMEOUT = 60;
|
|
37
37
|
/**
|
|
38
38
|
* Runtime name for the class.
|
|
39
|
-
* @internal
|
|
40
39
|
*/
|
|
41
|
-
static
|
|
40
|
+
static CLASS_NAME = "Iota";
|
|
42
41
|
/**
|
|
43
42
|
* Create a new IOTA client.
|
|
44
43
|
* @param config The configuration.
|
|
45
44
|
* @returns The client instance.
|
|
46
45
|
*/
|
|
47
46
|
static createClient(config) {
|
|
48
|
-
core.Guards.object(Iota.
|
|
49
|
-
core.Guards.object(Iota.
|
|
50
|
-
core.Guards.string(Iota.
|
|
47
|
+
core.Guards.object(Iota.CLASS_NAME, "config", config);
|
|
48
|
+
core.Guards.object(Iota.CLASS_NAME, "config.clientOptions", config.clientOptions);
|
|
49
|
+
core.Guards.string(Iota.CLASS_NAME, "config.clientOptions.url", config.clientOptions.url);
|
|
51
50
|
return new client.IotaClient(config.clientOptions);
|
|
52
51
|
}
|
|
53
52
|
/**
|
|
@@ -55,7 +54,7 @@ class Iota {
|
|
|
55
54
|
* @param config The configuration to populate.
|
|
56
55
|
*/
|
|
57
56
|
static populateConfig(config) {
|
|
58
|
-
core.Guards.object(Iota.
|
|
57
|
+
core.Guards.object(Iota.CLASS_NAME, "config.clientOptions", config.clientOptions);
|
|
59
58
|
config.vaultMnemonicId ??= Iota.DEFAULT_MNEMONIC_SECRET_NAME;
|
|
60
59
|
config.vaultSeedId ??= Iota.DEFAULT_SEED_SECRET_NAME;
|
|
61
60
|
config.coinType ??= Iota.DEFAULT_COIN_TYPE;
|
|
@@ -72,10 +71,10 @@ class Iota {
|
|
|
72
71
|
* @returns The list of addresses.
|
|
73
72
|
*/
|
|
74
73
|
static getAddresses(seed, coinType, accountIndex, startAddressIndex, count, isInternal) {
|
|
75
|
-
core.Guards.integer(Iota.
|
|
76
|
-
core.Guards.integer(Iota.
|
|
77
|
-
core.Guards.integer(Iota.
|
|
78
|
-
core.Guards.integer(Iota.
|
|
74
|
+
core.Guards.integer(Iota.CLASS_NAME, "coinType", coinType);
|
|
75
|
+
core.Guards.integer(Iota.CLASS_NAME, "accountIndex", accountIndex);
|
|
76
|
+
core.Guards.integer(Iota.CLASS_NAME, "startAddressIndex", startAddressIndex);
|
|
77
|
+
core.Guards.integer(Iota.CLASS_NAME, "count", count);
|
|
79
78
|
const addresses = [];
|
|
80
79
|
for (let i = startAddressIndex; i < startAddressIndex + count; i++) {
|
|
81
80
|
// Derive the keypair using the seed
|
|
@@ -95,9 +94,9 @@ class Iota {
|
|
|
95
94
|
* @returns The key pair containing private key and public key.
|
|
96
95
|
*/
|
|
97
96
|
static getKeyPair(seed, coinType, accountIndex, addressIndex, isInternal) {
|
|
98
|
-
core.Guards.integer(Iota.
|
|
99
|
-
core.Guards.integer(Iota.
|
|
100
|
-
core.Guards.integer(Iota.
|
|
97
|
+
core.Guards.integer(Iota.CLASS_NAME, "coinType", coinType);
|
|
98
|
+
core.Guards.integer(Iota.CLASS_NAME, "accountIndex", accountIndex);
|
|
99
|
+
core.Guards.integer(Iota.CLASS_NAME, "addressIndex", addressIndex);
|
|
101
100
|
const keyPair = crypto.Bip44.keyPair(seed, crypto.KeyType.Ed25519, coinType ?? Iota.DEFAULT_COIN_TYPE, accountIndex, isInternal ?? false, addressIndex);
|
|
102
101
|
return keyPair;
|
|
103
102
|
}
|
|
@@ -105,7 +104,7 @@ class Iota {
|
|
|
105
104
|
* Prepare and post a transaction.
|
|
106
105
|
* @param config The configuration.
|
|
107
106
|
* @param vaultConnector The vault connector.
|
|
108
|
-
* @param
|
|
107
|
+
* @param logging The logging component.
|
|
109
108
|
* @param identity The identity of the user to access the vault keys.
|
|
110
109
|
* @param client The client instance.
|
|
111
110
|
* @param source The source address.
|
|
@@ -114,27 +113,27 @@ class Iota {
|
|
|
114
113
|
* @param options The transaction options.
|
|
115
114
|
* @returns The transaction result.
|
|
116
115
|
*/
|
|
117
|
-
static async prepareAndPostValueTransaction(config, vaultConnector,
|
|
116
|
+
static async prepareAndPostValueTransaction(config, vaultConnector, logging, identity, client, source, amount, recipient, options) {
|
|
118
117
|
try {
|
|
119
118
|
const txb = new transactions.Transaction();
|
|
120
119
|
const [coin] = txb.splitCoins(txb.gas, [txb.pure.u64(amount)]);
|
|
121
120
|
txb.transferObjects([coin], txb.pure.address(recipient));
|
|
122
121
|
// Check if gas station configuration is present
|
|
123
122
|
if (core.Is.object(config.gasStation)) {
|
|
124
|
-
return await
|
|
123
|
+
return await Iota.prepareAndPostGasStationTransaction(config, vaultConnector, identity, client, source, txb);
|
|
125
124
|
}
|
|
126
|
-
const result = await
|
|
125
|
+
const result = await Iota.prepareAndPostTransaction(config, vaultConnector, logging, identity, client, source, txb, options);
|
|
127
126
|
return result;
|
|
128
127
|
}
|
|
129
128
|
catch (error) {
|
|
130
|
-
throw new core.GeneralError(Iota.
|
|
129
|
+
throw new core.GeneralError(Iota.CLASS_NAME, "valueTransactionFailed", undefined, Iota.extractPayloadError(error));
|
|
131
130
|
}
|
|
132
131
|
}
|
|
133
132
|
/**
|
|
134
133
|
* Prepare and post a transaction.
|
|
135
134
|
* @param config The configuration.
|
|
136
135
|
* @param vaultConnector The vault connector.
|
|
137
|
-
* @param
|
|
136
|
+
* @param logging The logging component.
|
|
138
137
|
* @param identity The identity of the user to access the vault keys.
|
|
139
138
|
* @param client The client instance.
|
|
140
139
|
* @param owner The owner of the address.
|
|
@@ -142,17 +141,17 @@ class Iota {
|
|
|
142
141
|
* @param options The transaction options.
|
|
143
142
|
* @returns The transaction response.
|
|
144
143
|
*/
|
|
145
|
-
static async prepareAndPostTransaction(config, vaultConnector,
|
|
144
|
+
static async prepareAndPostTransaction(config, vaultConnector, logging, identity, client, owner, transaction, options) {
|
|
146
145
|
// Check if gas station configuration is present
|
|
147
146
|
if (core.Is.object(config.gasStation)) {
|
|
148
|
-
return
|
|
147
|
+
return Iota.prepareAndPostGasStationTransaction(config, vaultConnector, identity, client, owner, transaction, options);
|
|
149
148
|
}
|
|
150
149
|
// Traditional transaction flow
|
|
151
150
|
// Dry run the transaction if cost logging is enabled to get the gas and storage costs
|
|
152
151
|
if (core.Is.stringValue(options?.dryRunLabel)) {
|
|
153
|
-
await Iota.dryRunTransaction(client,
|
|
152
|
+
await Iota.dryRunTransaction(client, logging, transaction, owner, options.dryRunLabel);
|
|
154
153
|
}
|
|
155
|
-
const seed = await
|
|
154
|
+
const seed = await Iota.getSeed(config, vaultConnector, identity);
|
|
156
155
|
const addressKeyPair = Iota.findAddress(config.maxAddressScanRange ?? Iota.DEFAULT_SCAN_RANGE, config.coinType ?? Iota.DEFAULT_COIN_TYPE, seed, owner);
|
|
157
156
|
const keypair = ed25519.Ed25519Keypair.fromSecretKey(addressKeyPair.privateKey);
|
|
158
157
|
try {
|
|
@@ -178,7 +177,7 @@ class Iota {
|
|
|
178
177
|
return response;
|
|
179
178
|
}
|
|
180
179
|
catch (error) {
|
|
181
|
-
throw new core.GeneralError(Iota.
|
|
180
|
+
throw new core.GeneralError(Iota.CLASS_NAME, "transactionFailed", undefined, Iota.extractPayloadError(error));
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
183
|
/**
|
|
@@ -213,7 +212,7 @@ class Iota {
|
|
|
213
212
|
return addressKeyPair;
|
|
214
213
|
}
|
|
215
214
|
}
|
|
216
|
-
throw new core.GeneralError(Iota.
|
|
215
|
+
throw new core.GeneralError(Iota.CLASS_NAME, "addressNotFound", { address });
|
|
217
216
|
}
|
|
218
217
|
/**
|
|
219
218
|
* Extract error from SDK payload.
|
|
@@ -226,8 +225,11 @@ class Iota {
|
|
|
226
225
|
if (!core.Is.empty(error.inner)) {
|
|
227
226
|
error.inner = Iota.extractPayloadError(error.inner);
|
|
228
227
|
}
|
|
228
|
+
if (!core.Is.empty(error.cause)) {
|
|
229
|
+
error.cause = Iota.extractPayloadError(error.cause);
|
|
230
|
+
}
|
|
229
231
|
if (error.code === "InsufficientGas") {
|
|
230
|
-
return new core.GeneralError(Iota.
|
|
232
|
+
return new core.GeneralError(Iota.CLASS_NAME, "insufficientFunds");
|
|
231
233
|
}
|
|
232
234
|
else if (error.message?.startsWith("ErrorObject")) {
|
|
233
235
|
const msg = /message: "(.*)"/.exec(error.message);
|
|
@@ -239,7 +241,7 @@ class Iota {
|
|
|
239
241
|
const baseError = core.BaseError.fromError(error);
|
|
240
242
|
if (baseError.name === "Base" && !core.Is.stringValue(baseError.source)) {
|
|
241
243
|
baseError.name = "IOTA";
|
|
242
|
-
baseError.source = Iota.
|
|
244
|
+
baseError.source = Iota.CLASS_NAME;
|
|
243
245
|
}
|
|
244
246
|
return baseError;
|
|
245
247
|
}
|
|
@@ -279,7 +281,7 @@ class Iota {
|
|
|
279
281
|
if (packageObject?.error?.code === "notExists") {
|
|
280
282
|
return false;
|
|
281
283
|
}
|
|
282
|
-
throw new core.GeneralError(Iota.
|
|
284
|
+
throw new core.GeneralError(Iota.CLASS_NAME, "packageObjectError", {
|
|
283
285
|
packageId,
|
|
284
286
|
error: packageObject.error
|
|
285
287
|
});
|
|
@@ -287,16 +289,15 @@ class Iota {
|
|
|
287
289
|
return true;
|
|
288
290
|
}
|
|
289
291
|
catch (error) {
|
|
290
|
-
throw new core.GeneralError(Iota.
|
|
291
|
-
packageId
|
|
292
|
-
|
|
293
|
-
});
|
|
292
|
+
throw new core.GeneralError(Iota.CLASS_NAME, "packageNotFoundOnNetwork", {
|
|
293
|
+
packageId
|
|
294
|
+
}, Iota.extractPayloadError(error));
|
|
294
295
|
}
|
|
295
296
|
}
|
|
296
297
|
/**
|
|
297
298
|
* Dry run a transaction and log the results.
|
|
298
299
|
* @param client The IOTA client.
|
|
299
|
-
* @param logging The logging
|
|
300
|
+
* @param logging The logging component.
|
|
300
301
|
* @param txb The transaction to dry run.
|
|
301
302
|
* @param sender The sender address.
|
|
302
303
|
* @param operation The operation to log.
|
|
@@ -313,7 +314,7 @@ class Iota {
|
|
|
313
314
|
transactionBlock: builtTx
|
|
314
315
|
});
|
|
315
316
|
if (dryRunResult.effects.status?.status !== "success") {
|
|
316
|
-
throw new core.GeneralError(
|
|
317
|
+
throw new core.GeneralError(Iota.CLASS_NAME, "dryRunFailed", {
|
|
317
318
|
error: dryRunResult.effects?.status?.error
|
|
318
319
|
});
|
|
319
320
|
}
|
|
@@ -330,25 +331,23 @@ class Iota {
|
|
|
330
331
|
balanceChanges: dryRunResult.balanceChanges ?? [],
|
|
331
332
|
objectChanges: dryRunResult.objectChanges ?? []
|
|
332
333
|
};
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
});
|
|
344
|
-
}
|
|
334
|
+
await logging?.log({
|
|
335
|
+
level: "info",
|
|
336
|
+
source: Iota.CLASS_NAME,
|
|
337
|
+
ts: Date.now(),
|
|
338
|
+
message: "transactionCosts",
|
|
339
|
+
data: {
|
|
340
|
+
operation,
|
|
341
|
+
cost: JSON.stringify(result)
|
|
342
|
+
}
|
|
343
|
+
});
|
|
345
344
|
return result;
|
|
346
345
|
}
|
|
347
346
|
catch (error) {
|
|
348
|
-
if (error
|
|
347
|
+
if (core.BaseError.isErrorName(error, core.GeneralError.CLASS_NAME)) {
|
|
349
348
|
throw error;
|
|
350
349
|
}
|
|
351
|
-
throw new core.GeneralError(Iota.
|
|
350
|
+
throw new core.GeneralError(Iota.CLASS_NAME, "dryRunFailed", undefined, Iota.extractPayloadError(error));
|
|
352
351
|
}
|
|
353
352
|
}
|
|
354
353
|
/**
|
|
@@ -402,11 +401,11 @@ class Iota {
|
|
|
402
401
|
* @returns The transaction response.
|
|
403
402
|
*/
|
|
404
403
|
static async prepareAndPostGasStationTransaction(config, vaultConnector, identity, client, owner, transaction, options) {
|
|
405
|
-
core.Guards.object(
|
|
404
|
+
core.Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
|
|
406
405
|
try {
|
|
407
406
|
// Reserve gas from the gas station
|
|
408
407
|
const gasBudget = config.gasBudget ?? 50000000;
|
|
409
|
-
const gasReservation = await
|
|
408
|
+
const gasReservation = await Iota.reserveGas(config, gasBudget);
|
|
410
409
|
// Set transaction parameters for sponsoring
|
|
411
410
|
transaction.setSender(owner);
|
|
412
411
|
transaction.setGasOwner(gasReservation.sponsorAddress);
|
|
@@ -415,14 +414,14 @@ class Iota {
|
|
|
415
414
|
// Build and sign transaction
|
|
416
415
|
const unsignedTxBytes = await transaction.build({ client });
|
|
417
416
|
// Sign the transaction with the user's private key
|
|
418
|
-
const seed = await
|
|
417
|
+
const seed = await Iota.getSeed(config, vaultConnector, identity);
|
|
419
418
|
const addressKeyPair = Iota.findAddress(config.maxAddressScanRange ?? Iota.DEFAULT_SCAN_RANGE, config.coinType ?? Iota.DEFAULT_COIN_TYPE, seed, owner);
|
|
420
419
|
const keypair = ed25519.Ed25519Keypair.fromSecretKey(addressKeyPair.privateKey);
|
|
421
420
|
const signature = await keypair.signTransaction(unsignedTxBytes);
|
|
422
|
-
return await
|
|
421
|
+
return await Iota.executeAndConfirmGasStationTransaction(config, client, gasReservation.reservationId, unsignedTxBytes, signature.signature, options);
|
|
423
422
|
}
|
|
424
423
|
catch (error) {
|
|
425
|
-
throw new core.GeneralError(Iota.
|
|
424
|
+
throw new core.GeneralError(Iota.CLASS_NAME, "gasStationTransactionFailed", undefined, Iota.extractPayloadError(error));
|
|
426
425
|
}
|
|
427
426
|
}
|
|
428
427
|
/**
|
|
@@ -432,7 +431,7 @@ class Iota {
|
|
|
432
431
|
* @returns The gas reservation result.
|
|
433
432
|
*/
|
|
434
433
|
static async reserveGas(config, gasBudget) {
|
|
435
|
-
core.Guards.object(
|
|
434
|
+
core.Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
|
|
436
435
|
const requestData = {
|
|
437
436
|
// eslint-disable-next-line camelcase
|
|
438
437
|
gas_budget: gasBudget,
|
|
@@ -440,7 +439,7 @@ class Iota {
|
|
|
440
439
|
reserve_duration_secs: 30
|
|
441
440
|
};
|
|
442
441
|
const baseUrl = core.StringHelper.trimTrailingSlashes(config.gasStation.gasStationUrl);
|
|
443
|
-
const result = await web.FetchHelper.fetchJson(
|
|
442
|
+
const result = await web.FetchHelper.fetchJson(Iota.CLASS_NAME, `${baseUrl}/v1/reserve_gas`, web.HttpMethod.POST, requestData, {
|
|
444
443
|
headers: {
|
|
445
444
|
Authorization: `Bearer ${config.gasStation.gasStationAuthToken}`
|
|
446
445
|
}
|
|
@@ -461,7 +460,7 @@ class Iota {
|
|
|
461
460
|
* @returns The transaction response.
|
|
462
461
|
*/
|
|
463
462
|
static async executeGasStationTransaction(config, reservationId, transactionBytes, userSignature) {
|
|
464
|
-
core.Guards.object(
|
|
463
|
+
core.Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
|
|
465
464
|
const requestData = {
|
|
466
465
|
// eslint-disable-next-line camelcase
|
|
467
466
|
reservation_id: reservationId,
|
|
@@ -471,7 +470,7 @@ class Iota {
|
|
|
471
470
|
user_sig: userSignature
|
|
472
471
|
};
|
|
473
472
|
const baseUrl = core.StringHelper.trimTrailingSlashes(config.gasStation.gasStationUrl);
|
|
474
|
-
const result = await web.FetchHelper.fetchJson(
|
|
473
|
+
const result = await web.FetchHelper.fetchJson(Iota.CLASS_NAME, `${baseUrl}/v1/execute_tx`, web.HttpMethod.POST, requestData, {
|
|
475
474
|
headers: {
|
|
476
475
|
Authorization: `Bearer ${config.gasStation.gasStationAuthToken}`
|
|
477
476
|
}
|
|
@@ -497,8 +496,8 @@ class Iota {
|
|
|
497
496
|
* @returns The transaction response (confirmed if waitForConfirmation is true).
|
|
498
497
|
*/
|
|
499
498
|
static async executeAndConfirmGasStationTransaction(config, client, reservationId, transactionBytes, userSignature, options) {
|
|
500
|
-
core.Guards.object(
|
|
501
|
-
const response = await
|
|
499
|
+
core.Guards.object(Iota.CLASS_NAME, "config.gasStation", config.gasStation);
|
|
500
|
+
const response = await Iota.executeGasStationTransaction(config, reservationId, transactionBytes, userSignature);
|
|
502
501
|
if (options?.waitForConfirmation ?? true) {
|
|
503
502
|
const confirmedTransaction = await Iota.waitForTransactionConfirmation(client, response.digest, config, {
|
|
504
503
|
showEffects: options?.showEffects ?? true,
|
|
@@ -511,4 +510,436 @@ class Iota {
|
|
|
511
510
|
}
|
|
512
511
|
}
|
|
513
512
|
|
|
513
|
+
// Copyright 2024 IOTA Stiftung.
|
|
514
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
515
|
+
/**
|
|
516
|
+
* Utility class providing common smart contract operations for IOTA-based contracts.
|
|
517
|
+
* This class uses composition pattern to provide shared functionality without inheritance complexity.
|
|
518
|
+
*/
|
|
519
|
+
class IotaSmartContractUtils {
|
|
520
|
+
/**
|
|
521
|
+
* Runtime name for the class.
|
|
522
|
+
*/
|
|
523
|
+
static CLASS_NAME = "IotaSmartContractUtils";
|
|
524
|
+
/**
|
|
525
|
+
* Migrate a smart contract object to the current version using admin privileges.
|
|
526
|
+
* This is a generic migration method that works with any IOTA smart contract.
|
|
527
|
+
* @param config The IOTA configuration.
|
|
528
|
+
* @param client The IOTA client instance.
|
|
529
|
+
* @param vaultConnector The vault connector for key management.
|
|
530
|
+
* @param walletConnector The wallet connector for address generation.
|
|
531
|
+
* @param logging Optional logging component.
|
|
532
|
+
* @param gasBudget The gas budget for the transaction.
|
|
533
|
+
* @param identity The identity of the controller with admin privileges.
|
|
534
|
+
* @param objectId The ID of the object to migrate.
|
|
535
|
+
* @param namespace The contract namespace (e.g., "nft", "verifiable_storage").
|
|
536
|
+
* @param packageId The deployed package ID for the contract.
|
|
537
|
+
* @param deploymentConfig The deployment configuration containing object IDs.
|
|
538
|
+
* @param walletAddressIndex Optional wallet address index for the controller.
|
|
539
|
+
* @returns Promise that resolves when migration is complete.
|
|
540
|
+
*/
|
|
541
|
+
static async migrateSmartContract(config, client, vaultConnector, walletConnector, logging, gasBudget, identity, objectId, namespace, packageId, deploymentConfig, walletAddressIndex) {
|
|
542
|
+
try {
|
|
543
|
+
const txb = new transactions.Transaction();
|
|
544
|
+
txb.setGasBudget(gasBudget);
|
|
545
|
+
const moduleName = IotaSmartContractUtils.getModuleName(namespace);
|
|
546
|
+
// Get admin address for the transaction
|
|
547
|
+
const adminAddress = await IotaSmartContractUtils.getPackageControllerAddress(walletConnector, identity, walletAddressIndex);
|
|
548
|
+
// Get the required object IDs from deployment config
|
|
549
|
+
const { adminCapId, migrationStateId } = await IotaSmartContractUtils.getContractObjectIds(client, namespace, config.network, deploymentConfig, packageId, adminAddress);
|
|
550
|
+
txb.moveCall({
|
|
551
|
+
target: `${packageId}::${moduleName}::migrate_${moduleName}`,
|
|
552
|
+
arguments: [txb.object(adminCapId), txb.object(migrationStateId), txb.object(objectId)]
|
|
553
|
+
});
|
|
554
|
+
const result = await Iota.prepareAndPostTransaction(config, vaultConnector, logging, identity, client, adminAddress, txb, {
|
|
555
|
+
dryRunLabel: config.enableCostLogging ? "migrate_object" : undefined
|
|
556
|
+
});
|
|
557
|
+
if (result.effects?.status?.status !== "success") {
|
|
558
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "migrationFailed", {
|
|
559
|
+
error: result.effects?.status?.error,
|
|
560
|
+
objectId
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
catch (error) {
|
|
565
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "migrateSmartContractFailed", { objectId }, error);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Enable migration operations using admin privileges.
|
|
570
|
+
* @param config The IOTA configuration.
|
|
571
|
+
* @param client The IOTA client instance.
|
|
572
|
+
* @param vaultConnector The vault connector for key management.
|
|
573
|
+
* @param walletConnector The wallet connector for address generation.
|
|
574
|
+
* @param logging Optional logging component.
|
|
575
|
+
* @param gasBudget The gas budget for the transaction.
|
|
576
|
+
* @param identity The identity of the controller with admin privileges.
|
|
577
|
+
* @param namespace The contract namespace (e.g., "nft", "verifiable_storage").
|
|
578
|
+
* @param packageId The deployed package ID for the contract.
|
|
579
|
+
* @param deploymentConfig The deployment configuration containing object IDs.
|
|
580
|
+
* @param walletAddressIndex Optional wallet address index for the controller.
|
|
581
|
+
* @returns Promise that resolves when migration is enabled.
|
|
582
|
+
*/
|
|
583
|
+
static async enableMigration(config, client, vaultConnector, walletConnector, logging, gasBudget, identity, namespace, packageId, deploymentConfig, walletAddressIndex) {
|
|
584
|
+
try {
|
|
585
|
+
const txb = new transactions.Transaction();
|
|
586
|
+
txb.setGasBudget(gasBudget);
|
|
587
|
+
const moduleName = IotaSmartContractUtils.getModuleName(namespace);
|
|
588
|
+
// Get admin address for the transaction
|
|
589
|
+
const adminAddress = await IotaSmartContractUtils.getPackageControllerAddress(walletConnector, identity, walletAddressIndex);
|
|
590
|
+
// Get the required object IDs from deployment config
|
|
591
|
+
const { adminCapId, migrationStateId } = await IotaSmartContractUtils.getContractObjectIds(client, namespace, config.network, deploymentConfig, packageId, adminAddress);
|
|
592
|
+
txb.moveCall({
|
|
593
|
+
target: `${packageId}::${moduleName}::enable_migration`,
|
|
594
|
+
arguments: [txb.object(adminCapId), txb.object(migrationStateId)]
|
|
595
|
+
});
|
|
596
|
+
const result = await Iota.prepareAndPostTransaction(config, vaultConnector, logging, identity, client, adminAddress, txb, {
|
|
597
|
+
dryRunLabel: config.enableCostLogging ? "enable_migration" : undefined
|
|
598
|
+
});
|
|
599
|
+
if (result.effects?.status?.status !== "success") {
|
|
600
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "enableMigrationFailed", {
|
|
601
|
+
error: result.effects?.status?.error
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
606
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "enableMigrationFailed", undefined, error);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Disable migration operations using admin privileges.
|
|
611
|
+
* @param config The IOTA configuration.
|
|
612
|
+
* @param client The IOTA client instance.
|
|
613
|
+
* @param vaultConnector The vault connector for key management.
|
|
614
|
+
* @param walletConnector The wallet connector for address generation.
|
|
615
|
+
* @param logging Optional logging component.
|
|
616
|
+
* @param gasBudget The gas budget for the transaction.
|
|
617
|
+
* @param identity The identity of the controller with admin privileges.
|
|
618
|
+
* @param namespace The contract namespace (e.g., "nft", "verifiable_storage").
|
|
619
|
+
* @param packageId The deployed package ID for the contract.
|
|
620
|
+
* @param deploymentConfig The deployment configuration containing object IDs.
|
|
621
|
+
* @param walletAddressIndex Optional wallet address index for the controller.
|
|
622
|
+
* @returns Promise that resolves when migration is disabled.
|
|
623
|
+
*/
|
|
624
|
+
static async disableMigration(config, client, vaultConnector, walletConnector, logging, gasBudget, identity, namespace, packageId, deploymentConfig, walletAddressIndex) {
|
|
625
|
+
try {
|
|
626
|
+
const txb = new transactions.Transaction();
|
|
627
|
+
txb.setGasBudget(gasBudget);
|
|
628
|
+
const moduleName = IotaSmartContractUtils.getModuleName(namespace);
|
|
629
|
+
// Get admin address for the transaction
|
|
630
|
+
const adminAddress = await IotaSmartContractUtils.getPackageControllerAddress(walletConnector, identity, walletAddressIndex);
|
|
631
|
+
// Get the required object IDs from deployment config
|
|
632
|
+
const { adminCapId, migrationStateId } = await IotaSmartContractUtils.getContractObjectIds(client, namespace, config.network, deploymentConfig, packageId, adminAddress);
|
|
633
|
+
txb.moveCall({
|
|
634
|
+
target: `${packageId}::${moduleName}::disable_migration`,
|
|
635
|
+
arguments: [txb.object(adminCapId), txb.object(migrationStateId)]
|
|
636
|
+
});
|
|
637
|
+
const result = await Iota.prepareAndPostTransaction(config, vaultConnector, logging, identity, client, adminAddress, txb, {
|
|
638
|
+
dryRunLabel: config.enableCostLogging ? "disable_migration" : undefined
|
|
639
|
+
});
|
|
640
|
+
if (result.effects?.status?.status !== "success") {
|
|
641
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "disableMigrationFailed", {
|
|
642
|
+
error: result.effects?.status?.error
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
catch (error) {
|
|
647
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "disableMigrationFailed", undefined, error);
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
/**
|
|
651
|
+
* Check if migration is currently active for a smart contract.
|
|
652
|
+
* @param config The IOTA configuration.
|
|
653
|
+
* @param client The IOTA client instance.
|
|
654
|
+
* @param namespace The contract namespace (e.g., "nft", "verifiable_storage").
|
|
655
|
+
* @param packageId The deployed package ID for the contract.
|
|
656
|
+
* @param deploymentConfig The deployment configuration containing object IDs.
|
|
657
|
+
* @param identity The identity for MigrationState discovery.
|
|
658
|
+
* @param walletConnector The wallet connector for address generation.
|
|
659
|
+
* @param walletAddressIndex Optional wallet address index.
|
|
660
|
+
* @returns True if migration is enabled, false otherwise.
|
|
661
|
+
*/
|
|
662
|
+
static async isMigrationActive(config, client, namespace, packageId, deploymentConfig, identity, walletConnector, walletAddressIndex) {
|
|
663
|
+
try {
|
|
664
|
+
// Get admin address for discovery
|
|
665
|
+
const adminAddress = await IotaSmartContractUtils.getPackageControllerAddress(walletConnector, identity, walletAddressIndex);
|
|
666
|
+
// Get the migration state ID
|
|
667
|
+
const { migrationStateId } = await IotaSmartContractUtils.getContractObjectIds(client, namespace, config.network, deploymentConfig, packageId, adminAddress);
|
|
668
|
+
const migrationStateResponse = await client.getObject({
|
|
669
|
+
id: migrationStateId,
|
|
670
|
+
options: {
|
|
671
|
+
showContent: true,
|
|
672
|
+
showType: true
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
if (!migrationStateResponse.data?.content) {
|
|
676
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "migrationStateNotReadable", {
|
|
677
|
+
migrationStateId
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
const content = migrationStateResponse.data.content;
|
|
681
|
+
if (content.dataType === "moveObject" && core.Is.objectValue(content.fields)) {
|
|
682
|
+
const fields = content.fields;
|
|
683
|
+
return core.Is.boolean(fields.enabled) ? fields.enabled : false;
|
|
684
|
+
}
|
|
685
|
+
return false;
|
|
686
|
+
}
|
|
687
|
+
catch (error) {
|
|
688
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "isMigrationActiveFailed", undefined, error);
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
692
|
+
* Get the current contract version from the deployed smart contract.
|
|
693
|
+
* @param config The IOTA configuration.
|
|
694
|
+
* @param client The IOTA client instance.
|
|
695
|
+
* @param namespace The contract namespace (e.g., "nft", "verifiable_storage").
|
|
696
|
+
* @param packageId The deployed package ID for the contract.
|
|
697
|
+
* @param identity The identity for package controller address.
|
|
698
|
+
* @param walletConnector The wallet connector for address generation.
|
|
699
|
+
* @param walletAddressIndex Optional wallet address index.
|
|
700
|
+
* @returns The current version number of the contract.
|
|
701
|
+
*/
|
|
702
|
+
static async getCurrentContractVersion(config, client, namespace, packageId, identity, walletConnector, walletAddressIndex) {
|
|
703
|
+
try {
|
|
704
|
+
const tx = new transactions.Transaction();
|
|
705
|
+
const moduleName = IotaSmartContractUtils.getModuleName(namespace);
|
|
706
|
+
tx.moveCall({
|
|
707
|
+
target: `${packageId}::${moduleName}::get_current_version`,
|
|
708
|
+
arguments: []
|
|
709
|
+
});
|
|
710
|
+
const controllerAddress = await IotaSmartContractUtils.getPackageControllerAddress(walletConnector, identity, walletAddressIndex);
|
|
711
|
+
const result = await client.devInspectTransactionBlock({
|
|
712
|
+
sender: controllerAddress,
|
|
713
|
+
transactionBlock: tx
|
|
714
|
+
});
|
|
715
|
+
if (core.Is.arrayValue(result.results) &&
|
|
716
|
+
core.Is.object(result.results[0]) &&
|
|
717
|
+
core.Is.arrayValue(result.results[0].returnValues)) {
|
|
718
|
+
const versionBytes = result.results[0].returnValues[0];
|
|
719
|
+
// Convert to Uint8Array if it's a regular array
|
|
720
|
+
const byteData = versionBytes[0];
|
|
721
|
+
if (!core.Is.arrayValue(byteData) && !core.Is.uint8Array(byteData)) {
|
|
722
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "invalidVersionData");
|
|
723
|
+
}
|
|
724
|
+
// Convert to Uint8Array for BCS parsing
|
|
725
|
+
const uint8Data = core.Is.uint8Array(byteData) ? byteData : new Uint8Array(byteData);
|
|
726
|
+
// The version is returned as a u64, decode it from bytes using BCS
|
|
727
|
+
// IOTA Move contracts return data in BCS format
|
|
728
|
+
const version = Number(bcs.bcs.u64().parse(uint8Data));
|
|
729
|
+
return version;
|
|
730
|
+
}
|
|
731
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "getCurrentContractVersionNoData", {
|
|
732
|
+
resultExists: core.Is.arrayValue(result.results),
|
|
733
|
+
resultLength: core.Is.arrayValue(result.results) ? result.results.length : 0,
|
|
734
|
+
hasReturnValues: core.Is.arrayValue(result.results?.[0]?.returnValues)
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
catch (error) {
|
|
738
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "getCurrentContractVersionFailed", undefined, error);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Validate that an object version is compatible with the current contract.
|
|
743
|
+
* @param config The IOTA configuration.
|
|
744
|
+
* @param client The IOTA client instance.
|
|
745
|
+
* @param namespace The contract namespace (e.g., "nft", "verifiable_storage").
|
|
746
|
+
* @param packageId The deployed package ID for the contract.
|
|
747
|
+
* @param identity The identity for version checking.
|
|
748
|
+
* @param objectId The object ID to validate.
|
|
749
|
+
* @param walletConnector The wallet connector for address generation.
|
|
750
|
+
* @param versionExtractor Function to extract version from object content.
|
|
751
|
+
* @param walletAddressIndex Optional wallet address index.
|
|
752
|
+
* @returns True if the object version is compatible, false otherwise.
|
|
753
|
+
*/
|
|
754
|
+
static async validateObjectVersion(config, client, namespace, packageId, identity, objectId, walletConnector, versionExtractor, walletAddressIndex) {
|
|
755
|
+
try {
|
|
756
|
+
// Get current contract version
|
|
757
|
+
const currentVersion = await IotaSmartContractUtils.getCurrentContractVersion(config, client, namespace, packageId, identity, walletConnector, walletAddressIndex);
|
|
758
|
+
// Get object version
|
|
759
|
+
const objectResponse = await client.getObject({
|
|
760
|
+
id: objectId,
|
|
761
|
+
options: {
|
|
762
|
+
showContent: true,
|
|
763
|
+
showType: true
|
|
764
|
+
}
|
|
765
|
+
});
|
|
766
|
+
if (!objectResponse.data?.content) {
|
|
767
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "objectNotReadable", {
|
|
768
|
+
objectId
|
|
769
|
+
});
|
|
770
|
+
}
|
|
771
|
+
const content = objectResponse.data.content;
|
|
772
|
+
if (content.dataType === "moveObject" && core.Is.objectValue(content.fields)) {
|
|
773
|
+
const objectVersion = versionExtractor(content);
|
|
774
|
+
return objectVersion <= currentVersion;
|
|
775
|
+
}
|
|
776
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "objectInvalidFormat", {
|
|
777
|
+
objectId,
|
|
778
|
+
content
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "validateObjectVersionFailed", { objectId }, error);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Get the module name for a given namespace.
|
|
787
|
+
* @param namespace The contract namespace.
|
|
788
|
+
* @returns The module name in snake_case format.
|
|
789
|
+
* @internal
|
|
790
|
+
*/
|
|
791
|
+
static getModuleName(namespace) {
|
|
792
|
+
// Convert namespace to snake_case for module name
|
|
793
|
+
return core.StringHelper.snakeCase(namespace);
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Get the package controller address for transactions.
|
|
797
|
+
* @param walletConnector The wallet connector for address generation.
|
|
798
|
+
* @param identity The identity to use.
|
|
799
|
+
* @param addressIndex Optional address index to use.
|
|
800
|
+
* @returns The controller address.
|
|
801
|
+
* @internal
|
|
802
|
+
*/
|
|
803
|
+
static async getPackageControllerAddress(walletConnector, identity, addressIndex = 0) {
|
|
804
|
+
const addresses = await walletConnector.getAddresses(identity, 0, addressIndex, 1);
|
|
805
|
+
return addresses[0];
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* Get contract object IDs (AdminCap and MigrationState) from deployment config with fallback discovery.
|
|
809
|
+
* @param client The IOTA client instance.
|
|
810
|
+
* @param namespace The contract namespace.
|
|
811
|
+
* @param network The network name.
|
|
812
|
+
* @param deploymentConfig The deployment configuration.
|
|
813
|
+
* @param packageId The package ID.
|
|
814
|
+
* @param adminAddress The admin address for object discovery.
|
|
815
|
+
* @returns Object containing adminCapId and migrationStateId.
|
|
816
|
+
* @internal
|
|
817
|
+
*/
|
|
818
|
+
static async getContractObjectIds(client, namespace, network, deploymentConfig, packageId, adminAddress) {
|
|
819
|
+
try {
|
|
820
|
+
// First try to load from deployment JSON
|
|
821
|
+
const networkConfig = deploymentConfig[network];
|
|
822
|
+
if (core.Is.objectValue(networkConfig)) {
|
|
823
|
+
const migrationStateId = networkConfig.migrationStateId;
|
|
824
|
+
// AdminCap must be discovered from blockchain (not stored in JSON)
|
|
825
|
+
const adminCapId = await IotaSmartContractUtils.discoverAdminCap(client, packageId, namespace, adminAddress);
|
|
826
|
+
if (core.Is.stringValue(migrationStateId) && core.Is.stringValue(adminCapId)) {
|
|
827
|
+
return { adminCapId, migrationStateId };
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
// Fallback: discover both from blockchain
|
|
831
|
+
return await IotaSmartContractUtils.discoverContractObjectsFromBlockchain(client, packageId, namespace, adminAddress);
|
|
832
|
+
}
|
|
833
|
+
catch (error) {
|
|
834
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "getContractObjectIdsFailed", { namespace, network, packageId }, error);
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Discover AdminCap object from the blockchain.
|
|
839
|
+
* @param client The IOTA client instance.
|
|
840
|
+
* @param packageId The package ID.
|
|
841
|
+
* @param namespace The contract namespace.
|
|
842
|
+
* @param adminAddress The admin address.
|
|
843
|
+
* @returns The AdminCap object ID.
|
|
844
|
+
* @internal
|
|
845
|
+
*/
|
|
846
|
+
static async discoverAdminCap(client, packageId, namespace, adminAddress) {
|
|
847
|
+
const adminCapType = `${packageId}::${IotaSmartContractUtils.getModuleName(namespace)}::AdminCap`;
|
|
848
|
+
const adminCapObjects = await client.getOwnedObjects({
|
|
849
|
+
owner: adminAddress,
|
|
850
|
+
filter: {
|
|
851
|
+
StructType: adminCapType
|
|
852
|
+
},
|
|
853
|
+
options: {
|
|
854
|
+
showContent: true,
|
|
855
|
+
showType: true
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
if (core.Is.arrayValue(adminCapObjects.data) && adminCapObjects.data.length > 0) {
|
|
859
|
+
const adminCapObject = adminCapObjects.data[0];
|
|
860
|
+
if (core.Is.stringValue(adminCapObject.data?.objectId)) {
|
|
861
|
+
return adminCapObject.data.objectId;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "adminCapNotFound", {
|
|
865
|
+
adminCapType,
|
|
866
|
+
adminAddress
|
|
867
|
+
});
|
|
868
|
+
}
|
|
869
|
+
/**
|
|
870
|
+
* Discover contract objects from blockchain as fallback.
|
|
871
|
+
* @param client The IOTA client instance.
|
|
872
|
+
* @param packageId The package ID.
|
|
873
|
+
* @param namespace The contract namespace.
|
|
874
|
+
* @param adminAddress The admin address.
|
|
875
|
+
* @returns Object containing adminCapId and migrationStateId.
|
|
876
|
+
* @internal
|
|
877
|
+
*/
|
|
878
|
+
static async discoverContractObjectsFromBlockchain(client, packageId, namespace, adminAddress) {
|
|
879
|
+
// Discover AdminCap
|
|
880
|
+
const adminCapId = await IotaSmartContractUtils.discoverAdminCap(client, packageId, namespace, adminAddress);
|
|
881
|
+
// Discover MigrationState through transaction history
|
|
882
|
+
const migrationStateType = `${packageId}::${IotaSmartContractUtils.getModuleName(namespace)}::MigrationState`;
|
|
883
|
+
const transactions = await client.queryTransactionBlocks({
|
|
884
|
+
filter: {
|
|
885
|
+
FromAddress: adminAddress
|
|
886
|
+
},
|
|
887
|
+
options: {
|
|
888
|
+
showObjectChanges: true,
|
|
889
|
+
showEffects: true
|
|
890
|
+
},
|
|
891
|
+
limit: 20,
|
|
892
|
+
order: "descending"
|
|
893
|
+
});
|
|
894
|
+
// Look for MigrationState object creation in transaction history
|
|
895
|
+
let migrationStateId;
|
|
896
|
+
for (const tx of transactions.data) {
|
|
897
|
+
const objectChanges = tx.objectChanges;
|
|
898
|
+
if (core.Is.arrayValue(objectChanges)) {
|
|
899
|
+
for (const change of objectChanges) {
|
|
900
|
+
if ((change.type === "created" || change.type === "mutated") &&
|
|
901
|
+
"objectType" in change &&
|
|
902
|
+
change.objectType === migrationStateType) {
|
|
903
|
+
migrationStateId = change.objectId;
|
|
904
|
+
break;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (migrationStateId) {
|
|
908
|
+
break;
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
if (!migrationStateId) {
|
|
913
|
+
throw new core.GeneralError(IotaSmartContractUtils.CLASS_NAME, "migrationStateNotFound", {
|
|
914
|
+
migrationStateType,
|
|
915
|
+
adminAddress
|
|
916
|
+
});
|
|
917
|
+
}
|
|
918
|
+
return { adminCapId, migrationStateId };
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
// Copyright 2024 IOTA Stiftung.
|
|
923
|
+
// SPDX-License-Identifier: Apache-2.0.
|
|
924
|
+
/**
|
|
925
|
+
* Network types supported for deployment
|
|
926
|
+
*/
|
|
927
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
928
|
+
const NetworkTypes = {
|
|
929
|
+
/**
|
|
930
|
+
* Testnet.
|
|
931
|
+
*/
|
|
932
|
+
Testnet: "testnet",
|
|
933
|
+
/**
|
|
934
|
+
* Devnet.
|
|
935
|
+
*/
|
|
936
|
+
Devnet: "devnet",
|
|
937
|
+
/**
|
|
938
|
+
* Mainnet.
|
|
939
|
+
*/
|
|
940
|
+
Mainnet: "mainnet"
|
|
941
|
+
};
|
|
942
|
+
|
|
514
943
|
exports.Iota = Iota;
|
|
944
|
+
exports.IotaSmartContractUtils = IotaSmartContractUtils;
|
|
945
|
+
exports.NetworkTypes = NetworkTypes;
|