@twin.org/move-to-json 0.0.2-next.3 → 0.0.2-next.5

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/README.md CHANGED
@@ -335,3 +335,58 @@ iota client call --package 0x2 --module package --function upgrade \
335
335
  --args 0xfd6269c28e3931e41aa9d9e08ffabb8162cf1fd0baaef14094b4442e6c743edf \
336
336
  --gas-budget 50000000
337
337
  ```
338
+
339
+ ## Known Issues
340
+
341
+ ### Windows
342
+
343
+ #### Long Path Limitation Error
344
+
345
+ **Error Symptoms:**
346
+
347
+ - Build commands fail with `Failed to reset to latest Git state 'mainnet'`
348
+ - Tests timeout during Move compilation
349
+ - Git dependency resolution failures
350
+
351
+ **Root Cause:**
352
+ Windows has a default file path length limitation of 260 characters. The IOTA framework repository contains files with very long paths (especially in test directories) that exceed this limit, causing Git operations to fail during dependency resolution.
353
+
354
+ **Solution:**
355
+ Enable long path support in both Git and Windows:
356
+
357
+ 1. **Enable Git Long Paths:**
358
+
359
+ ```bash
360
+ git config --global core.longpaths true
361
+ ```
362
+
363
+ 2. **Enable Windows Long Path Support (requires Administrator privileges):**
364
+
365
+ ```powershell
366
+ # Run PowerShell as Administrator
367
+ Set-ItemProperty -Path 'HKLM:SYSTEM\CurrentControlSet\Control\FileSystem' -Name 'LongPathsEnabled' -Value 1
368
+ ```
369
+
370
+ 3. **Restart your computer** for Windows registry changes to take effect.
371
+
372
+ **Verification:**
373
+ After applying the fix, you should see successful Git dependency updates:
374
+
375
+ ```bash
376
+ UPDATING GIT DEPENDENCY https://github.com/iotaledger/iota.git
377
+ INCLUDING DEPENDENCY Iota
378
+ INCLUDING DEPENDENCY MoveStdlib
379
+ BUILDING nft
380
+ ```
381
+
382
+ **Alternative Workaround:**
383
+ If you cannot enable long path support system-wide, you can use the `--skip-fetch-latest-git-deps` flag as a workaround, though this will use cached dependencies instead of the latest versions:
384
+
385
+ ```bash
386
+ iota move build --skip-fetch-latest-git-deps
387
+ ```
388
+
389
+ ## Contributing
390
+
391
+ To contribute to this package see the guidelines for building and publishing in
392
+ [CONTRIBUTING](./CONTRIBUTING.md)
@@ -6,35 +6,14 @@ var cliCore = require('@twin.org/cli-core');
6
6
  var node_fs = require('node:fs');
7
7
  var core = require('@twin.org/core');
8
8
  var crypto = require('@twin.org/crypto');
9
+ var dltIota = require('@twin.org/dlt-iota');
9
10
  var FastGlob = require('fast-glob');
10
11
  var node_child_process = require('node:child_process');
11
12
  var node_util = require('node:util');
12
13
  var client = require('@iota/iota-sdk/client');
13
14
  var faucet = require('@iota/iota-sdk/faucet');
14
- var dltIota = require('@twin.org/dlt-iota');
15
15
 
16
16
  var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
17
- // Copyright 2024 IOTA Stiftung.
18
- // SPDX-License-Identifier: Apache-2.0.
19
- /**
20
- * Network types supported for deployment
21
- */
22
- // eslint-disable-next-line @typescript-eslint/naming-convention
23
- const NetworkTypes = {
24
- /**
25
- * Testnet.
26
- */
27
- Testnet: "testnet",
28
- /**
29
- * Devnet.
30
- */
31
- Devnet: "devnet",
32
- /**
33
- * Mainnet.
34
- */
35
- Mainnet: "mainnet"
36
- };
37
-
38
17
  // Copyright 2024 IOTA Stiftung.
39
18
  // SPDX-License-Identifier: Apache-2.0.
40
19
  /**
@@ -104,7 +83,7 @@ function buildCommandBuild(program) {
104
83
  async function actionCommandBuild(inputGlob, opts) {
105
84
  try {
106
85
  const network = opts.network ?? process.env.NETWORK;
107
- core.Guards.arrayOneOf("commands", "network", network, Object.values(NetworkTypes));
86
+ core.Guards.arrayOneOf("commands", "network", network, Object.values(dltIota.NetworkTypes));
108
87
  // Verify the IOTA SDK before we do anything else
109
88
  await verifyIotaSDK();
110
89
  const { normalizedGlob, normalizedOutput, executionDir } = normalizePathsAndWorkingDir(inputGlob, opts.output ?? "smart-contract-deployments.json");
@@ -158,7 +137,7 @@ async function actionCommandBuild(inputGlob, opts) {
158
137
  const { contractName, packageId, packageData } = compiled;
159
138
  const targetNetworkData = finalJson[network];
160
139
  targetNetworkData.packageId = packageId;
161
- targetNetworkData.package = packageData;
140
+ targetNetworkData.packageBytecode = packageData;
162
141
  cliCore.CLIDisplay.value(`Updated ${network} package`, contractName, 2);
163
142
  }
164
143
  }
@@ -343,7 +322,7 @@ function buildCommandDeploy(program) {
343
322
  .action(actionCommandDeploy);
344
323
  }
345
324
  /**
346
- * Switch IOTA CLI to the target network environment.
325
+ * Switch IOTA CLI to the target network environment and set the active address.
347
326
  * @param network Target network to switch to
348
327
  * @param dryRun Whether this is a dry run (checks environment but doesn't switch)
349
328
  */
@@ -365,9 +344,27 @@ async function setIotaEnvironment(network, dryRun = false) {
365
344
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.iotaEnvironmentCheck"), `✅ ${network} environment exists`, 1);
366
345
  return;
367
346
  }
368
- // Switch to target network environment
369
- await execAsync(`iota client switch --env ${network}`);
347
+ // 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);
350
+ // Check if address exists in IOTA CLI
351
+ const { stdout: addressListOutput } = await execAsync("iota client addresses --json");
352
+ const addressInfo = JSON.parse(addressListOutput);
353
+ const addressExists = addressInfo.addresses.some(([_, addr]) => addr === targetAddress);
354
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.checkingAddress"), targetAddress, 1);
355
+ if (!addressExists) {
356
+ // Import the address using the mnemonic
357
+ cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.deploy.progress.importingDeployerAddress"));
358
+ const mnemonic = await getDeploymentMnemonic(network);
359
+ const aliasName = `deployer-${network}`;
360
+ const derivationPath = crypto.Bip44.path(dltIota.Iota.DEFAULT_COIN_TYPE, 0, false, addressIndex).toString();
361
+ await execAsync(`iota keytool import "${mnemonic}" ed25519 "${derivationPath}" --alias "${aliasName}"`);
362
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.importedDeployerAddress"), `${aliasName} (${targetAddress})`, 1);
363
+ }
364
+ // Switch both environment and address in one command
365
+ await execAsync(`iota client switch --env ${network} --address ${targetAddress}`);
370
366
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.switchedIotaEnvironment"), network, 1);
367
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.switchedActiveAddress"), targetAddress, 1);
371
368
  // Verify the switch was successful
372
369
  const { stdout: activeEnv } = await execAsync("iota client active-env");
373
370
  if (!activeEnv.includes(network)) {
@@ -376,6 +373,16 @@ async function setIotaEnvironment(network, dryRun = false) {
376
373
  activeEnv
377
374
  });
378
375
  }
376
+ // Verify address switch was successful
377
+ const { stdout: activeAddressOutput } = await execAsync("iota client active-address");
378
+ const activeAddress = activeAddressOutput.trim();
379
+ if (activeAddress !== targetAddress) {
380
+ throw new core.GeneralError("commands", "commands.deploy.addressSwitchFailed", {
381
+ network,
382
+ targetAddress,
383
+ activeAddress
384
+ });
385
+ }
379
386
  }
380
387
  catch (error) {
381
388
  throw new core.GeneralError("commands", "commands.deploy.environmentOperationFailed", { network, operation: dryRun ? "check" : "switch to" }, error);
@@ -397,7 +404,7 @@ async function actionCommandDeploy(opts) {
397
404
  const dryRun = opts.dryRun ?? false;
398
405
  const force = opts.force ?? false;
399
406
  const network = opts.network ?? process.env.NETWORK;
400
- core.Guards.arrayOneOf("commands", "network", network, Object.values(NetworkTypes));
407
+ core.Guards.arrayOneOf("commands", "network", network, Object.values(dltIota.NetworkTypes));
401
408
  // Verify the IOTA SDK before we do anything else
402
409
  await verifyIotaSDK();
403
410
  // Check/switch to target network environment BEFORE loading config
@@ -405,7 +412,7 @@ async function actionCommandDeploy(opts) {
405
412
  const config = await createNetworkConfig(network);
406
413
  validateNetworkConfig(config, network);
407
414
  const contractsData = await loadCompiledContracts(contractsPath);
408
- if (network === "mainnet") {
415
+ if (network === dltIota.NetworkTypes.Mainnet) {
409
416
  await validateDeploymentEnvironment(network);
410
417
  }
411
418
  const networkContracts = contractsData[network];
@@ -522,12 +529,12 @@ async function validateEnvironmentForNetwork(network, config, isDryRun = false)
522
529
  else {
523
530
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.walletAddress"), walletAddress, 1);
524
531
  }
525
- if (network === "mainnet") {
532
+ if (network === dltIota.NetworkTypes.Mainnet) {
526
533
  await validateDeploymentEnvironment(network);
527
534
  }
528
- else if ((network === "testnet" || network === "devnet") && !isDryRun) {
529
- // For testnet/devnet, request funds from faucet
530
- await requestFaucetFunds(network, walletAddress);
535
+ else if ((network === dltIota.NetworkTypes.Testnet || network === dltIota.NetworkTypes.Devnet) && !isDryRun) {
536
+ // For testnet/devnet, check balance first and only request funds if needed
537
+ await checkBalanceAndRequestFaucetIfNeeded(network, config, walletAddress);
531
538
  }
532
539
  return walletAddress;
533
540
  }
@@ -541,29 +548,31 @@ async function validateEnvironmentForNetwork(network, config, isDryRun = false)
541
548
  */
542
549
  async function checkWalletBalance(network, config, walletAddress, isDryRun = false) {
543
550
  const client$1 = new client.IotaClient({ url: config.rpc.url });
544
- const balanceIota = await client$1.getBalance({ owner: walletAddress });
545
- const balanceNumberIota = Number(balanceIota.totalBalance);
546
- const requiredIota = nanosToIota(config.deployment.gasBudget);
551
+ const balanceResponse = await client$1.getBalance({ owner: walletAddress });
552
+ const balanceInNanos = Number(balanceResponse.totalBalance);
553
+ const requiredInNanos = config.deployment.gasBudget;
554
+ const balanceInIota = nanosToIota(balanceInNanos);
555
+ const requiredInIota = nanosToIota(requiredInNanos);
547
556
  const balanceLabel = isDryRun
548
557
  ? "commands.deploy.labels.dryRunWalletBalance"
549
558
  : "commands.deploy.labels.walletBalance";
550
559
  const gasBudgetLabel = isDryRun
551
560
  ? "commands.deploy.labels.dryRunGasBudget"
552
561
  : "commands.deploy.labels.gasBudget";
553
- cliCore.CLIDisplay.value(core.I18n.formatMessage(balanceLabel), `${balanceNumberIota.toFixed(2)} IOTA`, 1);
554
- cliCore.CLIDisplay.value(core.I18n.formatMessage(gasBudgetLabel), `${requiredIota.toFixed(2)} IOTA`, 1);
555
- // Handle insufficient balance based on network and run type
556
- if (balanceNumberIota < requiredIota) {
562
+ cliCore.CLIDisplay.value(core.I18n.formatMessage(balanceLabel), `${balanceInIota.toFixed(2)} IOTA`, 1);
563
+ cliCore.CLIDisplay.value(core.I18n.formatMessage(gasBudgetLabel), `${requiredInIota.toFixed(2)} IOTA`, 1);
564
+ // Handle insufficient balance based on network and run type (compare in same units - nanos)
565
+ if (balanceInNanos < requiredInNanos) {
557
566
  if (isDryRun) {
558
567
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.warning"), core.I18n.formatMessage("commands.deploy.labels.insufficientBalanceWarning", {
559
- currentBalance: balanceNumberIota.toFixed(2),
560
- requiredBalance: requiredIota.toFixed(2)
568
+ currentBalance: balanceInIota.toFixed(2),
569
+ requiredBalance: requiredInIota.toFixed(2)
561
570
  }), 2);
562
571
  }
563
- else if (network === "mainnet") {
572
+ else if (network === dltIota.NetworkTypes.Mainnet) {
564
573
  throw new core.GeneralError("commands", "commands.deploy.insufficientBalance", {
565
- balance: balanceNumberIota,
566
- required: requiredIota,
574
+ balance: balanceInIota,
575
+ required: requiredInIota,
567
576
  walletAddress
568
577
  });
569
578
  }
@@ -571,13 +580,13 @@ async function checkWalletBalance(network, config, walletAddress, isDryRun = fal
571
580
  // For testnet/devnet, show warning but continue
572
581
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.warning"), core.I18n.formatMessage("commands.deploy.labels.insufficientBalanceAfterFaucet", {
573
582
  network,
574
- balance: balanceNumberIota.toFixed(2),
575
- required: requiredIota.toFixed(2),
583
+ balance: balanceInIota.toFixed(2),
584
+ required: requiredInIota.toFixed(2),
576
585
  walletAddress
577
586
  }), 2);
578
587
  }
579
588
  }
580
- return balanceNumberIota;
589
+ return balanceInNanos;
581
590
  }
582
591
  /**
583
592
  * Handle dry run validation and display.
@@ -613,10 +622,16 @@ async function handleActualDeployment(contractName, contractData, config, networ
613
622
  await checkWalletBalance(network, config, walletAddress, false);
614
623
  const deploymentResult = await deployWithIotaCli(config.deployment.gasBudget);
615
624
  contractData.deployedPackageId = deploymentResult.packageId;
616
- contractData.upgradeCap = deploymentResult.upgradeCap;
625
+ contractData.upgradeCapabilityId = deploymentResult.upgradeCap;
626
+ if (deploymentResult.migrationStateId) {
627
+ contractData.migrationStateId = deploymentResult.migrationStateId;
628
+ }
617
629
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.deployedPackageIdResult"), deploymentResult.packageId, 1);
618
630
  if (deploymentResult.upgradeCap) {
619
- cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.upgradeCapId"), deploymentResult.upgradeCap, 1);
631
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.upgradeCapabilityIdResult"), deploymentResult.upgradeCap, 1);
632
+ }
633
+ if (contractData.migrationStateId) {
634
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.migrationStateIdResult"), contractData.migrationStateId, 1);
620
635
  }
621
636
  }
622
637
  catch (err) {
@@ -628,7 +643,7 @@ async function handleActualDeployment(contractName, contractData, config, networ
628
643
  * @param contractName Name of the contract
629
644
  * @param contractData Contract compilation data
630
645
  * @param contractData.packageId Package ID
631
- * @param contractData.package Package
646
+ * @param contractData.packageBytecode Package bytecode
632
647
  * @param contractData.deployedPackageId Deployed package ID
633
648
  * @param config Network configuration
634
649
  * @param network Target network
@@ -682,7 +697,7 @@ function nanosToIota(nanos) {
682
697
  * @returns Promise that resolves when funding is complete.
683
698
  */
684
699
  async function requestFaucetFunds(network, walletAddress) {
685
- if (network !== "testnet" && network !== "devnet") {
700
+ if (network !== dltIota.NetworkTypes.Testnet && network !== dltIota.NetworkTypes.Devnet) {
686
701
  return;
687
702
  }
688
703
  cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.deploy.progress.requestingFaucetFunds", { network }));
@@ -695,20 +710,59 @@ async function requestFaucetFunds(network, walletAddress) {
695
710
  throw new core.GeneralError("commands", "commands.deploy.fundingFailed", undefined, response.error);
696
711
  }
697
712
  const client$1 = new client.IotaClient({ url: process.env.RPC_URL ?? "" });
698
- const balanceIota = await client$1.getBalance({ owner: walletAddress });
699
- const balanceNumberIota = Number(balanceIota.totalBalance);
700
- if (balanceNumberIota > 0n) {
701
- const amountIota = nanosToIota(Number(balanceNumberIota));
702
- cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.faucetFundsRequested"), `${amountIota.toFixed(2)} IOTA`, 1);
713
+ const balanceResponse = await client$1.getBalance({ owner: walletAddress });
714
+ const balanceInNanos = Number(balanceResponse.totalBalance);
715
+ if (balanceInNanos > 0) {
716
+ const amountInIota = nanosToIota(balanceInNanos);
717
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.faucetFundsRequested"), `${amountInIota.toFixed(2)} IOTA`, 1);
703
718
  }
704
719
  else {
705
720
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.warning"), core.I18n.formatMessage("commands.deploy.labels.faucetNoFundsAdded", { network }), 2);
706
721
  }
707
722
  }
723
+ /**
724
+ * Check wallet balance and request faucet funds only if needed.
725
+ * @param network The target network (testnet or devnet).
726
+ * @param config Network configuration.
727
+ * @param walletAddress The wallet address to check and potentially fund.
728
+ * @returns Promise that resolves when balance check and optional funding is complete.
729
+ */
730
+ async function checkBalanceAndRequestFaucetIfNeeded(network, config, walletAddress) {
731
+ if (network !== dltIota.NetworkTypes.Testnet && network !== dltIota.NetworkTypes.Devnet) {
732
+ return;
733
+ }
734
+ // Check current balance
735
+ const client$1 = new client.IotaClient({ url: config.rpc.url });
736
+ const balanceResponse = await client$1.getBalance({ owner: walletAddress });
737
+ const balanceInNanos = Number(balanceResponse.totalBalance);
738
+ const requiredInNanos = config.deployment.gasBudget;
739
+ // Convert to IOTA for display purposes
740
+ const balanceInIota = nanosToIota(balanceInNanos);
741
+ const requiredInIota = nanosToIota(requiredInNanos);
742
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.walletBalance"), `${balanceInIota.toFixed(2)} IOTA`, 1);
743
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.gasBudget"), `${requiredInIota.toFixed(2)} IOTA`, 1);
744
+ // Only request faucet funds if balance is insufficient (compare in same units - nanos)
745
+ if (balanceInNanos < requiredInNanos) {
746
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.warning"), core.I18n.formatMessage("commands.deploy.labels.insufficientBalance", {
747
+ currentBalance: balanceInIota.toFixed(2),
748
+ requiredBalance: requiredInIota.toFixed(2)
749
+ }), 1);
750
+ cliCore.CLIDisplay.task(core.I18n.formatMessage("commands.deploy.progress.requestingAdditionalFaucetFunds"));
751
+ await requestFaucetFunds(network, walletAddress);
752
+ // Check balance again after faucet request
753
+ const updatedBalanceResponse = await client$1.getBalance({ owner: walletAddress });
754
+ const updatedBalanceInNanos = Number(updatedBalanceResponse.totalBalance);
755
+ const updatedBalanceInIota = nanosToIota(updatedBalanceInNanos);
756
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.updatedWalletBalance"), `${updatedBalanceInIota.toFixed(2)} IOTA`, 1);
757
+ }
758
+ else {
759
+ cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.balanceCheck"), core.I18n.formatMessage("commands.deploy.labels.sufficientFundsAvailable"), 1);
760
+ }
761
+ }
708
762
  /**
709
763
  * Deploy contract using IOTA CLI.
710
764
  * @param gasBudget Gas budget for deployment.
711
- * @returns Deployment result with package ID.
765
+ * @returns Deployment result with package ID, upgrade cap, and migration state ID.
712
766
  */
713
767
  async function deployWithIotaCli(gasBudget) {
714
768
  // Find the Move project directory
@@ -719,8 +773,11 @@ async function deployWithIotaCli(gasBudget) {
719
773
  currentDir: process.cwd()
720
774
  });
721
775
  }
776
+ // Prioritize Move.toml in current directory, then use first found
777
+ const currentDirMoveToml = path.join(process.cwd(), "Move.toml");
778
+ const selectedMoveToml = moveTomlPaths.find(p => p === currentDirMoveToml) ?? moveTomlPaths[0];
722
779
  // Use the actual Move project directory
723
- const moveProjectRoot = path.dirname(moveTomlPaths[0]);
780
+ const moveProjectRoot = path.dirname(selectedMoveToml);
724
781
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.moveProjectRoot"), moveProjectRoot, 1);
725
782
  const publishCmd = `iota client publish --gas-budget ${gasBudget} --json`;
726
783
  cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.publishCommand"), publishCmd, 1);
@@ -736,9 +793,12 @@ async function deployWithIotaCli(gasBudget) {
736
793
  }
737
794
  // Extract UpgradeCap ID from created objects
738
795
  const upgradeCap = result.objectChanges?.find((change) => change.objectType === "0x2::package::UpgradeCap")?.objectId;
796
+ // Extract MigrationState ID from created objects
797
+ const migrationStateId = result.objectChanges?.find((change) => change.objectType?.endsWith("::MigrationState"))?.objectId;
739
798
  return {
740
799
  packageId,
741
- upgradeCap
800
+ upgradeCap,
801
+ migrationStateId
742
802
  };
743
803
  }
744
804
  /**
@@ -774,7 +834,7 @@ class CLI extends cliCore.CLIBase {
774
834
  return this.execute({
775
835
  title: "TWIN Move to JSON",
776
836
  appName: "move-to-json",
777
- version: "0.0.2-next.3", // x-release-please-version
837
+ version: "0.0.2-next.5", // x-release-please-version
778
838
  icon: "⚙️ ",
779
839
  supportsEnvFiles: true,
780
840
  overrideOutputWidth: options?.overrideOutputWidth
@@ -791,7 +851,6 @@ class CLI extends cliCore.CLIBase {
791
851
  }
792
852
 
793
853
  exports.CLI = CLI;
794
- exports.NetworkTypes = NetworkTypes;
795
854
  exports.actionCommandBuild = actionCommandBuild;
796
855
  exports.actionCommandDeploy = actionCommandDeploy;
797
856
  exports.buildCommandBuild = buildCommandBuild;
@@ -3,34 +3,13 @@ import { fileURLToPath } from 'node:url';
3
3
  import { CLIDisplay, CLIUtils, CLIBase } from '@twin.org/cli-core';
4
4
  import { promises } from 'node:fs';
5
5
  import { I18n, GeneralError, Guards, StringHelper, Converter, Is, Coerce } from '@twin.org/core';
6
- import { Sha3, Bip39 } from '@twin.org/crypto';
6
+ import { Sha3, Bip44, Bip39 } from '@twin.org/crypto';
7
+ import { NetworkTypes, Iota } from '@twin.org/dlt-iota';
7
8
  import FastGlob from 'fast-glob';
8
9
  import { exec } from 'node:child_process';
9
10
  import { promisify } from 'node:util';
10
11
  import { IotaClient } from '@iota/iota-sdk/client';
11
12
  import { requestIotaFromFaucetV0 } from '@iota/iota-sdk/faucet';
12
- import { Iota } from '@twin.org/dlt-iota';
13
-
14
- // Copyright 2024 IOTA Stiftung.
15
- // SPDX-License-Identifier: Apache-2.0.
16
- /**
17
- * Network types supported for deployment
18
- */
19
- // eslint-disable-next-line @typescript-eslint/naming-convention
20
- const NetworkTypes = {
21
- /**
22
- * Testnet.
23
- */
24
- Testnet: "testnet",
25
- /**
26
- * Devnet.
27
- */
28
- Devnet: "devnet",
29
- /**
30
- * Mainnet.
31
- */
32
- Mainnet: "mainnet"
33
- };
34
13
 
35
14
  // Copyright 2024 IOTA Stiftung.
36
15
  // SPDX-License-Identifier: Apache-2.0.
@@ -155,7 +134,7 @@ async function actionCommandBuild(inputGlob, opts) {
155
134
  const { contractName, packageId, packageData } = compiled;
156
135
  const targetNetworkData = finalJson[network];
157
136
  targetNetworkData.packageId = packageId;
158
- targetNetworkData.package = packageData;
137
+ targetNetworkData.packageBytecode = packageData;
159
138
  CLIDisplay.value(`Updated ${network} package`, contractName, 2);
160
139
  }
161
140
  }
@@ -340,7 +319,7 @@ function buildCommandDeploy(program) {
340
319
  .action(actionCommandDeploy);
341
320
  }
342
321
  /**
343
- * Switch IOTA CLI to the target network environment.
322
+ * Switch IOTA CLI to the target network environment and set the active address.
344
323
  * @param network Target network to switch to
345
324
  * @param dryRun Whether this is a dry run (checks environment but doesn't switch)
346
325
  */
@@ -362,9 +341,27 @@ async function setIotaEnvironment(network, dryRun = false) {
362
341
  CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.iotaEnvironmentCheck"), `✅ ${network} environment exists`, 1);
363
342
  return;
364
343
  }
365
- // Switch to target network environment
366
- await execAsync(`iota client switch --env ${network}`);
344
+ // Derive the target address from existing mnemonic/seed
345
+ const addressIndex = Coerce.number(process.env.ADDRESS_INDEX) ?? 0;
346
+ const targetAddress = await getDeploymentWalletAddress(network, addressIndex);
347
+ // Check if address exists in IOTA CLI
348
+ const { stdout: addressListOutput } = await execAsync("iota client addresses --json");
349
+ const addressInfo = JSON.parse(addressListOutput);
350
+ const addressExists = addressInfo.addresses.some(([_, addr]) => addr === targetAddress);
351
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.checkingAddress"), targetAddress, 1);
352
+ if (!addressExists) {
353
+ // Import the address using the mnemonic
354
+ CLIDisplay.task(I18n.formatMessage("commands.deploy.progress.importingDeployerAddress"));
355
+ const mnemonic = await getDeploymentMnemonic(network);
356
+ const aliasName = `deployer-${network}`;
357
+ const derivationPath = Bip44.path(Iota.DEFAULT_COIN_TYPE, 0, false, addressIndex).toString();
358
+ await execAsync(`iota keytool import "${mnemonic}" ed25519 "${derivationPath}" --alias "${aliasName}"`);
359
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.importedDeployerAddress"), `${aliasName} (${targetAddress})`, 1);
360
+ }
361
+ // Switch both environment and address in one command
362
+ await execAsync(`iota client switch --env ${network} --address ${targetAddress}`);
367
363
  CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.switchedIotaEnvironment"), network, 1);
364
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.switchedActiveAddress"), targetAddress, 1);
368
365
  // Verify the switch was successful
369
366
  const { stdout: activeEnv } = await execAsync("iota client active-env");
370
367
  if (!activeEnv.includes(network)) {
@@ -373,6 +370,16 @@ async function setIotaEnvironment(network, dryRun = false) {
373
370
  activeEnv
374
371
  });
375
372
  }
373
+ // Verify address switch was successful
374
+ const { stdout: activeAddressOutput } = await execAsync("iota client active-address");
375
+ const activeAddress = activeAddressOutput.trim();
376
+ if (activeAddress !== targetAddress) {
377
+ throw new GeneralError("commands", "commands.deploy.addressSwitchFailed", {
378
+ network,
379
+ targetAddress,
380
+ activeAddress
381
+ });
382
+ }
376
383
  }
377
384
  catch (error) {
378
385
  throw new GeneralError("commands", "commands.deploy.environmentOperationFailed", { network, operation: dryRun ? "check" : "switch to" }, error);
@@ -402,7 +409,7 @@ async function actionCommandDeploy(opts) {
402
409
  const config = await createNetworkConfig(network);
403
410
  validateNetworkConfig(config, network);
404
411
  const contractsData = await loadCompiledContracts(contractsPath);
405
- if (network === "mainnet") {
412
+ if (network === NetworkTypes.Mainnet) {
406
413
  await validateDeploymentEnvironment(network);
407
414
  }
408
415
  const networkContracts = contractsData[network];
@@ -519,12 +526,12 @@ async function validateEnvironmentForNetwork(network, config, isDryRun = false)
519
526
  else {
520
527
  CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.walletAddress"), walletAddress, 1);
521
528
  }
522
- if (network === "mainnet") {
529
+ if (network === NetworkTypes.Mainnet) {
523
530
  await validateDeploymentEnvironment(network);
524
531
  }
525
- else if ((network === "testnet" || network === "devnet") && !isDryRun) {
526
- // For testnet/devnet, request funds from faucet
527
- await requestFaucetFunds(network, walletAddress);
532
+ else if ((network === NetworkTypes.Testnet || network === NetworkTypes.Devnet) && !isDryRun) {
533
+ // For testnet/devnet, check balance first and only request funds if needed
534
+ await checkBalanceAndRequestFaucetIfNeeded(network, config, walletAddress);
528
535
  }
529
536
  return walletAddress;
530
537
  }
@@ -538,29 +545,31 @@ async function validateEnvironmentForNetwork(network, config, isDryRun = false)
538
545
  */
539
546
  async function checkWalletBalance(network, config, walletAddress, isDryRun = false) {
540
547
  const client = new IotaClient({ url: config.rpc.url });
541
- const balanceIota = await client.getBalance({ owner: walletAddress });
542
- const balanceNumberIota = Number(balanceIota.totalBalance);
543
- const requiredIota = nanosToIota(config.deployment.gasBudget);
548
+ const balanceResponse = await client.getBalance({ owner: walletAddress });
549
+ const balanceInNanos = Number(balanceResponse.totalBalance);
550
+ const requiredInNanos = config.deployment.gasBudget;
551
+ const balanceInIota = nanosToIota(balanceInNanos);
552
+ const requiredInIota = nanosToIota(requiredInNanos);
544
553
  const balanceLabel = isDryRun
545
554
  ? "commands.deploy.labels.dryRunWalletBalance"
546
555
  : "commands.deploy.labels.walletBalance";
547
556
  const gasBudgetLabel = isDryRun
548
557
  ? "commands.deploy.labels.dryRunGasBudget"
549
558
  : "commands.deploy.labels.gasBudget";
550
- CLIDisplay.value(I18n.formatMessage(balanceLabel), `${balanceNumberIota.toFixed(2)} IOTA`, 1);
551
- CLIDisplay.value(I18n.formatMessage(gasBudgetLabel), `${requiredIota.toFixed(2)} IOTA`, 1);
552
- // Handle insufficient balance based on network and run type
553
- if (balanceNumberIota < requiredIota) {
559
+ CLIDisplay.value(I18n.formatMessage(balanceLabel), `${balanceInIota.toFixed(2)} IOTA`, 1);
560
+ CLIDisplay.value(I18n.formatMessage(gasBudgetLabel), `${requiredInIota.toFixed(2)} IOTA`, 1);
561
+ // Handle insufficient balance based on network and run type (compare in same units - nanos)
562
+ if (balanceInNanos < requiredInNanos) {
554
563
  if (isDryRun) {
555
564
  CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.warning"), I18n.formatMessage("commands.deploy.labels.insufficientBalanceWarning", {
556
- currentBalance: balanceNumberIota.toFixed(2),
557
- requiredBalance: requiredIota.toFixed(2)
565
+ currentBalance: balanceInIota.toFixed(2),
566
+ requiredBalance: requiredInIota.toFixed(2)
558
567
  }), 2);
559
568
  }
560
- else if (network === "mainnet") {
569
+ else if (network === NetworkTypes.Mainnet) {
561
570
  throw new GeneralError("commands", "commands.deploy.insufficientBalance", {
562
- balance: balanceNumberIota,
563
- required: requiredIota,
571
+ balance: balanceInIota,
572
+ required: requiredInIota,
564
573
  walletAddress
565
574
  });
566
575
  }
@@ -568,13 +577,13 @@ async function checkWalletBalance(network, config, walletAddress, isDryRun = fal
568
577
  // For testnet/devnet, show warning but continue
569
578
  CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.warning"), I18n.formatMessage("commands.deploy.labels.insufficientBalanceAfterFaucet", {
570
579
  network,
571
- balance: balanceNumberIota.toFixed(2),
572
- required: requiredIota.toFixed(2),
580
+ balance: balanceInIota.toFixed(2),
581
+ required: requiredInIota.toFixed(2),
573
582
  walletAddress
574
583
  }), 2);
575
584
  }
576
585
  }
577
- return balanceNumberIota;
586
+ return balanceInNanos;
578
587
  }
579
588
  /**
580
589
  * Handle dry run validation and display.
@@ -610,10 +619,16 @@ async function handleActualDeployment(contractName, contractData, config, networ
610
619
  await checkWalletBalance(network, config, walletAddress, false);
611
620
  const deploymentResult = await deployWithIotaCli(config.deployment.gasBudget);
612
621
  contractData.deployedPackageId = deploymentResult.packageId;
613
- contractData.upgradeCap = deploymentResult.upgradeCap;
622
+ contractData.upgradeCapabilityId = deploymentResult.upgradeCap;
623
+ if (deploymentResult.migrationStateId) {
624
+ contractData.migrationStateId = deploymentResult.migrationStateId;
625
+ }
614
626
  CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.deployedPackageIdResult"), deploymentResult.packageId, 1);
615
627
  if (deploymentResult.upgradeCap) {
616
- CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.upgradeCapId"), deploymentResult.upgradeCap, 1);
628
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.upgradeCapabilityIdResult"), deploymentResult.upgradeCap, 1);
629
+ }
630
+ if (contractData.migrationStateId) {
631
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.migrationStateIdResult"), contractData.migrationStateId, 1);
617
632
  }
618
633
  }
619
634
  catch (err) {
@@ -625,7 +640,7 @@ async function handleActualDeployment(contractName, contractData, config, networ
625
640
  * @param contractName Name of the contract
626
641
  * @param contractData Contract compilation data
627
642
  * @param contractData.packageId Package ID
628
- * @param contractData.package Package
643
+ * @param contractData.packageBytecode Package bytecode
629
644
  * @param contractData.deployedPackageId Deployed package ID
630
645
  * @param config Network configuration
631
646
  * @param network Target network
@@ -679,7 +694,7 @@ function nanosToIota(nanos) {
679
694
  * @returns Promise that resolves when funding is complete.
680
695
  */
681
696
  async function requestFaucetFunds(network, walletAddress) {
682
- if (network !== "testnet" && network !== "devnet") {
697
+ if (network !== NetworkTypes.Testnet && network !== NetworkTypes.Devnet) {
683
698
  return;
684
699
  }
685
700
  CLIDisplay.task(I18n.formatMessage("commands.deploy.progress.requestingFaucetFunds", { network }));
@@ -692,20 +707,59 @@ async function requestFaucetFunds(network, walletAddress) {
692
707
  throw new GeneralError("commands", "commands.deploy.fundingFailed", undefined, response.error);
693
708
  }
694
709
  const client = new IotaClient({ url: process.env.RPC_URL ?? "" });
695
- const balanceIota = await client.getBalance({ owner: walletAddress });
696
- const balanceNumberIota = Number(balanceIota.totalBalance);
697
- if (balanceNumberIota > 0n) {
698
- const amountIota = nanosToIota(Number(balanceNumberIota));
699
- CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.faucetFundsRequested"), `${amountIota.toFixed(2)} IOTA`, 1);
710
+ const balanceResponse = await client.getBalance({ owner: walletAddress });
711
+ const balanceInNanos = Number(balanceResponse.totalBalance);
712
+ if (balanceInNanos > 0) {
713
+ const amountInIota = nanosToIota(balanceInNanos);
714
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.faucetFundsRequested"), `${amountInIota.toFixed(2)} IOTA`, 1);
700
715
  }
701
716
  else {
702
717
  CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.warning"), I18n.formatMessage("commands.deploy.labels.faucetNoFundsAdded", { network }), 2);
703
718
  }
704
719
  }
720
+ /**
721
+ * Check wallet balance and request faucet funds only if needed.
722
+ * @param network The target network (testnet or devnet).
723
+ * @param config Network configuration.
724
+ * @param walletAddress The wallet address to check and potentially fund.
725
+ * @returns Promise that resolves when balance check and optional funding is complete.
726
+ */
727
+ async function checkBalanceAndRequestFaucetIfNeeded(network, config, walletAddress) {
728
+ if (network !== NetworkTypes.Testnet && network !== NetworkTypes.Devnet) {
729
+ return;
730
+ }
731
+ // Check current balance
732
+ const client = new IotaClient({ url: config.rpc.url });
733
+ const balanceResponse = await client.getBalance({ owner: walletAddress });
734
+ const balanceInNanos = Number(balanceResponse.totalBalance);
735
+ const requiredInNanos = config.deployment.gasBudget;
736
+ // Convert to IOTA for display purposes
737
+ const balanceInIota = nanosToIota(balanceInNanos);
738
+ const requiredInIota = nanosToIota(requiredInNanos);
739
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.walletBalance"), `${balanceInIota.toFixed(2)} IOTA`, 1);
740
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.gasBudget"), `${requiredInIota.toFixed(2)} IOTA`, 1);
741
+ // Only request faucet funds if balance is insufficient (compare in same units - nanos)
742
+ if (balanceInNanos < requiredInNanos) {
743
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.warning"), I18n.formatMessage("commands.deploy.labels.insufficientBalance", {
744
+ currentBalance: balanceInIota.toFixed(2),
745
+ requiredBalance: requiredInIota.toFixed(2)
746
+ }), 1);
747
+ CLIDisplay.task(I18n.formatMessage("commands.deploy.progress.requestingAdditionalFaucetFunds"));
748
+ await requestFaucetFunds(network, walletAddress);
749
+ // Check balance again after faucet request
750
+ const updatedBalanceResponse = await client.getBalance({ owner: walletAddress });
751
+ const updatedBalanceInNanos = Number(updatedBalanceResponse.totalBalance);
752
+ const updatedBalanceInIota = nanosToIota(updatedBalanceInNanos);
753
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.updatedWalletBalance"), `${updatedBalanceInIota.toFixed(2)} IOTA`, 1);
754
+ }
755
+ else {
756
+ CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.balanceCheck"), I18n.formatMessage("commands.deploy.labels.sufficientFundsAvailable"), 1);
757
+ }
758
+ }
705
759
  /**
706
760
  * Deploy contract using IOTA CLI.
707
761
  * @param gasBudget Gas budget for deployment.
708
- * @returns Deployment result with package ID.
762
+ * @returns Deployment result with package ID, upgrade cap, and migration state ID.
709
763
  */
710
764
  async function deployWithIotaCli(gasBudget) {
711
765
  // Find the Move project directory
@@ -716,8 +770,11 @@ async function deployWithIotaCli(gasBudget) {
716
770
  currentDir: process.cwd()
717
771
  });
718
772
  }
773
+ // Prioritize Move.toml in current directory, then use first found
774
+ const currentDirMoveToml = path.join(process.cwd(), "Move.toml");
775
+ const selectedMoveToml = moveTomlPaths.find(p => p === currentDirMoveToml) ?? moveTomlPaths[0];
719
776
  // Use the actual Move project directory
720
- const moveProjectRoot = path.dirname(moveTomlPaths[0]);
777
+ const moveProjectRoot = path.dirname(selectedMoveToml);
721
778
  CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.moveProjectRoot"), moveProjectRoot, 1);
722
779
  const publishCmd = `iota client publish --gas-budget ${gasBudget} --json`;
723
780
  CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.publishCommand"), publishCmd, 1);
@@ -733,9 +790,12 @@ async function deployWithIotaCli(gasBudget) {
733
790
  }
734
791
  // Extract UpgradeCap ID from created objects
735
792
  const upgradeCap = result.objectChanges?.find((change) => change.objectType === "0x2::package::UpgradeCap")?.objectId;
793
+ // Extract MigrationState ID from created objects
794
+ const migrationStateId = result.objectChanges?.find((change) => change.objectType?.endsWith("::MigrationState"))?.objectId;
736
795
  return {
737
796
  packageId,
738
- upgradeCap
797
+ upgradeCap,
798
+ migrationStateId
739
799
  };
740
800
  }
741
801
  /**
@@ -771,7 +831,7 @@ class CLI extends CLIBase {
771
831
  return this.execute({
772
832
  title: "TWIN Move to JSON",
773
833
  appName: "move-to-json",
774
- version: "0.0.2-next.3", // x-release-please-version
834
+ version: "0.0.2-next.5", // x-release-please-version
775
835
  icon: "⚙️ ",
776
836
  supportsEnvFiles: true,
777
837
  overrideOutputWidth: options?.overrideOutputWidth
@@ -787,4 +847,4 @@ class CLI extends CLIBase {
787
847
  }
788
848
  }
789
849
 
790
- export { CLI, NetworkTypes, actionCommandBuild, actionCommandDeploy, buildCommandBuild, buildCommandDeploy, getDeploymentMnemonic, getDeploymentSeed, searchDirectoryForMoveToml, validateDeploymentEnvironment, verifyIotaSDK };
850
+ export { CLI, actionCommandBuild, actionCommandDeploy, buildCommandBuild, buildCommandDeploy, getDeploymentMnemonic, getDeploymentSeed, searchDirectoryForMoveToml, validateDeploymentEnvironment, verifyIotaSDK };
@@ -45,7 +45,8 @@
45
45
  "noPackageData": "No package data found for contract in compiled modules",
46
46
  "packageNotFoundOnNetwork": "Package {packageId} not found on network {network}. Please ensure the package is deployed.",
47
47
  "dryRunInvalidConfig": "Dry run failed due to invalid configuration",
48
- "noMoveTomlFilesFound": "No Move.toml files found in current directory or subdirectories. Make sure you're running this from a directory containing Move projects."
48
+ "noMoveTomlFilesFound": "No Move.toml files found in current directory or subdirectories. Make sure you're running this from a directory containing Move projects.",
49
+ "addressSwitchFailed": "Failed to switch to active address {targetAddress} for {network}. Active address: {activeAddress}"
49
50
  }
50
51
  },
51
52
  "validation": {
@@ -226,6 +227,23 @@
226
227
  "gasReservationFailed": "The gas reservation failed",
227
228
  "gasStationExecutionFailed": "The gas station execution failed"
228
229
  },
230
+ "iotaSmartContractUtils": {
231
+ "migrationFailed": "Smart contract migration failed",
232
+ "migrateSmartContractFailed": "Failed to migrate smart contract for object \"{objectId}\"",
233
+ "enableMigrationFailed": "Failed to enable migration for smart contract",
234
+ "disableMigrationFailed": "Failed to disable migration for smart contract",
235
+ "migrationStateNotReadable": "Cannot read migration state for ID \"{migrationStateId}\"",
236
+ "isMigrationActiveFailed": "Failed to check if migration is active",
237
+ "invalidVersionData": "Invalid version data received from smart contract",
238
+ "getCurrentContractVersionNoData": "No version data returned from contract",
239
+ "getCurrentContractVersionFailed": "Failed to get current contract version",
240
+ "objectNotReadable": "Cannot read object with ID \"{objectId}\"",
241
+ "objectInvalidFormat": "Object \"{objectId}\" has invalid format",
242
+ "validateObjectVersionFailed": "Failed to validate object version for \"{objectId}\"",
243
+ "getContractObjectIdsFailed": "Failed to get contract object IDs for namespace \"{namespace}\" on network \"{network}\"",
244
+ "adminCapNotFound": "AdminCap object not found for type \"{adminCapType}\" and address \"{adminAddress}\"",
245
+ "migrationStateNotFound": "MigrationState object not found for type \"{migrationStateType}\" and address \"{adminAddress}\""
246
+ },
229
247
  "entitySchemaHelper": {
230
248
  "noIsPrimary": "Property \"entitySchema.properties\" must contain a value with isPrimary set",
231
249
  "multipleIsPrimary": "Property \"entitySchema.properties\" contains more than one property with isPrimary set",
@@ -413,7 +431,7 @@
413
431
  "dryRunMode": "Dry Run Mode",
414
432
  "forceMode": "Force Mode",
415
433
  "deployedPackageId": "Deployed Package ID",
416
- "upgradeCap": "Upgrade Cap",
434
+ "upgradeCapabilityId": "Upgrade Capability ID",
417
435
  "gasUsed": "Gas Used",
418
436
  "deploymentStatus": "Deployment Status",
419
437
  "iotaEnvironmentCheck": "IOTA environment check",
@@ -430,7 +448,10 @@
430
448
  "walletAddress": "Wallet Address",
431
449
  "walletBalance": "Wallet Balance",
432
450
  "deployedPackageIdResult": "Deployed Package ID",
433
- "upgradeCapId": "UpgradeCap ID",
451
+ "upgradeCapabilityIdResult": "Upgrade Capability ID",
452
+ "migrationStateIdResult": "Migration State ID",
453
+ "importedDeployerAddress": "Imported deployer address",
454
+ "switchedActiveAddress": "Switched active address",
434
455
  "moveProjectRoot": "Move Project Root",
435
456
  "publishCommand": "Publish Command",
436
457
  "workingDirectory": "Working Directory",
@@ -443,15 +464,20 @@
443
464
  "balanceStatus": "Balance Status",
444
465
  "faucetFundsRequested": "Faucet funds requested",
445
466
  "faucetRequestFailed": "Faucet request failed for {network}: {error}",
446
- "insufficientBalanceAfterFaucet": "Insufficient balance after faucet request on {network}: {balance} IOTA < {required} IOTA for wallet {walletAddress}. Attempting deployment anyway."
467
+ "insufficientBalanceAfterFaucet": "Insufficient balance after faucet request on {network}: {balance} IOTA < {required} IOTA for wallet {walletAddress}. Attempting deployment anyway.",
468
+ "updatedWalletBalance": "Updated wallet balance",
469
+ "balanceCheck": "Balance check",
470
+ "sufficientFundsAvailable": "Sufficient funds available, skipping faucet request"
447
471
  },
448
472
  "progress": {
449
473
  "loadingContracts": "Loading compiled contracts...",
450
474
  "checkingEnvironment": "Checking IOTA CLI environment...",
451
475
  "settingEnvironment": "Setting IOTA CLI environment...",
476
+ "importingDeployerAddress": "Importing deployer address to IOTA CLI...",
452
477
  "deployingContract": "Deploying contract: {contractName} ({network})",
453
478
  "updatingContractsFile": "Updating contracts file...",
454
- "requestingFaucetFunds": "Requesting funds from {network} faucet..."
479
+ "requestingFaucetFunds": "Requesting funds from {network} faucet...",
480
+ "requestingAdditionalFaucetFunds": "Requesting additional funds from faucet..."
455
481
  },
456
482
  "messages": {},
457
483
  "section": {
@@ -1,5 +1,5 @@
1
+ import { NetworkTypes } from "@twin.org/dlt-iota";
1
2
  import type { Command } from "commander";
2
- import { NetworkTypes } from "../models/networkTypes";
3
3
  /**
4
4
  * Build the build command to be consumed by the CLI.
5
5
  * @param program The command to build on.
@@ -1,5 +1,5 @@
1
+ import { NetworkTypes } from "@twin.org/dlt-iota";
1
2
  import type { Command } from "commander";
2
- import { NetworkTypes } from "../models/networkTypes";
3
3
  /**
4
4
  * Build the deploy command.
5
5
  * @param program The command program.
@@ -1,10 +1,7 @@
1
1
  export * from "./cli";
2
2
  export * from "./commands/build";
3
3
  export * from "./commands/deploy";
4
- export * from "./models/IContractData";
5
4
  export * from "./models/INetworkConfig";
6
- export * from "./models/ISmartContractDeployments";
7
- export * from "./models/networkTypes";
8
5
  export * from "./utils/envSetup";
9
6
  export * from "./utils/iotaUtils";
10
7
  export * from "./utils/moveToJsonUtils";
@@ -1,4 +1,4 @@
1
- import type { NetworkTypes } from "./networkTypes";
1
+ import type { NetworkTypes } from "@twin.org/dlt-iota";
2
2
  /**
3
3
  * Network configuration interface
4
4
  */
@@ -1,4 +1,4 @@
1
- import type { NetworkTypes } from "../models/networkTypes.js";
1
+ import type { NetworkTypes } from "@twin.org/dlt-iota";
2
2
  /**
3
3
  * Validate that required environment variables are set for deployment.
4
4
  * @param network The target network.
package/docs/changelog.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @twin.org/move-to-json - Changelog
2
2
 
3
+ ## [0.0.2-next.5](https://github.com/twinfoundation/dlt/compare/move-to-json-v0.0.2-next.4...move-to-json-v0.0.2-next.5) (2025-08-25)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * modifying logging type param ([#36](https://github.com/twinfoundation/dlt/issues/36)) ([b884fcc](https://github.com/twinfoundation/dlt/commit/b884fccef5bea5c6818cf8bfa8af197d3622cac6))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * dependencies
15
+ * @twin.org/dlt-iota bumped from 0.0.2-next.4 to 0.0.2-next.5
16
+
17
+ ## [0.0.2-next.4](https://github.com/twinfoundation/dlt/compare/move-to-json-v0.0.2-next.3...move-to-json-v0.0.2-next.4) (2025-08-22)
18
+
19
+
20
+ ### Features
21
+
22
+ * upgrade capabilities ([#32](https://github.com/twinfoundation/dlt/issues/32)) ([437219f](https://github.com/twinfoundation/dlt/commit/437219f0f784ec38353c01e1c8ce6bfba3b1b530))
23
+
24
+
25
+ ### Dependencies
26
+
27
+ * The following workspace dependencies were updated
28
+ * dependencies
29
+ * @twin.org/dlt-iota bumped from 0.0.2-next.3 to 0.0.2-next.4
30
+
3
31
  ## [0.0.2-next.3](https://github.com/twinfoundation/dlt/compare/move-to-json-v0.0.2-next.2...move-to-json-v0.0.2-next.3) (2025-08-20)
4
32
 
5
33
 
@@ -18,7 +18,7 @@ Additional options.
18
18
 
19
19
  #### network?
20
20
 
21
- [`NetworkTypes`](../type-aliases/NetworkTypes.md)
21
+ `NetworkTypes`
22
22
 
23
23
  Target network (testnet/devnet/mainnet) - optional if NETWORK env var is set.
24
24
 
@@ -18,7 +18,7 @@ Path to compiled modules JSON.
18
18
 
19
19
  #### network?
20
20
 
21
- [`NetworkTypes`](../type-aliases/NetworkTypes.md)
21
+ `NetworkTypes`
22
22
 
23
23
  Network identifier - optional if NETWORK env var is set.
24
24
 
@@ -8,7 +8,7 @@ Get the deployment mnemonic for a network.
8
8
 
9
9
  ### network
10
10
 
11
- [`NetworkTypes`](../type-aliases/NetworkTypes.md)
11
+ `NetworkTypes`
12
12
 
13
13
  The target network.
14
14
 
@@ -8,7 +8,7 @@ Get the deployment seed for a network (if available).
8
8
 
9
9
  ### network
10
10
 
11
- [`NetworkTypes`](../type-aliases/NetworkTypes.md)
11
+ `NetworkTypes`
12
12
 
13
13
  The target network.
14
14
 
@@ -8,7 +8,7 @@ Validate that required environment variables are set for deployment.
8
8
 
9
9
  ### network
10
10
 
11
- [`NetworkTypes`](../type-aliases/NetworkTypes.md)
11
+ `NetworkTypes`
12
12
 
13
13
  The target network.
14
14
 
@@ -6,18 +6,8 @@
6
6
 
7
7
  ## Interfaces
8
8
 
9
- - [IContractData](interfaces/IContractData.md)
10
9
  - [INetworkConfig](interfaces/INetworkConfig.md)
11
10
 
12
- ## Type Aliases
13
-
14
- - [ISmartContractDeployments](type-aliases/ISmartContractDeployments.md)
15
- - [NetworkTypes](type-aliases/NetworkTypes.md)
16
-
17
- ## Variables
18
-
19
- - [NetworkTypes](variables/NetworkTypes.md)
20
-
21
11
  ## Functions
22
12
 
23
13
  - [buildCommandBuild](functions/buildCommandBuild.md)
@@ -6,7 +6,7 @@ Network configuration interface
6
6
 
7
7
  ### network
8
8
 
9
- > **network**: [`NetworkTypes`](../type-aliases/NetworkTypes.md)
9
+ > **network**: `NetworkTypes`
10
10
 
11
11
  The network type
12
12
 
package/locales/en.json CHANGED
@@ -36,7 +36,8 @@
36
36
  "noPackageData": "No package data found for contract in compiled modules",
37
37
  "packageNotFoundOnNetwork": "Package {packageId} not found on network {network}. Please ensure the package is deployed.",
38
38
  "dryRunInvalidConfig": "Dry run failed due to invalid configuration",
39
- "noMoveTomlFilesFound": "No Move.toml files found in current directory or subdirectories. Make sure you're running this from a directory containing Move projects."
39
+ "noMoveTomlFilesFound": "No Move.toml files found in current directory or subdirectories. Make sure you're running this from a directory containing Move projects.",
40
+ "addressSwitchFailed": "Failed to switch to active address {targetAddress} for {network}. Active address: {activeAddress}"
40
41
  }
41
42
  },
42
43
  "envSetup": {
@@ -122,7 +123,7 @@
122
123
  "dryRunMode": "Dry Run Mode",
123
124
  "forceMode": "Force Mode",
124
125
  "deployedPackageId": "Deployed Package ID",
125
- "upgradeCap": "Upgrade Cap",
126
+ "upgradeCapabilityId": "Upgrade Capability ID",
126
127
  "gasUsed": "Gas Used",
127
128
  "deploymentStatus": "Deployment Status",
128
129
  "iotaEnvironmentCheck": "IOTA environment check",
@@ -139,7 +140,10 @@
139
140
  "walletAddress": "Wallet Address",
140
141
  "walletBalance": "Wallet Balance",
141
142
  "deployedPackageIdResult": "Deployed Package ID",
142
- "upgradeCapId": "UpgradeCap ID",
143
+ "upgradeCapabilityIdResult": "Upgrade Capability ID",
144
+ "migrationStateIdResult": "Migration State ID",
145
+ "importedDeployerAddress": "Imported deployer address",
146
+ "switchedActiveAddress": "Switched active address",
143
147
  "moveProjectRoot": "Move Project Root",
144
148
  "publishCommand": "Publish Command",
145
149
  "workingDirectory": "Working Directory",
@@ -152,15 +156,20 @@
152
156
  "balanceStatus": "Balance Status",
153
157
  "faucetFundsRequested": "Faucet funds requested",
154
158
  "faucetRequestFailed": "Faucet request failed for {network}: {error}",
155
- "insufficientBalanceAfterFaucet": "Insufficient balance after faucet request on {network}: {balance} IOTA < {required} IOTA for wallet {walletAddress}. Attempting deployment anyway."
159
+ "insufficientBalanceAfterFaucet": "Insufficient balance after faucet request on {network}: {balance} IOTA < {required} IOTA for wallet {walletAddress}. Attempting deployment anyway.",
160
+ "updatedWalletBalance": "Updated wallet balance",
161
+ "balanceCheck": "Balance check",
162
+ "sufficientFundsAvailable": "Sufficient funds available, skipping faucet request"
156
163
  },
157
164
  "progress": {
158
165
  "loadingContracts": "Loading compiled contracts...",
159
166
  "checkingEnvironment": "Checking IOTA CLI environment...",
160
167
  "settingEnvironment": "Setting IOTA CLI environment...",
168
+ "importingDeployerAddress": "Importing deployer address to IOTA CLI...",
161
169
  "deployingContract": "Deploying contract: {contractName} ({network})",
162
170
  "updatingContractsFile": "Updating contracts file...",
163
- "requestingFaucetFunds": "Requesting funds from {network} faucet..."
171
+ "requestingFaucetFunds": "Requesting funds from {network} faucet...",
172
+ "requestingAdditionalFaucetFunds": "Requesting additional funds from faucet..."
164
173
  },
165
174
  "messages": {},
166
175
  "section": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@twin.org/move-to-json",
3
- "version": "0.0.2-next.3",
3
+ "version": "0.0.2-next.5",
4
4
  "description": "Tool to convert Move source files to JSON",
5
5
  "repository": {
6
6
  "type": "git",
@@ -18,7 +18,7 @@
18
18
  "@twin.org/cli-core": "next",
19
19
  "@twin.org/core": "next",
20
20
  "@twin.org/crypto": "next",
21
- "@twin.org/dlt-iota": "0.0.2-next.3",
21
+ "@twin.org/dlt-iota": "0.0.2-next.5",
22
22
  "@twin.org/nameof": "next",
23
23
  "@twin.org/wallet-connector-iota": "next",
24
24
  "commander": "14.0.0",
@@ -1,21 +0,0 @@
1
- /**
2
- * Interface for contract data stored in smart-contract-deployments.json
3
- */
4
- export interface IContractData {
5
- /**
6
- * Package ID generated during build
7
- */
8
- packageId: string;
9
- /**
10
- * Base64-encoded package bytecode
11
- */
12
- package: string | string[];
13
- /**
14
- * Package ID from actual deployment
15
- */
16
- deployedPackageId?: string;
17
- /**
18
- * UpgradeCap object ID for package upgrades
19
- */
20
- upgradeCap?: string;
21
- }
@@ -1,8 +0,0 @@
1
- import type { IContractData } from "./IContractData";
2
- import type { NetworkTypes } from "./networkTypes";
3
- /**
4
- * Type for the smart-contract-deployments.json structure, mapping each network to its contract data.
5
- */
6
- export type ISmartContractDeployments = {
7
- [K in NetworkTypes]?: IContractData;
8
- };
@@ -1,21 +0,0 @@
1
- /**
2
- * Network types supported for deployment
3
- */
4
- export declare const NetworkTypes: {
5
- /**
6
- * Testnet.
7
- */
8
- readonly Testnet: "testnet";
9
- /**
10
- * Devnet.
11
- */
12
- readonly Devnet: "devnet";
13
- /**
14
- * Mainnet.
15
- */
16
- readonly Mainnet: "mainnet";
17
- };
18
- /**
19
- * Network types supported for deployment
20
- */
21
- export type NetworkTypes = (typeof NetworkTypes)[keyof typeof NetworkTypes];
@@ -1,35 +0,0 @@
1
- # Interface: IContractData
2
-
3
- Interface for contract data stored in smart-contract-deployments.json
4
-
5
- ## Properties
6
-
7
- ### packageId
8
-
9
- > **packageId**: `string`
10
-
11
- Package ID generated during build
12
-
13
- ***
14
-
15
- ### package
16
-
17
- > **package**: `string` \| `string`[]
18
-
19
- Base64-encoded package bytecode
20
-
21
- ***
22
-
23
- ### deployedPackageId?
24
-
25
- > `optional` **deployedPackageId**: `string`
26
-
27
- Package ID from actual deployment
28
-
29
- ***
30
-
31
- ### upgradeCap?
32
-
33
- > `optional` **upgradeCap**: `string`
34
-
35
- UpgradeCap object ID for package upgrades
@@ -1,5 +0,0 @@
1
- # Type Alias: ISmartContractDeployments
2
-
3
- > **ISmartContractDeployments** = `{ [K in NetworkTypes]?: IContractData }`
4
-
5
- Type for the smart-contract-deployments.json structure, mapping each network to its contract data.
@@ -1,5 +0,0 @@
1
- # Type Alias: NetworkTypes
2
-
3
- > **NetworkTypes** = *typeof* [`NetworkTypes`](../variables/NetworkTypes.md)\[keyof *typeof* [`NetworkTypes`](../variables/NetworkTypes.md)\]
4
-
5
- Network types supported for deployment
@@ -1,25 +0,0 @@
1
- # Variable: NetworkTypes
2
-
3
- > `const` **NetworkTypes**: `object`
4
-
5
- Network types supported for deployment
6
-
7
- ## Type declaration
8
-
9
- ### Testnet
10
-
11
- > `readonly` **Testnet**: `"testnet"` = `"testnet"`
12
-
13
- Testnet.
14
-
15
- ### Devnet
16
-
17
- > `readonly` **Devnet**: `"devnet"` = `"devnet"`
18
-
19
- Devnet.
20
-
21
- ### Mainnet
22
-
23
- > `readonly` **Mainnet**: `"mainnet"` = `"mainnet"`
24
-
25
- Mainnet.