@twin.org/move-to-json 0.0.2-next.6 → 0.0.2-next.8

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.
@@ -67,33 +67,31 @@ function buildCommandBuild(program) {
67
67
  .command("build")
68
68
  .description(core.I18n.formatMessage("commands.build.description"))
69
69
  .argument("<inputGlob>", core.I18n.formatMessage("commands.build.options.inputGlob.description"))
70
- .option(core.I18n.formatMessage("commands.build.options.network.param"), core.I18n.formatMessage("commands.build.options.network.description"))
70
+ .option(core.I18n.formatMessage("commands.build.options.network.param"), core.I18n.formatMessage("commands.build.options.network.description"), "!NETWORK")
71
71
  .option(core.I18n.formatMessage("commands.build.options.output.param"), core.I18n.formatMessage("commands.build.options.output.description"), "smart-contract-deployments.json")
72
- .action(async (inputGlob, opts) => {
73
- await actionCommandBuild(inputGlob, opts);
74
- });
72
+ .action(actionCommandBuild);
75
73
  }
76
74
  /**
77
75
  * Action for the build command.
78
76
  * @param inputGlob A glob pattern that matches one or more Move files
79
77
  * @param opts Additional options.
80
- * @param opts.network Target network (testnet/devnet/mainnet) - optional if NETWORK env var is set.
78
+ * @param opts.network Target network (testnet/devnet/mainnet).
81
79
  * @param opts.output Where we store the final compiled modules.
82
80
  */
83
81
  async function actionCommandBuild(inputGlob, opts) {
84
82
  try {
85
- const network = opts.network ?? process.env.NETWORK;
83
+ const networkRaw = cliCore.CLIParam.stringValue("network", opts.network);
84
+ const network = networkRaw;
86
85
  core.Guards.arrayOneOf("commands", "network", network, Object.values(dltIota.NetworkTypes));
87
86
  // Verify the IOTA SDK before we do anything else
88
87
  await verifyIotaSDK();
89
88
  const { normalizedGlob, normalizedOutput, executionDir } = normalizePathsAndWorkingDir(inputGlob, opts.output ?? "smart-contract-deployments.json");
90
89
  cliCore.CLIDisplay.section(core.I18n.formatMessage("commands.build.section.buildingMoveContracts", {
91
- network: network.toUpperCase()
90
+ network
92
91
  }));
93
92
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.labels.inputGlob"), inputGlob);
94
93
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.labels.outputJson"), normalizedOutput);
95
94
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.labels.network"), network);
96
- cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.labels.platform"), "iota");
97
95
  cliCore.CLIDisplay.break();
98
96
  // Find matching .move files
99
97
  cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.build.progress.searchingFiles"));
@@ -121,7 +119,6 @@ async function actionCommandBuild(inputGlob, opts) {
121
119
  }
122
120
  const existingJson = await cliCore.CLIUtils.readJsonFile(normalizedOutput);
123
121
  const finalJson = existingJson ?? {};
124
- finalJson[network] ??= {};
125
122
  if (existingJson) {
126
123
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.labels.mergingWithExistingJson"), normalizedOutput);
127
124
  }
@@ -134,10 +131,16 @@ async function actionCommandBuild(inputGlob, opts) {
134
131
  try {
135
132
  const compiled = await processMoveFile(moveFile);
136
133
  if (compiled) {
137
- const { contractName, packageId, packageData } = compiled;
138
- const targetNetworkData = finalJson[network];
139
- targetNetworkData.packageId = packageId;
140
- targetNetworkData.packageBytecode = packageData;
134
+ const { contractName, packageId, packageBytecode } = compiled;
135
+ // Capture the last package id before overwriting it
136
+ const lastPackageId = finalJson[network]?.packageId;
137
+ finalJson[network] ??= { packageId, packageBytecode };
138
+ // If the last package id is different we need to clear
139
+ // the deployed package id, otherwise calling deploy will
140
+ // not do anything as it thinks the package is already deployed.
141
+ if (lastPackageId !== packageId) {
142
+ delete finalJson[network].deployedPackageId;
143
+ }
141
144
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.labels.updatedNetworkPackage", { network }), contractName, 2);
142
145
  }
143
146
  }
@@ -153,12 +156,13 @@ async function actionCommandBuild(inputGlob, opts) {
153
156
  }
154
157
  catch (err) {
155
158
  cliCore.CLIDisplay.error(err);
159
+ throw err;
156
160
  }
157
161
  }
158
162
  /**
159
163
  * Process a single Move file by compiling it, computing the packageId, and base64-encoding the .mv modules.
160
164
  * @param moveFile The path to a single Move source file.
161
- * @returns The compiled results or null if no modules found.
165
+ * @returns The compiled results or undefined if no modules found.
162
166
  */
163
167
  async function processMoveFile(moveFile) {
164
168
  // The contract name is based on the .move file's base name in kebab-case
@@ -167,7 +171,6 @@ async function processMoveFile(moveFile) {
167
171
  // Find the "project root" (the directory containing Move.toml).
168
172
  const projectRoot = getProjectRoot(moveFile);
169
173
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.labels.contractName"), contractName, 1);
170
- cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.labels.platform"), "iota", 1);
171
174
  // Compile the contract
172
175
  try {
173
176
  const cliArgs = ["move", "build"];
@@ -177,7 +180,7 @@ async function processMoveFile(moveFile) {
177
180
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.labels.compileResult"), core.I18n.formatMessage("commands.build.labels.buildCompleted"), 1);
178
181
  }
179
182
  catch (error) {
180
- throw new core.GeneralError("commands", "commands.build.buildFailed", { platform: "iota", file: moveFile }, error);
183
+ throw new core.GeneralError("commands", "commands.build.buildFailed", { file: moveFile }, error);
181
184
  }
182
185
  // Get the bytecode modules
183
186
  const buildFolderName = core.StringHelper.snakeCase(baseName);
@@ -187,13 +190,13 @@ async function processMoveFile(moveFile) {
187
190
  }
188
191
  catch {
189
192
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.warnings.noBytecodeModulesFolder", { contractName }), "", 2);
190
- return null;
193
+ return;
191
194
  }
192
195
  const moduleFiles = await node_fs.promises.readdir(bytecodeModulesPath);
193
196
  const mvFiles = moduleFiles.filter(f => f.endsWith(".mv"));
194
197
  if (mvFiles.length === 0) {
195
198
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.build.warnings.noMvFilesFound", { contractName }), "", 2);
196
- return null;
199
+ return;
197
200
  }
198
201
  // Compute the package ID
199
202
  const modulesBytesForHash = [];
@@ -213,7 +216,7 @@ async function processMoveFile(moveFile) {
213
216
  return {
214
217
  contractName,
215
218
  packageId: computedPackageId,
216
- packageData
219
+ packageBytecode: packageData
217
220
  };
218
221
  }
219
222
  /**
@@ -242,66 +245,267 @@ function normalizePathsAndWorkingDir(inputGlob, outputJson) {
242
245
  };
243
246
  }
244
247
 
248
+ // Copyright 2024 IOTA Stiftung.
249
+ // SPDX-License-Identifier: Apache-2.0.
250
+ /**
251
+ * Executes a command with a timeout.
252
+ * @param command The command to execute.
253
+ * @param timeoutMs The timeout in milliseconds.
254
+ * @returns The stdout and stderr of the command
255
+ */
256
+ async function execAsyncWithTimeout(command, timeoutMs) {
257
+ return new Promise((resolve, reject) => {
258
+ const timeoutId = setTimeout(() => {
259
+ reject(new core.GeneralError("environmentUtils", "error.environmentUtils.commandTimeout", {
260
+ command,
261
+ timeout: timeoutMs
262
+ }));
263
+ }, timeoutMs);
264
+ node_child_process.exec(command, { timeout: timeoutMs }, (error, stdout, stderr) => {
265
+ clearTimeout(timeoutId);
266
+ if (error) {
267
+ reject(error);
268
+ }
269
+ else {
270
+ resolve({ stdout, stderr });
271
+ }
272
+ });
273
+ });
274
+ }
275
+ /**
276
+ * Execute a command with input.
277
+ * @param command The command to execute.
278
+ * @param inputs The inputs to provide to the command.
279
+ * @returns A promise that resolves when the command completes.
280
+ */
281
+ async function execWithInput(command, inputs) {
282
+ const child = node_child_process.spawn(command, { shell: true, stdio: ["pipe", "inherit", "inherit"] });
283
+ for (const input of inputs) {
284
+ await new Promise(resolve => setTimeout(resolve, 500));
285
+ child.stdin.write(`${input}\n`);
286
+ }
287
+ child.stdin.end();
288
+ return new Promise((resolve, reject) => {
289
+ const timeout = setTimeout(() => {
290
+ child.kill("SIGTERM");
291
+ reject(new core.GeneralError("environmentUtils", "error.environmentUtils.commandTimeout", {
292
+ command,
293
+ inputs,
294
+ timeout: 5000
295
+ }));
296
+ }, 5000);
297
+ child.on("close", code => {
298
+ clearTimeout(timeout);
299
+ if (code === 0) {
300
+ resolve();
301
+ }
302
+ else {
303
+ reject(new core.GeneralError("environmentUtils", "error.environmentUtils.commandFailedWithCode", {
304
+ command,
305
+ inputs,
306
+ code
307
+ }));
308
+ }
309
+ });
310
+ child.on("error", error => {
311
+ clearTimeout(timeout);
312
+ reject(new core.GeneralError("environmentUtils", "error.environmentUtils.commandExecutionFailed", {
313
+ command,
314
+ inputs,
315
+ error
316
+ }));
317
+ });
318
+ });
319
+ }
320
+ /**
321
+ * Verify that IOTA CLI is installed and available.
322
+ * @throws GeneralError if IOTA CLI is not installed or version check fails.
323
+ */
324
+ async function verifyIotaCliInstalled() {
325
+ try {
326
+ await execAsyncWithTimeout("iota --version", 5000);
327
+ }
328
+ catch (error) {
329
+ if (error.code === 127 ||
330
+ error.code === "ENOENT") {
331
+ throw new core.GeneralError("environmentUtils", "error.environmentUtils.iotaCliNotFound", undefined, error);
332
+ }
333
+ throw new core.GeneralError("environmentUtils", "error.environmentUtils.iotaCliVerificationFailed", undefined, error);
334
+ }
335
+ }
336
+ /**
337
+ * Check if a specific IOTA environment exists.
338
+ * @param network The network alias to check for.
339
+ * @returns Promise<boolean> True if environment exists, false otherwise.
340
+ */
341
+ async function checkEnvironmentExists(network) {
342
+ try {
343
+ const { stdout } = await execAsyncWithTimeout("iota client envs --json", 5000);
344
+ const envData = JSON.parse(stdout);
345
+ // The IOTA CLI returns [environments[], currentEnv] format
346
+ const environments = core.Is.arrayValue(envData) ? envData[0] : envData;
347
+ if (core.Is.arrayValue(environments)) {
348
+ return environments.some(env => env.alias === network);
349
+ }
350
+ return false;
351
+ }
352
+ catch {
353
+ // Silently return false if environment check fails - let calling code handle messaging
354
+ return false;
355
+ }
356
+ }
357
+ /**
358
+ * Create a new IOTA environment.
359
+ * @param network The network alias to create.
360
+ * @param rpcUrl The RPC URL for the network.
361
+ * @returns Promise<void>
362
+ */
363
+ async function createEnvironment(network, rpcUrl) {
364
+ const isWindows = process.platform === "win32";
365
+ try {
366
+ const additionalInfo = isWindows ? "" : " || true";
367
+ await execWithInput(`iota client new-env --alias ${network} --rpc ${rpcUrl}${additionalInfo}`, [
368
+ "0"
369
+ ]);
370
+ await execAsyncWithTimeout(`iota client switch --env ${network}${additionalInfo}`, 5000);
371
+ }
372
+ catch (error) {
373
+ throw new core.GeneralError("environmentUtils", "environmentCreationFailed", { network, rpcUrl }, error);
374
+ }
375
+ }
376
+ /**
377
+ * Create default IOTA environments when none exist.
378
+ * @returns Promise<void>
379
+ */
380
+ async function createDefaultEnvironments() {
381
+ const defaultEnvironments = [
382
+ { alias: "mainnet", rpc: "https://api.mainnet.iota.cafe" },
383
+ { alias: "devnet", rpc: "https://api.devnet.iota.cafe" },
384
+ { alias: "testnet", rpc: "https://api.testnet.iota.cafe" },
385
+ { alias: "localnet", rpc: "http://127.0.0.1:9000" }
386
+ ];
387
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("info.environmentUtils.creatingDefaultEnvironments"), `${defaultEnvironments.length} environments`, 1);
388
+ for (const env of defaultEnvironments) {
389
+ try {
390
+ await createEnvironment(env.alias, env.rpc);
391
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("info.environmentUtils.createdEnvironment"), env.alias, 2);
392
+ }
393
+ catch {
394
+ // Continue creating other environments even if one fails
395
+ }
396
+ }
397
+ // Set testnet as active by default
398
+ const isWindows = process.platform === "win32";
399
+ const additionalInfo = isWindows ? "" : " || true";
400
+ try {
401
+ await execAsyncWithTimeout(`iota client switch --env testnet${additionalInfo}`, 5000);
402
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("info.environmentUtils.activeEnvironment"), "testnet", 1);
403
+ }
404
+ catch {
405
+ // Ignore switch failures - testnet might not exist yet
406
+ }
407
+ }
408
+ /**
409
+ * Check if any IOTA environments exist by attempting to list them.
410
+ * If the command times out, it likely means no environments are configured.
411
+ * @returns Promise<boolean> True if any environments exist, false otherwise.
412
+ */
413
+ async function checkAnyEnvironmentsExist() {
414
+ try {
415
+ const { stdout } = await execAsyncWithTimeout("iota client envs --json", 5000);
416
+ const envData = JSON.parse(stdout);
417
+ const environments = core.Is.arrayValue(envData) ? envData[0] : envData;
418
+ return core.Is.arrayValue(environments);
419
+ }
420
+ catch {
421
+ // Timeout or error likely means no environments exist
422
+ return false;
423
+ }
424
+ }
425
+ /**
426
+ * Ensure that a specific IOTA environment exists, creating it if necessary.
427
+ * If no environments exist at all, creates default environments first.
428
+ * @param network The network alias to ensure exists.
429
+ * @param rpcUrl The RPC URL for the network.
430
+ * @returns Promise<void>
431
+ */
432
+ async function ensureEnvironment(network, rpcUrl) {
433
+ // First verify IOTA CLI is installed
434
+ await verifyIotaCliInstalled();
435
+ // Check if any environments exist at all
436
+ const anyEnvironmentsExist = await checkAnyEnvironmentsExist();
437
+ if (!anyEnvironmentsExist) {
438
+ // No environments exist - create defaults
439
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("info.environmentUtils.noEnvironments"), core.I18n.formatMessage("info.environmentUtils.creatingDefaults"), 1);
440
+ await createDefaultEnvironments();
441
+ }
442
+ // Now check if the specific environment exists
443
+ const exists = await checkEnvironmentExists(network);
444
+ if (!exists) {
445
+ // Use CLI framework for user-facing message
446
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("info.environmentUtils.creatingEnvironment"), network, 1);
447
+ await createEnvironment(network, rpcUrl);
448
+ }
449
+ else {
450
+ // Switch to the environment to make sure it's active
451
+ const isWindows = process.platform === "win32";
452
+ const additionalInfo = isWindows ? "" : " || true";
453
+ await execAsyncWithTimeout(`iota client switch --env ${network}${additionalInfo}`, 5000);
454
+ }
455
+ }
456
+
245
457
  // Copyright 2024 IOTA Stiftung.
246
458
  // SPDX-License-Identifier: Apache-2.0.
247
459
  /**
248
460
  * Validate that required environment variables are set for deployment.
249
461
  * @param network The target network.
462
+ * @param deployerMnemonic The deployer mnemonic to validate.
250
463
  * @throws GeneralError if required environment variables are missing.
251
464
  */
252
- async function validateDeploymentEnvironment(network) {
253
- await getDeploymentMnemonic(network);
465
+ async function validateDeploymentEnvironment(network, deployerMnemonic) {
466
+ await getDeploymentMnemonic(network, deployerMnemonic);
254
467
  }
255
468
  /**
256
469
  * Get the deployment mnemonic for a network.
257
470
  * @param network The target network.
471
+ * @param deployerMnemonic The deployer mnemonic from environment variables.
258
472
  * @returns The mnemonic string.
259
473
  * @throws GeneralError if mnemonic is not found or invalid.
260
474
  */
261
- async function getDeploymentMnemonic(network) {
262
- const mnemonic = process.env.DEPLOYER_MNEMONIC;
263
- if (!mnemonic) {
475
+ async function getDeploymentMnemonic(network, deployerMnemonic) {
476
+ if (!core.Is.stringValue(deployerMnemonic)) {
264
477
  throw new core.GeneralError("envSetup", "mnemonicMissing", {
265
478
  network,
266
479
  mnemonicVar: "DEPLOYER_MNEMONIC"
267
480
  });
268
481
  }
269
- // Validate mnemonic format (should be 24 words)
270
- const words = mnemonic.trim().split(/\s+/);
271
- if (words.length !== 24) {
482
+ // Validate mnemonic format using Bip39 validation
483
+ if (!crypto.Bip39.validateMnemonic(deployerMnemonic)) {
272
484
  throw new core.GeneralError("envSetup", "mnemonicInvalidFormat", {
273
485
  network,
274
- mnemonicVar: "DEPLOYER_MNEMONIC",
275
- wordCount: words.length
486
+ mnemonicVar: "DEPLOYER_MNEMONIC"
276
487
  });
277
488
  }
278
- return mnemonic;
489
+ return deployerMnemonic;
279
490
  }
280
491
  /**
281
492
  * Get the deployment seed for a network (if available).
282
493
  * @param network The target network.
494
+ * @param deployerSeed The deployer seed from environment variables (optional).
283
495
  * @returns The seed string or undefined if not set.
284
496
  * @throws GeneralError if seed is not found or invalid.
285
497
  */
286
- async function getDeploymentSeed(network) {
287
- const seed = process.env.DEPLOYER_SEED;
288
- if (!core.Is.stringValue(seed)) {
498
+ async function getDeploymentSeed(network, deployerSeed) {
499
+ if (!core.Is.stringValue(deployerSeed)) {
289
500
  return undefined;
290
501
  }
291
- if (!core.Is.stringHex(seed, true)) {
502
+ if (!core.Is.stringHexLength(deployerSeed, 64, true)) {
292
503
  throw new core.GeneralError("envSetup", "seedInvalidFormat", {
293
504
  network,
294
505
  seedVar: "DEPLOYER_SEED"
295
506
  });
296
507
  }
297
- if (seed.length < 66) {
298
- // 0x + 64 hex characters
299
- throw new core.GeneralError("envSetup", "seedInvalidFormat", {
300
- network,
301
- seedVar: "DEPLOYER_SEED"
302
- });
303
- }
304
- return seed;
508
+ return deployerSeed;
305
509
  }
306
510
 
307
511
  // Copyright 2024 IOTA Stiftung.
@@ -316,40 +520,134 @@ function buildCommandDeploy(program) {
316
520
  .command("deploy")
317
521
  .description(core.I18n.formatMessage("commands.deploy.description"))
318
522
  .option(core.I18n.formatMessage("commands.deploy.options.contracts.param"), core.I18n.formatMessage("commands.deploy.options.contracts.description"), "smart-contract-deployments.json")
319
- .option(core.I18n.formatMessage("commands.deploy.options.network.param"), core.I18n.formatMessage("commands.deploy.options.network.description"))
523
+ .option(core.I18n.formatMessage("commands.deploy.options.network.param"), core.I18n.formatMessage("commands.deploy.options.network.description"), "!NETWORK")
320
524
  .option(core.I18n.formatMessage("commands.deploy.options.dryRun.param"), core.I18n.formatMessage("commands.deploy.options.dryRun.description"))
321
525
  .option(core.I18n.formatMessage("commands.deploy.options.force.param"), core.I18n.formatMessage("commands.deploy.options.force.description"))
526
+ .option(core.I18n.formatMessage("commands.deploy.options.rpcUrl.param"), core.I18n.formatMessage("commands.deploy.options.rpcUrl.description"), "!RPC_URL")
527
+ .option(core.I18n.formatMessage("commands.deploy.options.addressIndex.param"), core.I18n.formatMessage("commands.deploy.options.addressIndex.description"), "!ADDRESS_INDEX")
528
+ .option(core.I18n.formatMessage("commands.deploy.options.rpcTimeout.param"), core.I18n.formatMessage("commands.deploy.options.rpcTimeout.description"), "!RPC_TIMEOUT")
529
+ .option(core.I18n.formatMessage("commands.deploy.options.gasBudget.param"), core.I18n.formatMessage("commands.deploy.options.gasBudget.description"), "!GAS_BUDGET")
530
+ .option(core.I18n.formatMessage("commands.deploy.options.confirmationTimeout.param"), core.I18n.formatMessage("commands.deploy.options.confirmationTimeout.description"), "!CONFIRMATION_TIMEOUT")
531
+ .option(core.I18n.formatMessage("commands.deploy.options.faucetUrl.param"), core.I18n.formatMessage("commands.deploy.options.faucetUrl.description"), "!FAUCET_URL")
532
+ .option(core.I18n.formatMessage("commands.deploy.options.deployerMnemonic.param"), core.I18n.formatMessage("commands.deploy.options.deployerMnemonic.description"), "!DEPLOYER_MNEMONIC")
533
+ .option(core.I18n.formatMessage("commands.deploy.options.deployerSeed.param"), core.I18n.formatMessage("commands.deploy.options.deployerSeed.description"), "!DEPLOYER_SEED")
322
534
  .action(actionCommandDeploy);
323
535
  }
536
+ /**
537
+ * Action for the deploy command.
538
+ * @param opts Command options.
539
+ * @param opts.contracts Path to compiled modules JSON.
540
+ * @param opts.network Network identifier - optional if NETWORK env var is set.
541
+ * @param opts.dryRun Simulate deployment without executing.
542
+ * @param opts.force Force redeployment of existing packages.
543
+ * @param opts.rpcUrl RPC endpoint URL for the network.
544
+ * @param opts.addressIndex Address index for key derivation.
545
+ * @param opts.rpcTimeout RPC request timeout in milliseconds.
546
+ * @param opts.gasBudget Gas budget for transactions.
547
+ * @param opts.confirmationTimeout Transaction confirmation timeout in milliseconds.
548
+ * @param opts.faucetUrl Faucet URL for requesting test tokens.
549
+ * @param opts.deployerMnemonic Deployer wallet mnemonic phrase.
550
+ * @param opts.deployerSeed Deployer wallet seed (alternative to mnemonic).
551
+ */
552
+ async function actionCommandDeploy(opts) {
553
+ try {
554
+ const contractsPath = opts.contracts ?? "smart-contract-deployments.json";
555
+ const dryRun = opts.dryRun ?? false;
556
+ const force = opts.force ?? false;
557
+ cliCore.CLIDisplay.section(core.I18n.formatMessage("commands.deploy.section.deployContracts"));
558
+ cliCore.CLIDisplay.section(contractsPath);
559
+ const networkRaw = cliCore.CLIParam.stringValue("network", opts.network);
560
+ const network = networkRaw;
561
+ core.Guards.arrayOneOf("commands", "network", network, Object.values(dltIota.NetworkTypes));
562
+ // Verify the IOTA SDK before we do anything else
563
+ await verifyIotaSDK();
564
+ // Get configuration values needed for environment setup
565
+ const rpcUrl = cliCore.CLIParam.stringValue("rpcUrl", opts.rpcUrl);
566
+ const addressIndex = cliCore.CLIParam.number("addressIndex", opts.addressIndex) ?? 0;
567
+ const rpcTimeout = cliCore.CLIParam.number("rpcTimeout", opts.rpcTimeout);
568
+ const gasBudget = cliCore.CLIParam.number("gasBudget", opts.gasBudget);
569
+ const confirmationTimeout = cliCore.CLIParam.number("confirmationTimeout", opts.confirmationTimeout);
570
+ const faucetUrl = network === dltIota.NetworkTypes.Mainnet
571
+ ? undefined
572
+ : cliCore.CLIParam.stringValue("faucetUrl", opts.faucetUrl);
573
+ let deployerMnemonic;
574
+ try {
575
+ deployerMnemonic = cliCore.CLIParam.stringValue("deployerMnemonic", opts.deployerMnemonic);
576
+ }
577
+ catch {
578
+ // Optional parameter, can be undefined
579
+ deployerMnemonic = undefined;
580
+ }
581
+ let deployerSeed;
582
+ try {
583
+ deployerSeed = cliCore.CLIParam.stringValue("deployerSeed", opts.deployerSeed);
584
+ }
585
+ catch {
586
+ // Optional parameter, can be undefined
587
+ deployerSeed = undefined;
588
+ }
589
+ // Validate that at least one deployer credential is provided
590
+ const hasValidMnemonic = core.Is.stringValue(deployerMnemonic);
591
+ const hasValidSeed = core.Is.stringValue(deployerSeed);
592
+ if (!hasValidMnemonic && !hasValidSeed) {
593
+ throw new core.GeneralError("commands", "commands.deploy.deployerCredentialRequired", {
594
+ network
595
+ });
596
+ }
597
+ // Check/switch to target network environment BEFORE loading config
598
+ await setIotaEnvironment(network, rpcUrl, addressIndex, dryRun, deployerMnemonic, deployerSeed);
599
+ const config = await createNetworkConfig(network, rpcUrl, addressIndex, rpcTimeout, gasBudget, confirmationTimeout);
600
+ validateNetworkConfig(config, network);
601
+ const contractsData = await loadCompiledContracts(contractsPath);
602
+ if (network === dltIota.NetworkTypes.Mainnet) {
603
+ const validatedMnemonic = await getDeploymentMnemonic(network, hasValidMnemonic ? deployerMnemonic : undefined);
604
+ await validateDeploymentEnvironment(network, validatedMnemonic);
605
+ }
606
+ const networkContracts = contractsData[network];
607
+ if (!core.Is.object(networkContracts)) {
608
+ throw new core.GeneralError("commands", "commands.deploy.noContractsFound", {
609
+ network,
610
+ contractsPath
611
+ });
612
+ }
613
+ await deployContract("contract", networkContracts, config, network, dryRun, force, faucetUrl, deployerMnemonic, deployerSeed);
614
+ if (!dryRun) {
615
+ await updateContractsFile(contractsPath, contractsData);
616
+ }
617
+ cliCore.CLIDisplay.done();
618
+ }
619
+ catch (err) {
620
+ cliCore.CLIDisplay.error(err);
621
+ throw err;
622
+ }
623
+ }
324
624
  /**
325
625
  * Switch IOTA CLI to the target network environment and set the active address.
326
626
  * @param network Target network to switch to
627
+ * @param rpcUrl The RPC URL for the network
628
+ * @param addressIndex The address index to derive the target address
327
629
  * @param dryRun Whether this is a dry run (checks environment but doesn't switch)
630
+ * @param deployerMnemonic The deployer mnemonic from environment variables.
631
+ * @param deployerSeed The deployer seed from environment variables (optional).
328
632
  */
329
- async function setIotaEnvironment(network, dryRun = false) {
633
+ async function setIotaEnvironment(network, rpcUrl, addressIndex, dryRun = false, deployerMnemonic, deployerSeed) {
330
634
  try {
331
635
  cliCore.CLIDisplay.task(dryRun
332
636
  ? core.I18n.formatMessage("commands.deploy.progress.checkingEnvironment")
333
637
  : core.I18n.formatMessage("commands.deploy.progress.settingEnvironment"));
334
- // Check if the environment exists
335
- const { stdout: envListOutput } = await execAsync("iota client envs");
336
- if (!envListOutput.includes(network)) {
337
- throw new core.GeneralError("commands", "commands.deploy.environmentNotFound", {
338
- network,
339
- availableEnvironments: envListOutput,
340
- setupCommand: `iota client new-env --alias ${network} --rpc <RPC_URL>`
341
- });
342
- }
638
+ // Ensure environment exists, create if necessary
639
+ core.Guards.stringValue("setIotaEnvironment", "rpcUrl", rpcUrl);
640
+ await ensureEnvironment(network, rpcUrl);
343
641
  if (dryRun) {
344
642
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.iotaEnvironmentCheck"), `✅ ${network} environment exists`, 1);
345
643
  return;
346
644
  }
347
645
  // Derive the target address from existing mnemonic/seed
348
- const addressIndex = core.Coerce.number(process.env.ADDRESS_INDEX) ?? 0;
349
- const targetAddress = await getDeploymentWalletAddress(network, addressIndex);
646
+ const targetAddress = await getDeploymentWalletAddress(network, addressIndex, deployerMnemonic, deployerSeed);
350
647
  // Ensure the correct deployer key exists in the keystore
351
648
  const aliasName = `deployer-${network}`;
352
- await ensureCorrectDeployerKey(network, aliasName, targetAddress, addressIndex);
649
+ const validatedMnemonic = await getDeploymentMnemonic(network, deployerMnemonic);
650
+ await ensureCorrectDeployerKey(network, aliasName, targetAddress, addressIndex, validatedMnemonic);
353
651
  // Switch both environment and address in one command
354
652
  await execAsync(`iota client switch --env ${network} --address ${targetAddress}`);
355
653
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.switchedIotaEnvironment"), network, 1);
@@ -377,76 +675,31 @@ async function setIotaEnvironment(network, dryRun = false) {
377
675
  throw new core.GeneralError("commands", "commands.deploy.environmentOperationFailed", { network, operation: dryRun ? "check" : "switch to" }, error);
378
676
  }
379
677
  }
380
- /**
381
- * Action for the deploy command.
382
- * @param opts Command options.
383
- * @param opts.contracts Path to compiled modules JSON.
384
- * @param opts.network Network identifier - optional if NETWORK env var is set.
385
- * @param opts.dryRun Simulate deployment without executing.
386
- * @param opts.force Force redeployment of existing packages.
387
- */
388
- async function actionCommandDeploy(opts) {
389
- cliCore.CLIDisplay.section(core.I18n.formatMessage("commands.deploy.section.deployContracts"));
390
- cliCore.CLIDisplay.section(opts.contracts ?? "smart-contract-deployments.json");
391
- try {
392
- const contractsPath = opts.contracts ?? "smart-contract-deployments.json";
393
- const dryRun = opts.dryRun ?? false;
394
- const force = opts.force ?? false;
395
- const network = opts.network ?? process.env.NETWORK;
396
- core.Guards.arrayOneOf("commands", "network", network, Object.values(dltIota.NetworkTypes));
397
- // Verify the IOTA SDK before we do anything else
398
- await verifyIotaSDK();
399
- // Check/switch to target network environment BEFORE loading config
400
- await setIotaEnvironment(network, dryRun);
401
- const config = await createNetworkConfig(network);
402
- validateNetworkConfig(config, network);
403
- const contractsData = await loadCompiledContracts(contractsPath);
404
- if (network === dltIota.NetworkTypes.Mainnet) {
405
- await validateDeploymentEnvironment(network);
406
- }
407
- const networkContracts = contractsData[network];
408
- if (!core.Is.object(networkContracts)) {
409
- throw new core.GeneralError("commands", "commands.deploy.noContractsFound", {
410
- network,
411
- contractsPath
412
- });
413
- }
414
- await deployContract("contract", networkContracts, config, network, dryRun, force);
415
- if (!dryRun) {
416
- await updateContractsFile(contractsPath, contractsData);
417
- }
418
- cliCore.CLIDisplay.done();
419
- }
420
- catch (err) {
421
- cliCore.CLIDisplay.error(err);
422
- throw err;
423
- }
424
- }
425
678
  /**
426
679
  * Creates the network configuration.
427
680
  * @param network Target network to determine which env file to load.
681
+ * @param rpcUrl The RPC URL for the network.
682
+ * @param addressIndex The address index for the wallet.
683
+ * @param rpcTimeout The RPC timeout in milliseconds.
684
+ * @param gasBudget The gas budget for deployment.
685
+ * @param confirmationTimeout The confirmation timeout in seconds.
428
686
  * @returns Network configuration.
429
687
  */
430
- async function createNetworkConfig(network) {
688
+ async function createNetworkConfig(network, rpcUrl, addressIndex, rpcTimeout, gasBudget, confirmationTimeout) {
431
689
  try {
432
- const rpcUrl = core.Coerce.string(process.env.RPC_URL);
433
- if (!core.Is.stringValue(rpcUrl)) {
434
- throw new core.GeneralError("commands", "commands.deploy.rpcUrlRequired", {
435
- network
436
- });
437
- }
690
+ core.Guards.stringValue("createNetworkConfig", "rpcUrl", rpcUrl);
438
691
  const config = {
439
692
  network,
440
693
  platform: "iota",
441
694
  rpc: {
442
695
  url: rpcUrl,
443
- timeout: core.Coerce.number(process.env.RPC_TIMEOUT) || 60000
696
+ timeout: rpcTimeout ?? 60000
444
697
  },
445
698
  deployment: {
446
- gasBudget: core.Coerce.number(process.env.GAS_BUDGET) || 50000000,
447
- confirmationTimeout: core.Coerce.number(process.env.CONFIRMATION_TIMEOUT) || 60,
699
+ gasBudget: gasBudget ?? 50000000,
700
+ confirmationTimeout: confirmationTimeout ?? 60,
448
701
  wallet: {
449
- addressIndex: core.Coerce.number(process.env.ADDRESS_INDEX) || 0
702
+ addressIndex
450
703
  }
451
704
  }
452
705
  };
@@ -508,10 +761,13 @@ async function loadCompiledContracts(contractsPath) {
508
761
  * @param network Target network.
509
762
  * @param config Network configuration.
510
763
  * @param isDryRun Whether this is a dry run.
764
+ * @param faucetUrl The faucet URL (optional, defaults to network-specific URL).
765
+ * @param deployerMnemonic The deployer mnemonic for validation.
766
+ * @param deployerSeed The deployer seed (optional).
511
767
  * @returns Wallet address for the deployment.
512
768
  */
513
- async function validateEnvironmentForNetwork(network, config, isDryRun = false) {
514
- const walletAddress = await getDeploymentWalletAddress(network, config.deployment.wallet.addressIndex);
769
+ async function validateEnvironmentForNetwork(network, config, isDryRun = false, faucetUrl, deployerMnemonic, deployerSeed) {
770
+ const walletAddress = await getDeploymentWalletAddress(network, config.deployment.wallet.addressIndex, deployerMnemonic, deployerSeed);
515
771
  if (isDryRun) {
516
772
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.dryRunWalletAddress"), walletAddress, 1);
517
773
  }
@@ -519,11 +775,12 @@ async function validateEnvironmentForNetwork(network, config, isDryRun = false)
519
775
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.walletAddress"), walletAddress, 1);
520
776
  }
521
777
  if (network === dltIota.NetworkTypes.Mainnet) {
522
- await validateDeploymentEnvironment(network);
778
+ const validatedMnemonic = await getDeploymentMnemonic(network, deployerMnemonic);
779
+ await validateDeploymentEnvironment(network, validatedMnemonic);
523
780
  }
524
781
  else if ((network === dltIota.NetworkTypes.Testnet || network === dltIota.NetworkTypes.Devnet) && !isDryRun) {
525
782
  // For testnet/devnet, check balance first and only request funds if needed
526
- await checkBalanceAndRequestFaucetIfNeeded(network, config, walletAddress);
783
+ await checkBalanceAndRequestFaucetIfNeeded(network, config, walletAddress, faucetUrl);
527
784
  }
528
785
  return walletAddress;
529
786
  }
@@ -583,13 +840,16 @@ async function checkWalletBalance(network, config, walletAddress, isDryRun = fal
583
840
  * @param contractData Contract data.
584
841
  * @param config Network configuration.
585
842
  * @param network Target network.
843
+ * @param faucetUrl The faucet URL (optional, defaults to network-specific URL).
844
+ * @param deployerMnemonic The deployer mnemonic from environment variables.
845
+ * @param deployerSeed The deployer seed from environment variables (optional).
586
846
  */
587
- async function handleDryRunValidation(contractName, contractData, config, network) {
847
+ async function handleDryRunValidation(contractName, contractData, config, network, faucetUrl, deployerMnemonic, deployerSeed) {
588
848
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.dryRunWouldDeploy"), `${contractName} (${network})`, 1);
589
849
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.dryRunPackageId"), contractData.packageId, 1);
590
850
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.dryRunRpcUrl"), config.rpc.url, 1);
591
851
  try {
592
- const walletAddress = await validateEnvironmentForNetwork(network, config, true);
852
+ const walletAddress = await validateEnvironmentForNetwork(network, config, true, faucetUrl, deployerMnemonic, deployerSeed);
593
853
  await checkWalletBalance(network, config, walletAddress, true);
594
854
  }
595
855
  catch (err) {
@@ -604,10 +864,13 @@ async function handleDryRunValidation(contractName, contractData, config, networ
604
864
  * @param contractData Contract data.
605
865
  * @param config Network configuration.
606
866
  * @param network Target network.
867
+ * @param faucetUrl The faucet URL (optional, defaults to network-specific URL).
868
+ * @param deployerMnemonic The deployer mnemonic from environment variables.
869
+ * @param deployerSeed The deployer seed from environment variables (optional).
607
870
  */
608
- async function handleActualDeployment(contractName, contractData, config, network) {
871
+ async function handleActualDeployment(contractName, contractData, config, network, faucetUrl, deployerMnemonic, deployerSeed) {
609
872
  try {
610
- const walletAddress = await validateEnvironmentForNetwork(network, config, false);
873
+ const walletAddress = await validateEnvironmentForNetwork(network, config, false, faucetUrl, deployerMnemonic, deployerSeed);
611
874
  await checkWalletBalance(network, config, walletAddress, false);
612
875
  const deploymentResult = await deployWithIotaCli(config.deployment.gasBudget);
613
876
  contractData.deployedPackageId = deploymentResult.packageId;
@@ -638,34 +901,39 @@ async function handleActualDeployment(contractName, contractData, config, networ
638
901
  * @param network Target network
639
902
  * @param dryRun Whether this is a dry run
640
903
  * @param force Whether to force redeployment
904
+ * @param faucetUrl The faucet URL (optional, defaults to network-specific URL).
905
+ * @param deployerMnemonic The deployer mnemonic from environment variables.
906
+ * @param deployerSeed The deployer seed from environment variables (optional).
641
907
  */
642
- async function deployContract(contractName, contractData, config, network, dryRun, force) {
908
+ async function deployContract(contractName, contractData, config, network, dryRun, force, faucetUrl, deployerMnemonic, deployerSeed) {
643
909
  cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.deploy.progress.deployingContract", { contractName, network }));
644
910
  if (core.Is.stringValue(contractData.deployedPackageId) && !force) {
645
911
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.contractAlreadyDeployed"), contractData.deployedPackageId, 1);
646
912
  return;
647
913
  }
648
914
  if (dryRun) {
649
- await handleDryRunValidation(contractName, contractData, config, network);
915
+ await handleDryRunValidation(contractName, contractData, config, network, faucetUrl, deployerMnemonic, deployerSeed);
650
916
  return;
651
917
  }
652
- await handleActualDeployment(contractName, contractData, config, network);
918
+ await handleActualDeployment(contractName, contractData, config, network, faucetUrl, deployerMnemonic, deployerSeed);
653
919
  }
654
920
  /**
655
921
  * Get wallet address for deployment, preferring seed over mnemonic if available.
656
922
  * @param network The target network.
657
923
  * @param addressIndex The address index to derive.
924
+ * @param deployerMnemonic The deployer mnemonic from environment variables.
925
+ * @param deployerSeed The deployer seed from environment variables (optional).
658
926
  * @returns The wallet address.
659
927
  */
660
- async function getDeploymentWalletAddress(network, addressIndex) {
928
+ async function getDeploymentWalletAddress(network, addressIndex, deployerMnemonic, deployerSeed) {
661
929
  // Try to use seed first if available
662
- const hexSeed = await getDeploymentSeed(network);
930
+ const hexSeed = await getDeploymentSeed(network, deployerSeed);
663
931
  let seed;
664
932
  if (core.Is.stringValue(hexSeed)) {
665
933
  seed = core.Converter.hexToBytes(hexSeed);
666
934
  }
667
935
  else {
668
- const mnemonic = await getDeploymentMnemonic(network);
936
+ const mnemonic = await getDeploymentMnemonic(network, deployerMnemonic);
669
937
  seed = crypto.Bip39.mnemonicToSeed(mnemonic);
670
938
  }
671
939
  const addresses = dltIota.Iota.getAddresses(seed, dltIota.Iota.DEFAULT_COIN_TYPE, 0, addressIndex, 1, false);
@@ -683,14 +951,12 @@ function nanosToIota(nanos) {
683
951
  * Request funds from the faucet for testnet or devnet deployment.
684
952
  * @param network The target network (testnet or devnet).
685
953
  * @param walletAddress The wallet address to fund.
954
+ * @param rpcUrl The RPC URL for the network.
955
+ * @param faucetUrl The faucet URL (optional, defaults to network-specific URL).
686
956
  * @returns Promise that resolves when funding is complete.
687
957
  */
688
- async function requestFaucetFunds(network, walletAddress) {
689
- if (network !== dltIota.NetworkTypes.Testnet && network !== dltIota.NetworkTypes.Devnet) {
690
- return;
691
- }
958
+ async function requestFaucetFunds(network, walletAddress, rpcUrl, faucetUrl) {
692
959
  cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.deploy.progress.requestingFaucetFunds", { network }));
693
- const faucetUrl = process.env.FAUCET_URL ?? `https://faucet.${network}.iota.cafe`;
694
960
  const response = await faucet.requestIotaFromFaucetV0({
695
961
  host: faucetUrl,
696
962
  recipient: walletAddress
@@ -698,7 +964,7 @@ async function requestFaucetFunds(network, walletAddress) {
698
964
  if (response?.error) {
699
965
  throw new core.GeneralError("commands", "commands.deploy.fundingFailed", undefined, response.error);
700
966
  }
701
- const client$1 = new client.IotaClient({ url: process.env.RPC_URL ?? "" });
967
+ const client$1 = new client.IotaClient({ url: rpcUrl });
702
968
  const balanceResponse = await client$1.getBalance({ owner: walletAddress });
703
969
  const balanceInNanos = Number(balanceResponse.totalBalance);
704
970
  if (balanceInNanos > 0) {
@@ -714,12 +980,10 @@ async function requestFaucetFunds(network, walletAddress) {
714
980
  * @param network The target network (testnet or devnet).
715
981
  * @param config Network configuration.
716
982
  * @param walletAddress The wallet address to check and potentially fund.
983
+ * @param faucetUrl The faucet URL (optional, defaults to network-specific URL).
717
984
  * @returns Promise that resolves when balance check and optional funding is complete.
718
985
  */
719
- async function checkBalanceAndRequestFaucetIfNeeded(network, config, walletAddress) {
720
- if (network !== dltIota.NetworkTypes.Testnet && network !== dltIota.NetworkTypes.Devnet) {
721
- return;
722
- }
986
+ async function checkBalanceAndRequestFaucetIfNeeded(network, config, walletAddress, faucetUrl) {
723
987
  // Check current balance
724
988
  const client$1 = new client.IotaClient({ url: config.rpc.url });
725
989
  const balanceResponse = await client$1.getBalance({ owner: walletAddress });
@@ -736,8 +1000,11 @@ async function checkBalanceAndRequestFaucetIfNeeded(network, config, walletAddre
736
1000
  currentBalance: balanceInIota.toFixed(2),
737
1001
  requiredBalance: requiredInIota.toFixed(2)
738
1002
  }), 1);
1003
+ if (!core.Is.stringValue(faucetUrl)) {
1004
+ throw new core.GeneralError("commands", "error.commands.deploy.noFaucetConfigured");
1005
+ }
739
1006
  cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.deploy.progress.requestingAdditionalFaucetFunds"));
740
- await requestFaucetFunds(network, walletAddress);
1007
+ await requestFaucetFunds(network, walletAddress, config.rpc.url, faucetUrl);
741
1008
  // Check balance again after faucet request
742
1009
  const updatedBalanceResponse = await client$1.getBalance({ owner: walletAddress });
743
1010
  const updatedBalanceInNanos = Number(updatedBalanceResponse.totalBalance);
@@ -829,8 +1096,9 @@ function generateUniqueBackupAlias(baseAlias, existingKeys) {
829
1096
  * @param aliasName The desired alias name (e.g., "deployer-testnet").
830
1097
  * @param expectedAddress The expected address from the current mnemonic.
831
1098
  * @param addressIndex The address index to use.
1099
+ * @param deployerMnemonic The deployer mnemonic.
832
1100
  */
833
- async function ensureCorrectDeployerKey(network, aliasName, expectedAddress, addressIndex) {
1101
+ async function ensureCorrectDeployerKey(network, aliasName, expectedAddress, addressIndex, deployerMnemonic) {
834
1102
  try {
835
1103
  // Check if the alias already exists in keystore
836
1104
  const { stdout: keysListOutput } = await execAsync("iota keytool list --json");
@@ -847,7 +1115,7 @@ async function ensureCorrectDeployerKey(network, aliasName, expectedAddress, add
847
1115
  await execAsync(`iota keytool update-alias "${aliasName}" "${backupAlias}"`);
848
1116
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.renamedExistingKey"), `${aliasName} → ${backupAlias} (${existingAddress})`, 1);
849
1117
  // Now import the correct key with the desired alias
850
- await importCorrectDeployerKey(network, aliasName, addressIndex, expectedAddress);
1118
+ await importCorrectDeployerKey(network, aliasName, addressIndex, expectedAddress, deployerMnemonic);
851
1119
  }
852
1120
  else {
853
1121
  // Existing key is correct - no action needed
@@ -856,7 +1124,7 @@ async function ensureCorrectDeployerKey(network, aliasName, expectedAddress, add
856
1124
  }
857
1125
  else {
858
1126
  // No existing alias - import the key
859
- await importCorrectDeployerKey(network, aliasName, addressIndex, expectedAddress);
1127
+ await importCorrectDeployerKey(network, aliasName, addressIndex, expectedAddress, deployerMnemonic);
860
1128
  }
861
1129
  // Verify the address exists in client addresses
862
1130
  const { stdout: addressListOutput } = await execAsync("iota client addresses --json");
@@ -879,10 +1147,11 @@ async function ensureCorrectDeployerKey(network, aliasName, expectedAddress, add
879
1147
  * @param aliasName The alias name to use.
880
1148
  * @param addressIndex The address index.
881
1149
  * @param targetAddress The expected target address (avoids redundant calculation).
1150
+ * @param deployerMnemonic The deployer mnemonic.
882
1151
  */
883
- async function importCorrectDeployerKey(network, aliasName, addressIndex, targetAddress) {
1152
+ async function importCorrectDeployerKey(network, aliasName, addressIndex, targetAddress, deployerMnemonic) {
884
1153
  cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.deploy.progress.importingDeployerKey", { aliasName }));
885
- const mnemonic = await getDeploymentMnemonic(network);
1154
+ const mnemonic = await getDeploymentMnemonic(network, deployerMnemonic);
886
1155
  const derivationPath = crypto.Bip44.path(dltIota.Iota.DEFAULT_COIN_TYPE, 0, false, addressIndex).toString();
887
1156
  await execAsync(`iota keytool import "${mnemonic}" ed25519 "${derivationPath}" --alias "${aliasName}"`);
888
1157
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.importedDeployerKey"), `${aliasName} (${targetAddress})`, 1);
@@ -906,7 +1175,7 @@ class CLI extends cliCore.CLIBase {
906
1175
  return this.execute({
907
1176
  title: "TWIN Move to JSON",
908
1177
  appName: "move-to-json",
909
- version: "0.0.2-next.6", // x-release-please-version
1178
+ version: "0.0.2-next.8", // x-release-please-version
910
1179
  icon: "⚙️ ",
911
1180
  supportsEnvFiles: true,
912
1181
  overrideOutputWidth: options?.overrideOutputWidth