@vultisig/cli 0.2.0 → 0.3.0

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.
Files changed (3) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/index.js +122 -21
  3. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # @vultisig/cli
2
2
 
3
+ ## 0.3.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#71](https://github.com/vultisig/vultisig-sdk/pull/71) [`cc4e5fd`](https://github.com/vultisig/vultisig-sdk/commit/cc4e5fd2ff83bcce1723435107af869a43ea069f) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Update CLI to support SDK vault creation API changes
8
+
9
+ **Breaking Changes:**
10
+ - Renamed `import-seedphrase` command to `create-from-seedphrase` to match SDK naming
11
+ - `vultisig import-seedphrase fast` → `vultisig create-from-seedphrase fast`
12
+ - `vultisig import-seedphrase secure` → `vultisig create-from-seedphrase secure`
13
+
14
+ **New Features:**
15
+ - Added `join secure` command to join existing SecureVault creation sessions
16
+ - Supports QR payload via `--qr`, `--qr-file`, or interactive prompt
17
+ - Auto-detects if mnemonic is required based on session type
18
+ - Example: `vultisig join secure --qr "vultisig://..."`
19
+
20
+ **Internal Changes:**
21
+ - Updated SDK API calls to use new method names:
22
+ - `importSeedphraseAsFastVault` → `createFastVaultFromSeedphrase`
23
+ - `importSeedphraseAsSecureVault` → `createSecureVaultFromSeedphrase`
24
+ - Renamed internal functions and types to match SDK naming conventions
25
+
26
+ ### Patch Changes
27
+
28
+ - [#71](https://github.com/vultisig/vultisig-sdk/pull/71) [`fee3f37`](https://github.com/vultisig/vultisig-sdk/commit/fee3f375f85011d14be814f06ff3d7f6684ea2fe) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - fix: address CodeRabbit PR #71 review suggestions
29
+
30
+ **Critical fixes:**
31
+ - JoinSecureVaultService: require `devices` parameter instead of defaulting to 2
32
+ - CLI vault-management: validate `devices` parameter before calling SDK
33
+ - parseKeygenQR: throw error on unknown libType instead of silently defaulting
34
+
35
+ **Code quality:**
36
+ - Replace try-catch with attempt() pattern in JoinSecureVaultService and parseKeygenQR
37
+ - Add abort signal checks in SecureVaultJoiner callbacks
38
+
39
+ **Documentation:**
40
+ - Add onProgress callback to joinSecureVault README documentation
41
+ - Fix markdown heading format in SDK-USERS-GUIDE.md
42
+ - Add language specifier to code block in CLAUDE.md
43
+
44
+ **Tests:**
45
+ - Fix Korean test mnemonic (removed invalid comma)
46
+ - Add Korean language detection test
47
+ - Remove sensitive private key logging in test helpers
48
+
49
+ - Updated dependencies [[`fee3f37`](https://github.com/vultisig/vultisig-sdk/commit/fee3f375f85011d14be814f06ff3d7f6684ea2fe), [`695e664`](https://github.com/vultisig/vultisig-sdk/commit/695e664668082ca55861cf4d8fcc8c323be94c06), [`4edf52d`](https://github.com/vultisig/vultisig-sdk/commit/4edf52d3a2985d2adf772239bf19b8301f360af8), [`d145809`](https://github.com/vultisig/vultisig-sdk/commit/d145809eb68653a3b22921fcb90ebc985de2b16a)]:
50
+ - @vultisig/sdk@0.3.0
51
+
3
52
  ## 0.2.0
4
53
 
5
54
  ### Minor Changes
package/dist/index.js CHANGED
@@ -1103,9 +1103,10 @@ var require_main = __commonJS({
1103
1103
 
1104
1104
  // src/index.ts
1105
1105
  import "dotenv/config";
1106
- import { Vultisig as Vultisig4 } from "@vultisig/sdk";
1106
+ import { parseKeygenQR, Vultisig as Vultisig4 } from "@vultisig/sdk";
1107
1107
  import chalk12 from "chalk";
1108
1108
  import { program } from "commander";
1109
+ import { promises as fs3 } from "fs";
1109
1110
  import inquirer8 from "inquirer";
1110
1111
 
1111
1112
  // src/core/command-context.ts
@@ -2506,7 +2507,7 @@ async function executeInfo(ctx2) {
2506
2507
  }
2507
2508
  displayVaultInfo(vault);
2508
2509
  }
2509
- async function executeImportSeedphraseFast(ctx2, options) {
2510
+ async function executeCreateFromSeedphraseFast(ctx2, options) {
2510
2511
  const { mnemonic, name, password, email, discoverChains, chains, signal } = options;
2511
2512
  const validateSpinner = createSpinner("Validating seedphrase...");
2512
2513
  const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
@@ -2539,7 +2540,7 @@ async function executeImportSeedphraseFast(ctx2, options) {
2539
2540
  }
2540
2541
  const importSpinner = createSpinner("Importing seedphrase...");
2541
2542
  const vaultId = await withAbortSignal(
2542
- ctx2.sdk.importSeedphraseAsFastVault({
2543
+ ctx2.sdk.createFastVaultFromSeedphrase({
2543
2544
  mnemonic,
2544
2545
  name,
2545
2546
  password,
@@ -2573,7 +2574,7 @@ async function executeImportSeedphraseFast(ctx2, options) {
2573
2574
  verifySpinner.succeed("Email verified successfully!");
2574
2575
  setupVaultEvents(vault);
2575
2576
  await ctx2.setActiveVault(vault);
2576
- success("\n+ Vault imported from seedphrase!");
2577
+ success("\n+ Vault created from seedphrase!");
2577
2578
  info("\nYour vault is ready. Run the following commands:");
2578
2579
  printResult(chalk5.cyan(" vultisig balance ") + "- View balances");
2579
2580
  printResult(chalk5.cyan(" vultisig addresses ") + "- View addresses");
@@ -2623,7 +2624,7 @@ async function executeImportSeedphraseFast(ctx2, options) {
2623
2624
  }
2624
2625
  throw new Error("Verification loop exited unexpectedly");
2625
2626
  }
2626
- async function executeImportSeedphraseSecure(ctx2, options) {
2627
+ async function executeCreateFromSeedphraseSecure(ctx2, options) {
2627
2628
  const { mnemonic, name, password, threshold, shares: totalShares, discoverChains, chains, signal } = options;
2628
2629
  const validateSpinner = createSpinner("Validating seedphrase...");
2629
2630
  const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
@@ -2657,7 +2658,7 @@ async function executeImportSeedphraseSecure(ctx2, options) {
2657
2658
  const importSpinner = createSpinner("Importing seedphrase as secure vault...");
2658
2659
  try {
2659
2660
  const result = await withAbortSignal(
2660
- ctx2.sdk.importSeedphraseAsSecureVault({
2661
+ ctx2.sdk.createSecureVaultFromSeedphrase({
2661
2662
  mnemonic,
2662
2663
  name,
2663
2664
  password,
@@ -2713,16 +2714,62 @@ Or use this URL: ${qrPayload}
2713
2714
  warn(`
2714
2715
  Important: Save your vault backup file (.vult) in a secure location.`);
2715
2716
  warn(`This is a ${threshold}-of-${totalShares} vault. You'll need ${threshold} devices to sign transactions.`);
2716
- success("\n+ Vault imported from seedphrase!");
2717
+ success("\n+ Vault created from seedphrase!");
2717
2718
  return result.vault;
2718
2719
  } catch (err) {
2719
- importSpinner.fail("Secure vault import failed");
2720
+ importSpinner.fail("Secure vault creation failed");
2720
2721
  if (err.message?.includes("not implemented")) {
2721
- warn("\nSecure vault seedphrase import is not yet implemented in the SDK");
2722
+ warn("\nSecure vault creation from seedphrase is not yet implemented in the SDK");
2722
2723
  }
2723
2724
  throw err;
2724
2725
  }
2725
2726
  }
2727
+ async function executeJoinSecure(ctx2, options) {
2728
+ const { qrPayload, mnemonic, password, devices, signal } = options;
2729
+ if (!devices || devices < 2) {
2730
+ throw new Error("devices is required when joining a SecureVault (minimum 2)");
2731
+ }
2732
+ const spinner = createSpinner("Joining SecureVault session...");
2733
+ try {
2734
+ const result = await withAbortSignal(
2735
+ ctx2.sdk.joinSecureVault(qrPayload, {
2736
+ mnemonic,
2737
+ password,
2738
+ devices,
2739
+ onProgress: (step) => {
2740
+ spinner.text = `${step.message} (${step.progress}%)`;
2741
+ },
2742
+ onDeviceJoined: (deviceId, totalJoined, required) => {
2743
+ if (!isSilent()) {
2744
+ spinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
2745
+ } else if (!isJsonOutput()) {
2746
+ printResult(`Device joined: ${totalJoined}/${required}`);
2747
+ }
2748
+ }
2749
+ }),
2750
+ signal
2751
+ );
2752
+ setupVaultEvents(result.vault);
2753
+ await ctx2.setActiveVault(result.vault);
2754
+ spinner.succeed(`Joined SecureVault: ${result.vault.name}`);
2755
+ if (isJsonOutput()) {
2756
+ outputJson({
2757
+ vault: {
2758
+ id: result.vaultId,
2759
+ name: result.vault.name,
2760
+ type: "secure"
2761
+ }
2762
+ });
2763
+ return result.vault;
2764
+ }
2765
+ warn("\nImportant: Save your vault backup file (.vult) in a secure location.");
2766
+ success("\n+ Successfully joined vault!");
2767
+ return result.vault;
2768
+ } catch (err) {
2769
+ spinner.fail("Failed to join SecureVault session");
2770
+ throw err;
2771
+ }
2772
+ }
2726
2773
 
2727
2774
  // src/commands/swap.ts
2728
2775
  async function executeSwapChains(ctx2) {
@@ -3040,8 +3087,9 @@ var COMMANDS = [
3040
3087
  "vaults",
3041
3088
  "vault",
3042
3089
  "import",
3043
- "import-seedphrase",
3090
+ "create-from-seedphrase",
3044
3091
  "create",
3092
+ "join",
3045
3093
  "info",
3046
3094
  "export",
3047
3095
  // Wallet operations
@@ -3099,13 +3147,20 @@ function createCompleter(ctx2) {
3099
3147
  const partial = parts[1] || "";
3100
3148
  return completeChainName(partial);
3101
3149
  }
3102
- if ((command === "create" || command === "import-seedphrase") && parts.length === 2) {
3150
+ if ((command === "create" || command === "create-from-seedphrase") && parts.length === 2) {
3103
3151
  const types = ["fast", "secure"];
3104
3152
  const partial = parts[1] || "";
3105
3153
  const partialLower = partial.toLowerCase();
3106
3154
  const matches = types.filter((t) => t.startsWith(partialLower));
3107
3155
  return [matches.length ? matches : types, partial];
3108
3156
  }
3157
+ if (command === "join" && parts.length === 2) {
3158
+ const types = ["secure"];
3159
+ const partial = parts[1] || "";
3160
+ const partialLower = partial.toLowerCase();
3161
+ const matches = types.filter((t) => t.startsWith(partialLower));
3162
+ return [matches.length ? matches : types, partial];
3163
+ }
3109
3164
  const hits = COMMANDS.filter((c) => c.startsWith(line));
3110
3165
  const show = hits.length ? hits : COMMANDS;
3111
3166
  return [show, line];
@@ -3863,7 +3918,7 @@ Error: ${error2.message}`));
3863
3918
  case "create":
3864
3919
  await this.createVault(args);
3865
3920
  break;
3866
- case "import-seedphrase":
3921
+ case "create-from-seedphrase":
3867
3922
  await this.importSeedphrase(args);
3868
3923
  break;
3869
3924
  case "info":
@@ -4049,7 +4104,7 @@ Error: ${error2.message}`));
4049
4104
  async importSeedphrase(args) {
4050
4105
  const type = args[0]?.toLowerCase();
4051
4106
  if (!type || type !== "fast" && type !== "secure") {
4052
- console.log(chalk9.cyan("Usage: import-seedphrase <fast|secure>"));
4107
+ console.log(chalk9.cyan("Usage: create-from-seedphrase <fast|secure>"));
4053
4108
  console.log(chalk9.gray(" fast - Import with VultiServer (2-of-2)"));
4054
4109
  console.log(chalk9.gray(" secure - Import with device coordination (N-of-M)"));
4055
4110
  return;
@@ -4085,7 +4140,7 @@ Error: ${error2.message}`));
4085
4140
  const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
4086
4141
  const discoverChains = discoverStr.toLowerCase() === "y";
4087
4142
  vault = await this.withCancellation(
4088
- (signal) => executeImportSeedphraseFast(this.ctx, {
4143
+ (signal) => executeCreateFromSeedphraseFast(this.ctx, {
4089
4144
  mnemonic,
4090
4145
  name,
4091
4146
  password,
@@ -4116,7 +4171,7 @@ Error: ${error2.message}`));
4116
4171
  const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
4117
4172
  const discoverChains = discoverStr.toLowerCase() === "y";
4118
4173
  vault = await this.withCancellation(
4119
- (signal) => executeImportSeedphraseSecure(this.ctx, {
4174
+ (signal) => executeCreateFromSeedphraseSecure(this.ctx, {
4120
4175
  mnemonic,
4121
4176
  name,
4122
4177
  password: password || void 0,
@@ -4387,7 +4442,7 @@ var cachedVersion = null;
4387
4442
  function getVersion() {
4388
4443
  if (cachedVersion) return cachedVersion;
4389
4444
  if (true) {
4390
- cachedVersion = "0.2.0";
4445
+ cachedVersion = "0.3.0";
4391
4446
  return cachedVersion;
4392
4447
  }
4393
4448
  try {
@@ -4906,7 +4961,7 @@ program.command("import <file>").description("Import vault from .vult file").act
4906
4961
  await executeImport(context, file);
4907
4962
  })
4908
4963
  );
4909
- var importSeedphraseCmd = program.command("import-seedphrase").description("Import wallet from BIP39 seedphrase");
4964
+ var createFromSeedphraseCmd = program.command("create-from-seedphrase").description("Create vault from BIP39 seedphrase");
4910
4965
  async function promptSeedphrase() {
4911
4966
  info("\nEnter your 12 or 24-word recovery phrase.");
4912
4967
  info("Words will be hidden as you type.\n");
@@ -4927,7 +4982,26 @@ async function promptSeedphrase() {
4927
4982
  ]);
4928
4983
  return answer.mnemonic.trim().toLowerCase();
4929
4984
  }
4930
- importSeedphraseCmd.command("fast").description("Import as FastVault (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--mnemonic <words>", "Seedphrase (12 or 24 words, space-separated)").option("--discover-chains", "Scan chains for existing balances").option("--chains <chains>", "Specific chains to enable (comma-separated)").action(
4985
+ async function promptQrPayload() {
4986
+ info("\nEnter the QR code payload from the initiator device.");
4987
+ info('The payload starts with "vultisig://".\n');
4988
+ const answer = await inquirer8.prompt([
4989
+ {
4990
+ type: "input",
4991
+ name: "qrPayload",
4992
+ message: "QR Payload:",
4993
+ validate: (input) => {
4994
+ const trimmed = input.trim();
4995
+ if (!trimmed.startsWith("vultisig://")) {
4996
+ return 'QR payload must start with "vultisig://"';
4997
+ }
4998
+ return true;
4999
+ }
5000
+ }
5001
+ ]);
5002
+ return answer.qrPayload.trim();
5003
+ }
5004
+ createFromSeedphraseCmd.command("fast").description("Create FastVault from seedphrase (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--mnemonic <words>", "Seedphrase (12 or 24 words, space-separated)").option("--discover-chains", "Scan chains for existing balances").option("--chains <chains>", "Specific chains to enable (comma-separated)").action(
4931
5005
  withExit(
4932
5006
  async (options) => {
4933
5007
  const context = await init(program.opts().vault);
@@ -4948,7 +5022,7 @@ importSeedphraseCmd.command("fast").description("Import as FastVault (server-ass
4948
5022
  }
4949
5023
  }
4950
5024
  }
4951
- await executeImportSeedphraseFast(context, {
5025
+ await executeCreateFromSeedphraseFast(context, {
4952
5026
  mnemonic,
4953
5027
  name: options.name,
4954
5028
  password: options.password,
@@ -4959,7 +5033,7 @@ importSeedphraseCmd.command("fast").description("Import as FastVault (server-ass
4959
5033
  }
4960
5034
  )
4961
5035
  );
4962
- importSeedphraseCmd.command("secure").description("Import as SecureVault (multi-device MPC)").requiredOption("--name <name>", "Vault name").option("--password <password>", "Vault password (optional)").option("--threshold <m>", "Signing threshold", "2").option("--shares <n>", "Total shares", "3").option("--mnemonic <words>", "Seedphrase (12 or 24 words)").option("--discover-chains", "Scan chains for existing balances").option("--chains <chains>", "Specific chains to enable (comma-separated)").action(
5036
+ createFromSeedphraseCmd.command("secure").description("Create SecureVault from seedphrase (multi-device MPC)").requiredOption("--name <name>", "Vault name").option("--password <password>", "Vault password (optional)").option("--threshold <m>", "Signing threshold", "2").option("--shares <n>", "Total shares", "3").option("--mnemonic <words>", "Seedphrase (12 or 24 words)").option("--discover-chains", "Scan chains for existing balances").option("--chains <chains>", "Specific chains to enable (comma-separated)").action(
4963
5037
  withExit(
4964
5038
  async (options) => {
4965
5039
  const context = await init(program.opts().vault);
@@ -4980,7 +5054,7 @@ importSeedphraseCmd.command("secure").description("Import as SecureVault (multi-
4980
5054
  }
4981
5055
  }
4982
5056
  }
4983
- await executeImportSeedphraseSecure(context, {
5057
+ await executeCreateFromSeedphraseSecure(context, {
4984
5058
  mnemonic,
4985
5059
  name: options.name,
4986
5060
  password: options.password,
@@ -4992,6 +5066,33 @@ importSeedphraseCmd.command("secure").description("Import as SecureVault (multi-
4992
5066
  }
4993
5067
  )
4994
5068
  );
5069
+ var joinCmd = program.command("join").description("Join an existing vault creation session");
5070
+ joinCmd.command("secure").description("Join a SecureVault creation session").option("--qr <payload>", "QR code payload from initiator (vultisig://...)").option("--qr-file <path>", "Read QR payload from file").option("--mnemonic <words>", "Seedphrase (required for seedphrase-based sessions)").option("--password <password>", "Vault password (optional)").option("--devices <n>", "Total devices in session", "2").action(
5071
+ withExit(
5072
+ async (options) => {
5073
+ const context = await init(program.opts().vault);
5074
+ let qrPayload = options.qr;
5075
+ if (!qrPayload && options.qrFile) {
5076
+ qrPayload = (await fs3.readFile(options.qrFile, "utf-8")).trim();
5077
+ }
5078
+ if (!qrPayload) {
5079
+ qrPayload = await promptQrPayload();
5080
+ }
5081
+ const qrParams = await parseKeygenQR(qrPayload);
5082
+ let mnemonic = options.mnemonic;
5083
+ if (qrParams.libType === "KEYIMPORT" && !mnemonic) {
5084
+ info("\nThis session requires a seedphrase to join.");
5085
+ mnemonic = await promptSeedphrase();
5086
+ }
5087
+ await executeJoinSecure(context, {
5088
+ qrPayload,
5089
+ mnemonic,
5090
+ password: options.password,
5091
+ devices: parseInt(options.devices, 10)
5092
+ });
5093
+ }
5094
+ )
5095
+ );
4995
5096
  program.command("verify <vaultId>").description("Verify vault with email verification code").option("-r, --resend", "Resend verification email").option("--code <code>", "Verification code").option("--email <email>", "Email address (required for --resend)").option("--password <password>", "Vault password (required for --resend)").action(
4996
5097
  withExit(
4997
5098
  async (vaultId, options) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vultisig/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Command-line wallet for Vultisig - multi-chain MPC wallet management",
5
5
  "type": "module",
6
6
  "bin": {
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "homepage": "https://vultisig.com",
50
50
  "dependencies": {
51
- "@vultisig/sdk": "^0.2.0",
51
+ "@vultisig/sdk": "^0.3.0",
52
52
  "chalk": "^5.3.0",
53
53
  "cli-table3": "^0.6.5",
54
54
  "commander": "^12.0.0",