@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 +55 -0
- package/dist/cjs/index.cjs +121 -62
- package/dist/esm/index.mjs +121 -61
- package/dist/locales/en.json +31 -5
- package/dist/types/commands/build.d.ts +1 -1
- package/dist/types/commands/deploy.d.ts +1 -1
- package/dist/types/index.d.ts +0 -3
- package/dist/types/models/INetworkConfig.d.ts +1 -1
- package/dist/types/utils/envSetup.d.ts +1 -1
- package/docs/changelog.md +28 -0
- package/docs/reference/functions/actionCommandBuild.md +1 -1
- package/docs/reference/functions/actionCommandDeploy.md +1 -1
- package/docs/reference/functions/getDeploymentMnemonic.md +1 -1
- package/docs/reference/functions/getDeploymentSeed.md +1 -1
- package/docs/reference/functions/validateDeploymentEnvironment.md +1 -1
- package/docs/reference/index.md +0 -10
- package/docs/reference/interfaces/INetworkConfig.md +1 -1
- package/locales/en.json +14 -5
- package/package.json +2 -2
- package/dist/types/models/IContractData.d.ts +0 -21
- package/dist/types/models/ISmartContractDeployments.d.ts +0 -8
- package/dist/types/models/networkTypes.d.ts +0 -21
- package/docs/reference/interfaces/IContractData.md +0 -35
- package/docs/reference/type-aliases/ISmartContractDeployments.md +0 -5
- package/docs/reference/type-aliases/NetworkTypes.md +0 -5
- package/docs/reference/variables/NetworkTypes.md +0 -25
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)
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -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.
|
|
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
|
-
//
|
|
369
|
-
|
|
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 ===
|
|
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 ===
|
|
532
|
+
if (network === dltIota.NetworkTypes.Mainnet) {
|
|
526
533
|
await validateDeploymentEnvironment(network);
|
|
527
534
|
}
|
|
528
|
-
else if ((network ===
|
|
529
|
-
// For testnet/devnet, request funds
|
|
530
|
-
await
|
|
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
|
|
545
|
-
const
|
|
546
|
-
const
|
|
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), `${
|
|
554
|
-
cliCore.CLIDisplay.value(core.I18n.formatMessage(gasBudgetLabel), `${
|
|
555
|
-
// Handle insufficient balance based on network and run type
|
|
556
|
-
if (
|
|
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:
|
|
560
|
-
requiredBalance:
|
|
568
|
+
currentBalance: balanceInIota.toFixed(2),
|
|
569
|
+
requiredBalance: requiredInIota.toFixed(2)
|
|
561
570
|
}), 2);
|
|
562
571
|
}
|
|
563
|
-
else if (network ===
|
|
572
|
+
else if (network === dltIota.NetworkTypes.Mainnet) {
|
|
564
573
|
throw new core.GeneralError("commands", "commands.deploy.insufficientBalance", {
|
|
565
|
-
balance:
|
|
566
|
-
required:
|
|
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:
|
|
575
|
-
required:
|
|
583
|
+
balance: balanceInIota.toFixed(2),
|
|
584
|
+
required: requiredInIota.toFixed(2),
|
|
576
585
|
walletAddress
|
|
577
586
|
}), 2);
|
|
578
587
|
}
|
|
579
588
|
}
|
|
580
|
-
return
|
|
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.
|
|
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.
|
|
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.
|
|
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 !==
|
|
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
|
|
699
|
-
const
|
|
700
|
-
if (
|
|
701
|
-
const
|
|
702
|
-
cliCore.CLIDisplay.value(core.I18n.formatMessage("commands.deploy.labels.faucetFundsRequested"), `${
|
|
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(
|
|
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.
|
|
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;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -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.
|
|
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
|
-
//
|
|
366
|
-
|
|
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 ===
|
|
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 ===
|
|
529
|
+
if (network === NetworkTypes.Mainnet) {
|
|
523
530
|
await validateDeploymentEnvironment(network);
|
|
524
531
|
}
|
|
525
|
-
else if ((network ===
|
|
526
|
-
// For testnet/devnet, request funds
|
|
527
|
-
await
|
|
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
|
|
542
|
-
const
|
|
543
|
-
const
|
|
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), `${
|
|
551
|
-
CLIDisplay.value(I18n.formatMessage(gasBudgetLabel), `${
|
|
552
|
-
// Handle insufficient balance based on network and run type
|
|
553
|
-
if (
|
|
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:
|
|
557
|
-
requiredBalance:
|
|
565
|
+
currentBalance: balanceInIota.toFixed(2),
|
|
566
|
+
requiredBalance: requiredInIota.toFixed(2)
|
|
558
567
|
}), 2);
|
|
559
568
|
}
|
|
560
|
-
else if (network ===
|
|
569
|
+
else if (network === NetworkTypes.Mainnet) {
|
|
561
570
|
throw new GeneralError("commands", "commands.deploy.insufficientBalance", {
|
|
562
|
-
balance:
|
|
563
|
-
required:
|
|
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:
|
|
572
|
-
required:
|
|
580
|
+
balance: balanceInIota.toFixed(2),
|
|
581
|
+
required: requiredInIota.toFixed(2),
|
|
573
582
|
walletAddress
|
|
574
583
|
}), 2);
|
|
575
584
|
}
|
|
576
585
|
}
|
|
577
|
-
return
|
|
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.
|
|
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.
|
|
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.
|
|
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 !==
|
|
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
|
|
696
|
-
const
|
|
697
|
-
if (
|
|
698
|
-
const
|
|
699
|
-
CLIDisplay.value(I18n.formatMessage("commands.deploy.labels.faucetFundsRequested"), `${
|
|
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(
|
|
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.
|
|
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,
|
|
850
|
+
export { CLI, actionCommandBuild, actionCommandDeploy, buildCommandBuild, buildCommandDeploy, getDeploymentMnemonic, getDeploymentSeed, searchDirectoryForMoveToml, validateDeploymentEnvironment, verifyIotaSDK };
|
package/dist/locales/en.json
CHANGED
|
@@ -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
|
-
"
|
|
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
|
-
"
|
|
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": {
|
package/dist/types/index.d.ts
CHANGED
|
@@ -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";
|
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
|
|
package/docs/reference/index.md
CHANGED
|
@@ -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)
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
+
"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.
|
|
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,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.
|