@vultisig/cli 0.2.0-beta.9 → 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.
- package/CHANGELOG.md +167 -0
- package/README.md +67 -0
- package/dist/index.js +551 -15
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,172 @@
|
|
|
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
|
+
|
|
52
|
+
## 0.2.0
|
|
53
|
+
|
|
54
|
+
### Minor Changes
|
|
55
|
+
|
|
56
|
+
- [#64](https://github.com/vultisig/vultisig-sdk/pull/64) [`a36a7f6`](https://github.com/vultisig/vultisig-sdk/commit/a36a7f614c03e32ebc7e843cbf1ab30b6be0d4af) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - feat(sdk): add broadcastRawTx() for broadcasting pre-signed transactions
|
|
57
|
+
|
|
58
|
+
Adds `broadcastRawTx()` method supporting all chain families:
|
|
59
|
+
- EVM: Ethereum, Polygon, BSC, Arbitrum, Base, etc. (hex-encoded)
|
|
60
|
+
- UTXO: Bitcoin, Litecoin, Dogecoin, etc. (hex-encoded)
|
|
61
|
+
- Solana: Base58 or Base64 encoded transaction bytes
|
|
62
|
+
- Cosmos: JSON `{tx_bytes}` or raw base64 protobuf (10 chains)
|
|
63
|
+
- TON: BOC as base64 string
|
|
64
|
+
- Polkadot: Hex-encoded extrinsic
|
|
65
|
+
- Ripple: Hex-encoded transaction blob
|
|
66
|
+
- Sui: JSON `{unsignedTx, signature}`
|
|
67
|
+
- Tron: JSON transaction object
|
|
68
|
+
|
|
69
|
+
CLI commands added:
|
|
70
|
+
- `vultisig sign --chain <chain> --bytes <base64>` - sign pre-hashed data
|
|
71
|
+
- `vultisig broadcast --chain <chain> --raw-tx <data>` - broadcast raw tx
|
|
72
|
+
|
|
73
|
+
Documentation updated with complete workflow examples for EVM, UTXO, Solana, and Sui.
|
|
74
|
+
|
|
75
|
+
- [#62](https://github.com/vultisig/vultisig-sdk/pull/62) [`008db7f`](https://github.com/vultisig/vultisig-sdk/commit/008db7fb27580ec78df3bbc41b25aac24924ffd8) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - feat: separate unlock and export passwords in CLI export command
|
|
76
|
+
|
|
77
|
+
The export command now has two distinct password options:
|
|
78
|
+
- `--password`: Unlocks the vault (decrypts stored keyshares for encrypted vaults)
|
|
79
|
+
- `--exportPassword`: Encrypts the exported file (defaults to `--password` if not specified)
|
|
80
|
+
|
|
81
|
+
This fixes the "Password required but callback returned empty value" error when exporting encrypted vaults.
|
|
82
|
+
|
|
83
|
+
Password resolution now uses an in-memory cache that persists across SDK callbacks, allowing the CLI to pre-cache the unlock password before vault loading.
|
|
84
|
+
|
|
85
|
+
- [#60](https://github.com/vultisig/vultisig-sdk/pull/60) [`b4cf357`](https://github.com/vultisig/vultisig-sdk/commit/b4cf357c98ef493b48c807e5bb45cd40b9893295) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - feat: Add SecureVault support for multi-device MPC vaults
|
|
86
|
+
- Implement SecureVault.create() for multi-device keygen ceremony
|
|
87
|
+
- Add RelaySigningService for coordinated signing via relay server
|
|
88
|
+
- Implement SecureVault.sign() and signBytes() methods
|
|
89
|
+
- Add QR code generation for mobile app pairing (compatible with Vultisig iOS/Android)
|
|
90
|
+
- CLI: Add `vault create --type secure` with terminal QR display
|
|
91
|
+
- CLI: Support secure vault signing with device coordination
|
|
92
|
+
- Add comprehensive unit, integration, and E2E tests
|
|
93
|
+
|
|
94
|
+
- [#68](https://github.com/vultisig/vultisig-sdk/pull/68) [`7979f3c`](https://github.com/vultisig/vultisig-sdk/commit/7979f3c502ea04db3c3de551bee297b8a9f9808b) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Add seedphrase (BIP39 mnemonic) import functionality
|
|
95
|
+
|
|
96
|
+
This release adds the ability to import existing wallets from BIP39 mnemonic phrases (12 or 24 words) into Vultisig vaults, mirroring the iOS implementation.
|
|
97
|
+
|
|
98
|
+
**New SDK Methods:**
|
|
99
|
+
- `sdk.validateSeedphrase()` - Validate a BIP39 mnemonic phrase
|
|
100
|
+
- `sdk.discoverChainsFromSeedphrase()` - Discover chains with balances before import
|
|
101
|
+
- `sdk.importSeedphraseAsFastVault()` - Import as FastVault (2-of-2 with VultiServer)
|
|
102
|
+
- `sdk.importSeedphraseAsSecureVault()` - Import as SecureVault (N-of-M multi-device)
|
|
103
|
+
|
|
104
|
+
**Features:**
|
|
105
|
+
- Chain discovery with progress callbacks to find existing balances
|
|
106
|
+
- Auto-enable chains with balances during import
|
|
107
|
+
- EdDSA key transformation using SHA-512 clamping for Schnorr TSS compatibility
|
|
108
|
+
- Full ECDSA (secp256k1) and EdDSA (ed25519) master key derivation
|
|
109
|
+
|
|
110
|
+
**New exported types:**
|
|
111
|
+
- `SeedphraseValidation`, `ChainDiscoveryProgress`, `ChainDiscoveryResult`
|
|
112
|
+
- `ChainDiscoveryPhase`, `DerivedMasterKeys`
|
|
113
|
+
- `ImportSeedphraseAsFastVaultOptions`, `ImportSeedphraseAsSecureVaultOptions`
|
|
114
|
+
- `SeedphraseImportResult`
|
|
115
|
+
|
|
116
|
+
**New services:**
|
|
117
|
+
- `SeedphraseValidator` - BIP39 validation using WalletCore
|
|
118
|
+
- `MasterKeyDeriver` - Key derivation from mnemonic
|
|
119
|
+
- `ChainDiscoveryService` - Balance scanning across chains
|
|
120
|
+
- `FastVaultSeedphraseImportService` - FastVault import orchestration
|
|
121
|
+
- `SecureVaultSeedphraseImportService` - SecureVault import orchestration
|
|
122
|
+
|
|
123
|
+
**New CLI Commands:**
|
|
124
|
+
- `vultisig import-seedphrase fast` - Import as FastVault (2-of-2 with VultiServer)
|
|
125
|
+
- `vultisig import-seedphrase secure` - Import as SecureVault (N-of-M multi-device)
|
|
126
|
+
|
|
127
|
+
**CLI Features:**
|
|
128
|
+
- Secure seedphrase input (masked with `*`)
|
|
129
|
+
- `--discover-chains` flag to scan for existing balances
|
|
130
|
+
- `--chains` flag to specify chains (comma-separated)
|
|
131
|
+
- Interactive shell support with tab completion
|
|
132
|
+
- Progress spinners during import
|
|
133
|
+
|
|
134
|
+
### Patch Changes
|
|
135
|
+
|
|
136
|
+
- [#57](https://github.com/vultisig/vultisig-sdk/pull/57) [`22bb16b`](https://github.com/vultisig/vultisig-sdk/commit/22bb16be8421a51aa32da6c1166539015380651e) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Optimize SDK bundling configuration
|
|
137
|
+
- Add terser minification (~60% bundle size reduction)
|
|
138
|
+
- Add clean script to remove stale dist files before builds
|
|
139
|
+
- Centralize duplicated onwarn handler in rollup config
|
|
140
|
+
- Add package.json exports for react-native and electron platforms
|
|
141
|
+
|
|
142
|
+
- [`cc96f64`](https://github.com/vultisig/vultisig-sdk/commit/cc96f64622a651eb6156f279afbbfe0aa4219179) - fix: re-release as alpha (0.1.0 was accidentally published as stable)
|
|
143
|
+
|
|
144
|
+
- [#55](https://github.com/vultisig/vultisig-sdk/pull/55) [`95ba10b`](https://github.com/vultisig/vultisig-sdk/commit/95ba10baf2dc2dc4ba8e48825f10f34ec275a73c) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Replace development command references (`npm run wallet`) with production CLI name (`vultisig`) in all user-facing messages.
|
|
145
|
+
|
|
146
|
+
- [#55](https://github.com/vultisig/vultisig-sdk/pull/55) [`95ba10b`](https://github.com/vultisig/vultisig-sdk/commit/95ba10baf2dc2dc4ba8e48825f10f34ec275a73c) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Fix interactive shell prompts by replacing REPL with readline to prevent stdin conflicts with inquirer
|
|
147
|
+
|
|
148
|
+
- [#58](https://github.com/vultisig/vultisig-sdk/pull/58) [`c9b7d88`](https://github.com/vultisig/vultisig-sdk/commit/c9b7d888e21e9db1b928ddc929294aa15157e476) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Fix password prompt being swallowed by spinner during signing
|
|
149
|
+
- Add `--password` option to `send` and `swap` commands for non-interactive use
|
|
150
|
+
- Pre-unlock vault before signing spinner starts to prevent prompt interference
|
|
151
|
+
- Password prompt now appears before spinner when not provided via CLI flag
|
|
152
|
+
|
|
153
|
+
- [`9dcfb8b`](https://github.com/vultisig/vultisig-sdk/commit/9dcfb8b4b29de73b1301f791b50dc417a8f899f3) - fix: use yarn npm publish to properly resolve workspace protocols
|
|
154
|
+
|
|
155
|
+
- [`0985a37`](https://github.com/vultisig/vultisig-sdk/commit/0985a375c6009e2550231d759e84b576454ce759) - fix: use workspace:^ for SDK dependency to resolve correctly when publishing
|
|
156
|
+
|
|
157
|
+
- [#62](https://github.com/vultisig/vultisig-sdk/pull/62) [`008db7f`](https://github.com/vultisig/vultisig-sdk/commit/008db7fb27580ec78df3bbc41b25aac24924ffd8) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Simplify export command by removing `--encrypt` and `--no-encrypt` flags. Password is now optional - if provided, vault is encrypted; if omitted or empty, vault is exported without encryption. Path argument now supports directories (appends SDK-generated filename).
|
|
158
|
+
|
|
159
|
+
- [#55](https://github.com/vultisig/vultisig-sdk/pull/55) [`95ba10b`](https://github.com/vultisig/vultisig-sdk/commit/95ba10baf2dc2dc4ba8e48825f10f34ec275a73c) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Update browser example and CLI for new fast vault creation API
|
|
160
|
+
- Updated to use new `createFastVault()` that returns just the vaultId
|
|
161
|
+
- Updated to use new `verifyVault()` that returns the FastVault
|
|
162
|
+
- Removed `code` from CLI `CreateVaultOptions` (verification code always prompted interactively)
|
|
163
|
+
- Removed `--code` option from CLI create command
|
|
164
|
+
|
|
165
|
+
- [#55](https://github.com/vultisig/vultisig-sdk/pull/55) [`95ba10b`](https://github.com/vultisig/vultisig-sdk/commit/95ba10baf2dc2dc4ba8e48825f10f34ec275a73c) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Add inline verification code retry during vault creation. When entering an incorrect code, users can now retry, resend the verification email, or abort gracefully instead of being kicked back to the main menu.
|
|
166
|
+
|
|
167
|
+
- Updated dependencies [[`7979f3c`](https://github.com/vultisig/vultisig-sdk/commit/7979f3c502ea04db3c3de551bee297b8a9f9808b), [`7f60cd5`](https://github.com/vultisig/vultisig-sdk/commit/7f60cd5835510bd9110d6382cf7d03bf1d5e04ff), [`a36a7f6`](https://github.com/vultisig/vultisig-sdk/commit/a36a7f614c03e32ebc7e843cbf1ab30b6be0d4af), [`22bb16b`](https://github.com/vultisig/vultisig-sdk/commit/22bb16be8421a51aa32da6c1166539015380651e), [`7979f3c`](https://github.com/vultisig/vultisig-sdk/commit/7979f3c502ea04db3c3de551bee297b8a9f9808b), [`95ba10b`](https://github.com/vultisig/vultisig-sdk/commit/95ba10baf2dc2dc4ba8e48825f10f34ec275a73c), [`cc96f64`](https://github.com/vultisig/vultisig-sdk/commit/cc96f64622a651eb6156f279afbbfe0aa4219179), [`7979f3c`](https://github.com/vultisig/vultisig-sdk/commit/7979f3c502ea04db3c3de551bee297b8a9f9808b), [`7979f3c`](https://github.com/vultisig/vultisig-sdk/commit/7979f3c502ea04db3c3de551bee297b8a9f9808b), [`22bb16b`](https://github.com/vultisig/vultisig-sdk/commit/22bb16be8421a51aa32da6c1166539015380651e), [`7979f3c`](https://github.com/vultisig/vultisig-sdk/commit/7979f3c502ea04db3c3de551bee297b8a9f9808b), [`008db7f`](https://github.com/vultisig/vultisig-sdk/commit/008db7fb27580ec78df3bbc41b25aac24924ffd8), [`7979f3c`](https://github.com/vultisig/vultisig-sdk/commit/7979f3c502ea04db3c3de551bee297b8a9f9808b), [`b4cf357`](https://github.com/vultisig/vultisig-sdk/commit/b4cf357c98ef493b48c807e5bb45cd40b9893295), [`7979f3c`](https://github.com/vultisig/vultisig-sdk/commit/7979f3c502ea04db3c3de551bee297b8a9f9808b), [`7979f3c`](https://github.com/vultisig/vultisig-sdk/commit/7979f3c502ea04db3c3de551bee297b8a9f9808b), [`91990d3`](https://github.com/vultisig/vultisig-sdk/commit/91990d3fc7ef1a8d7068f5cbae8f8f3dda5b68f3), [`95ba10b`](https://github.com/vultisig/vultisig-sdk/commit/95ba10baf2dc2dc4ba8e48825f10f34ec275a73c), [`7979f3c`](https://github.com/vultisig/vultisig-sdk/commit/7979f3c502ea04db3c3de551bee297b8a9f9808b)]:
|
|
168
|
+
- @vultisig/sdk@0.2.0
|
|
169
|
+
|
|
3
170
|
## 0.2.0-beta.9
|
|
4
171
|
|
|
5
172
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -105,6 +105,52 @@ All devices joined. Running keygen...
|
|
|
105
105
|
Vault ID: vault_abc123def456
|
|
106
106
|
```
|
|
107
107
|
|
|
108
|
+
### Import from Seedphrase
|
|
109
|
+
|
|
110
|
+
Import an existing wallet from a BIP39 recovery phrase (12 or 24 words):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
# FastVault import (server-assisted 2-of-2)
|
|
114
|
+
vultisig import-seedphrase fast --name "Imported Wallet" --email user@example.com
|
|
115
|
+
|
|
116
|
+
# SecureVault import (multi-device MPC)
|
|
117
|
+
vultisig import-seedphrase secure --name "Team Wallet" --shares 3
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Import options:**
|
|
121
|
+
- `--mnemonic <words>` - Recovery phrase (space-separated words)
|
|
122
|
+
- `--discover-chains` - Scan chains for existing balances before import
|
|
123
|
+
- `--chains <chains>` - Specific chains to enable (comma-separated)
|
|
124
|
+
|
|
125
|
+
When `--mnemonic` is not provided, you'll be prompted to enter it securely (masked input).
|
|
126
|
+
|
|
127
|
+
**Example session:**
|
|
128
|
+
```bash
|
|
129
|
+
$ vultisig import-seedphrase fast --name "My Wallet" --email user@example.com --discover-chains
|
|
130
|
+
|
|
131
|
+
Enter your 12 or 24-word recovery phrase.
|
|
132
|
+
Words will be hidden as you type.
|
|
133
|
+
|
|
134
|
+
Seedphrase: ************************
|
|
135
|
+
Password: ********
|
|
136
|
+
✓ Valid 12-word seedphrase
|
|
137
|
+
|
|
138
|
+
Discovering chains with balances...
|
|
139
|
+
Bitcoin: bc1q...xyz 0.05 BTC
|
|
140
|
+
Ethereum: 0x1234... 1.2 ETH
|
|
141
|
+
✓ Found 2 chains with balances
|
|
142
|
+
|
|
143
|
+
Importing seedphrase... (35%)
|
|
144
|
+
✓ Keys generated, awaiting email verification
|
|
145
|
+
|
|
146
|
+
Enter verification code: 123456
|
|
147
|
+
✓ Vault verified successfully!
|
|
148
|
+
|
|
149
|
+
Vault imported: My Wallet
|
|
150
|
+
Bitcoin: bc1q...xyz
|
|
151
|
+
Ethereum: 0x1234...abc
|
|
152
|
+
```
|
|
153
|
+
|
|
108
154
|
### Check Balances
|
|
109
155
|
|
|
110
156
|
```bash
|
|
@@ -173,6 +219,8 @@ vultisig -i
|
|
|
173
219
|
| `create` | Create a new fast vault (server-assisted) |
|
|
174
220
|
| `create --secure` | Create a secure vault (multi-device MPC) |
|
|
175
221
|
| `import <file>` | Import vault from .vult file |
|
|
222
|
+
| `import-seedphrase fast` | Import seedphrase as FastVault (2-of-2) |
|
|
223
|
+
| `import-seedphrase secure` | Import seedphrase as SecureVault (N-of-M) |
|
|
176
224
|
| `export [path]` | Export vault to file |
|
|
177
225
|
| `verify <vaultId>` | Verify vault with email code |
|
|
178
226
|
| `vaults` | List all stored vaults |
|
|
@@ -186,6 +234,23 @@ vultisig -i
|
|
|
186
234
|
- `--shares <n>` - Number of devices for secure vault (default: 2)
|
|
187
235
|
- `--threshold <n>` - Signing threshold (default: ceil((shares+1)/2))
|
|
188
236
|
|
|
237
|
+
**Import seedphrase options (fast):**
|
|
238
|
+
- `--name <name>` - Vault name (required)
|
|
239
|
+
- `--email <email>` - Email for verification (required)
|
|
240
|
+
- `--password <password>` - Vault password (required, prompted if not provided)
|
|
241
|
+
- `--mnemonic <words>` - Recovery phrase (prompted securely if not provided)
|
|
242
|
+
- `--discover-chains` - Auto-enable chains with existing balances
|
|
243
|
+
- `--chains <chains>` - Specific chains to enable (comma-separated)
|
|
244
|
+
|
|
245
|
+
**Import seedphrase options (secure):**
|
|
246
|
+
- `--name <name>` - Vault name (required)
|
|
247
|
+
- `--shares <n>` - Number of devices (default: 2)
|
|
248
|
+
- `--threshold <n>` - Signing threshold (default: ceil((shares+1)/2))
|
|
249
|
+
- `--password <password>` - Vault password (optional)
|
|
250
|
+
- `--mnemonic <words>` - Recovery phrase (prompted securely if not provided)
|
|
251
|
+
- `--discover-chains` - Auto-enable chains with existing balances
|
|
252
|
+
- `--chains <chains>` - Specific chains to enable (comma-separated)
|
|
253
|
+
|
|
189
254
|
**Export options:**
|
|
190
255
|
- `[path]` - Output file or directory (defaults to SDK-generated filename in current directory)
|
|
191
256
|
- `--password <password>` - Password to unlock encrypted vaults
|
|
@@ -463,6 +528,8 @@ vultisig broadcast --chain sui --raw-tx '{"unsignedTx":"<base64-tx-bytes>","sign
|
|
|
463
528
|
| Command | Description |
|
|
464
529
|
|---------|-------------|
|
|
465
530
|
| `vault <name>` | Switch to a different vault |
|
|
531
|
+
| `create <fast\|secure>` | Create a new vault |
|
|
532
|
+
| `import-seedphrase <fast\|secure>` | Import wallet from recovery phrase |
|
|
466
533
|
| `lock` | Lock vault (clear cached password) |
|
|
467
534
|
| `unlock` | Unlock vault (cache password) |
|
|
468
535
|
| `status` | Show vault status |
|
package/dist/index.js
CHANGED
|
@@ -1103,9 +1103,11 @@ 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";
|
|
1110
|
+
import inquirer8 from "inquirer";
|
|
1109
1111
|
|
|
1110
1112
|
// src/core/command-context.ts
|
|
1111
1113
|
var DEFAULT_PASSWORD_CACHE_TTL = 5 * 60 * 1e3;
|
|
@@ -2211,7 +2213,7 @@ async function executeCreateFast(ctx2, options) {
|
|
|
2211
2213
|
if (action === "resend") {
|
|
2212
2214
|
const resendSpinner = createSpinner("Resending verification email...");
|
|
2213
2215
|
try {
|
|
2214
|
-
await ctx2.sdk.resendVaultVerification(vaultId);
|
|
2216
|
+
await ctx2.sdk.resendVaultVerification({ vaultId, email, password });
|
|
2215
2217
|
resendSpinner.succeed("Verification email sent!");
|
|
2216
2218
|
info("Check your inbox for the new code. Note: There may be a ~3 minute cooldown between resends.");
|
|
2217
2219
|
} catch (resendErr) {
|
|
@@ -2312,10 +2314,42 @@ async function executeImport(ctx2, file) {
|
|
|
2312
2314
|
}
|
|
2313
2315
|
async function executeVerify(ctx2, vaultId, options = {}) {
|
|
2314
2316
|
if (options.resend) {
|
|
2317
|
+
let email = options.email;
|
|
2318
|
+
let password = options.password;
|
|
2319
|
+
if (!email || !password) {
|
|
2320
|
+
info("Email and password are required to resend verification.");
|
|
2321
|
+
const answers = await inquirer4.prompt([
|
|
2322
|
+
...!email ? [
|
|
2323
|
+
{
|
|
2324
|
+
type: "input",
|
|
2325
|
+
name: "email",
|
|
2326
|
+
message: "Email address:",
|
|
2327
|
+
validate: (input) => input.includes("@") || "Please enter a valid email"
|
|
2328
|
+
}
|
|
2329
|
+
] : [],
|
|
2330
|
+
...!password ? [
|
|
2331
|
+
{
|
|
2332
|
+
type: "password",
|
|
2333
|
+
name: "password",
|
|
2334
|
+
message: "Vault password:",
|
|
2335
|
+
mask: "*",
|
|
2336
|
+
validate: (input) => input.length >= 8 || "Password must be at least 8 characters"
|
|
2337
|
+
}
|
|
2338
|
+
] : []
|
|
2339
|
+
]);
|
|
2340
|
+
email = email || answers.email;
|
|
2341
|
+
password = password || answers.password;
|
|
2342
|
+
}
|
|
2315
2343
|
const spinner2 = createSpinner("Resending verification email...");
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2344
|
+
try {
|
|
2345
|
+
await ctx2.sdk.resendVaultVerification({ vaultId, email, password });
|
|
2346
|
+
spinner2.succeed("Verification email sent!");
|
|
2347
|
+
info("Check your inbox for the new verification code.");
|
|
2348
|
+
} catch (resendErr) {
|
|
2349
|
+
spinner2.fail("Failed to resend verification email");
|
|
2350
|
+
error(resendErr.message || "Could not resend email. You may need to wait a few minutes.");
|
|
2351
|
+
return false;
|
|
2352
|
+
}
|
|
2319
2353
|
}
|
|
2320
2354
|
let code = options.code;
|
|
2321
2355
|
if (!code) {
|
|
@@ -2473,6 +2507,269 @@ async function executeInfo(ctx2) {
|
|
|
2473
2507
|
}
|
|
2474
2508
|
displayVaultInfo(vault);
|
|
2475
2509
|
}
|
|
2510
|
+
async function executeCreateFromSeedphraseFast(ctx2, options) {
|
|
2511
|
+
const { mnemonic, name, password, email, discoverChains, chains, signal } = options;
|
|
2512
|
+
const validateSpinner = createSpinner("Validating seedphrase...");
|
|
2513
|
+
const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
|
|
2514
|
+
if (!validation.valid) {
|
|
2515
|
+
validateSpinner.fail("Invalid seedphrase");
|
|
2516
|
+
if (validation.invalidWords?.length) {
|
|
2517
|
+
warn(`Invalid words: ${validation.invalidWords.join(", ")}`);
|
|
2518
|
+
}
|
|
2519
|
+
throw new Error(validation.error || "Invalid mnemonic phrase");
|
|
2520
|
+
}
|
|
2521
|
+
validateSpinner.succeed(`Valid ${validation.wordCount}-word seedphrase`);
|
|
2522
|
+
if (discoverChains) {
|
|
2523
|
+
const discoverSpinner = createSpinner("Discovering chains with balances...");
|
|
2524
|
+
try {
|
|
2525
|
+
const discovered = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2526
|
+
discoverSpinner.text = `Discovering: ${p.chain || "scanning"} (${p.chainsProcessed}/${p.chainsTotal})`;
|
|
2527
|
+
});
|
|
2528
|
+
const chainsWithBalance = discovered.filter((c) => c.hasBalance);
|
|
2529
|
+
discoverSpinner.succeed(`Found ${chainsWithBalance.length} chains with balances`);
|
|
2530
|
+
if (chainsWithBalance.length > 0 && !isSilent()) {
|
|
2531
|
+
info("\nChains with balances:");
|
|
2532
|
+
for (const result of chainsWithBalance) {
|
|
2533
|
+
info(` ${result.chain}: ${result.balance} ${result.symbol}`);
|
|
2534
|
+
}
|
|
2535
|
+
info("");
|
|
2536
|
+
}
|
|
2537
|
+
} catch {
|
|
2538
|
+
discoverSpinner.warn("Chain discovery failed, continuing with import...");
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
const importSpinner = createSpinner("Importing seedphrase...");
|
|
2542
|
+
const vaultId = await withAbortSignal(
|
|
2543
|
+
ctx2.sdk.createFastVaultFromSeedphrase({
|
|
2544
|
+
mnemonic,
|
|
2545
|
+
name,
|
|
2546
|
+
password,
|
|
2547
|
+
email,
|
|
2548
|
+
// Don't pass discoverChains - CLI handles discovery above
|
|
2549
|
+
chains,
|
|
2550
|
+
onProgress: (step) => {
|
|
2551
|
+
importSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2552
|
+
}
|
|
2553
|
+
}),
|
|
2554
|
+
signal
|
|
2555
|
+
);
|
|
2556
|
+
importSpinner.succeed("Keys generated, email verification required");
|
|
2557
|
+
warn("\nA verification code has been sent to your email.");
|
|
2558
|
+
info("Please check your inbox and enter the code.");
|
|
2559
|
+
const MAX_VERIFY_ATTEMPTS = 5;
|
|
2560
|
+
let attempts = 0;
|
|
2561
|
+
while (attempts < MAX_VERIFY_ATTEMPTS) {
|
|
2562
|
+
attempts++;
|
|
2563
|
+
const codeAnswer = await inquirer4.prompt([
|
|
2564
|
+
{
|
|
2565
|
+
type: "input",
|
|
2566
|
+
name: "code",
|
|
2567
|
+
message: `Verification code sent to ${email}. Enter code:`,
|
|
2568
|
+
validate: (input) => /^\d{4,6}$/.test(input) || "Code must be 4-6 digits"
|
|
2569
|
+
}
|
|
2570
|
+
]);
|
|
2571
|
+
const verifySpinner = createSpinner("Verifying email code...");
|
|
2572
|
+
try {
|
|
2573
|
+
const vault = await ctx2.sdk.verifyVault(vaultId, codeAnswer.code);
|
|
2574
|
+
verifySpinner.succeed("Email verified successfully!");
|
|
2575
|
+
setupVaultEvents(vault);
|
|
2576
|
+
await ctx2.setActiveVault(vault);
|
|
2577
|
+
success("\n+ Vault created from seedphrase!");
|
|
2578
|
+
info("\nYour vault is ready. Run the following commands:");
|
|
2579
|
+
printResult(chalk5.cyan(" vultisig balance ") + "- View balances");
|
|
2580
|
+
printResult(chalk5.cyan(" vultisig addresses ") + "- View addresses");
|
|
2581
|
+
printResult(chalk5.cyan(" vultisig portfolio ") + "- View portfolio value");
|
|
2582
|
+
return vault;
|
|
2583
|
+
} catch (err) {
|
|
2584
|
+
verifySpinner.fail("Verification failed");
|
|
2585
|
+
error(`
|
|
2586
|
+
\u2717 ${err.message || "Invalid verification code"}`);
|
|
2587
|
+
if (attempts >= MAX_VERIFY_ATTEMPTS) {
|
|
2588
|
+
warn("\nMaximum attempts reached.");
|
|
2589
|
+
warn("\nTo retry verification later, use:");
|
|
2590
|
+
info(` vultisig verify ${vaultId}`);
|
|
2591
|
+
err.exitCode = 1;
|
|
2592
|
+
throw err;
|
|
2593
|
+
}
|
|
2594
|
+
const { action } = await inquirer4.prompt([
|
|
2595
|
+
{
|
|
2596
|
+
type: "list",
|
|
2597
|
+
name: "action",
|
|
2598
|
+
message: `What would you like to do? (${MAX_VERIFY_ATTEMPTS - attempts} attempts remaining)`,
|
|
2599
|
+
choices: [
|
|
2600
|
+
{ name: "Enter a different code", value: "retry" },
|
|
2601
|
+
{ name: "Resend verification email (rate limited)", value: "resend" },
|
|
2602
|
+
{ name: "Abort and verify later", value: "abort" }
|
|
2603
|
+
]
|
|
2604
|
+
}
|
|
2605
|
+
]);
|
|
2606
|
+
if (action === "abort") {
|
|
2607
|
+
warn("\nSeedphrase import paused. To complete verification, use:");
|
|
2608
|
+
info(` vultisig verify ${vaultId}`);
|
|
2609
|
+
warn("\nNote: The pending vault is stored in memory only and will be lost if you exit.");
|
|
2610
|
+
return void 0;
|
|
2611
|
+
}
|
|
2612
|
+
if (action === "resend") {
|
|
2613
|
+
const resendSpinner = createSpinner("Resending verification email...");
|
|
2614
|
+
try {
|
|
2615
|
+
await ctx2.sdk.resendVaultVerification({ vaultId, email, password });
|
|
2616
|
+
resendSpinner.succeed("Verification email sent!");
|
|
2617
|
+
info("Check your inbox for the new code.");
|
|
2618
|
+
} catch (resendErr) {
|
|
2619
|
+
resendSpinner.fail("Failed to resend");
|
|
2620
|
+
warn(resendErr.message || "Could not resend email.");
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
throw new Error("Verification loop exited unexpectedly");
|
|
2626
|
+
}
|
|
2627
|
+
async function executeCreateFromSeedphraseSecure(ctx2, options) {
|
|
2628
|
+
const { mnemonic, name, password, threshold, shares: totalShares, discoverChains, chains, signal } = options;
|
|
2629
|
+
const validateSpinner = createSpinner("Validating seedphrase...");
|
|
2630
|
+
const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
|
|
2631
|
+
if (!validation.valid) {
|
|
2632
|
+
validateSpinner.fail("Invalid seedphrase");
|
|
2633
|
+
if (validation.invalidWords?.length) {
|
|
2634
|
+
warn(`Invalid words: ${validation.invalidWords.join(", ")}`);
|
|
2635
|
+
}
|
|
2636
|
+
throw new Error(validation.error || "Invalid mnemonic phrase");
|
|
2637
|
+
}
|
|
2638
|
+
validateSpinner.succeed(`Valid ${validation.wordCount}-word seedphrase`);
|
|
2639
|
+
if (discoverChains) {
|
|
2640
|
+
const discoverSpinner = createSpinner("Discovering chains with balances...");
|
|
2641
|
+
try {
|
|
2642
|
+
const discovered = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2643
|
+
discoverSpinner.text = `Discovering: ${p.chain || "scanning"} (${p.chainsProcessed}/${p.chainsTotal})`;
|
|
2644
|
+
});
|
|
2645
|
+
const chainsWithBalance = discovered.filter((c) => c.hasBalance);
|
|
2646
|
+
discoverSpinner.succeed(`Found ${chainsWithBalance.length} chains with balances`);
|
|
2647
|
+
if (chainsWithBalance.length > 0 && !isSilent()) {
|
|
2648
|
+
info("\nChains with balances:");
|
|
2649
|
+
for (const result of chainsWithBalance) {
|
|
2650
|
+
info(` ${result.chain}: ${result.balance} ${result.symbol}`);
|
|
2651
|
+
}
|
|
2652
|
+
info("");
|
|
2653
|
+
}
|
|
2654
|
+
} catch {
|
|
2655
|
+
discoverSpinner.warn("Chain discovery failed, continuing with import...");
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
const importSpinner = createSpinner("Importing seedphrase as secure vault...");
|
|
2659
|
+
try {
|
|
2660
|
+
const result = await withAbortSignal(
|
|
2661
|
+
ctx2.sdk.createSecureVaultFromSeedphrase({
|
|
2662
|
+
mnemonic,
|
|
2663
|
+
name,
|
|
2664
|
+
password,
|
|
2665
|
+
devices: totalShares,
|
|
2666
|
+
threshold,
|
|
2667
|
+
// Don't pass discoverChains - CLI handles discovery above
|
|
2668
|
+
chains,
|
|
2669
|
+
onProgress: (step) => {
|
|
2670
|
+
importSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2671
|
+
},
|
|
2672
|
+
onQRCodeReady: (qrPayload) => {
|
|
2673
|
+
if (isJsonOutput()) {
|
|
2674
|
+
printResult(qrPayload);
|
|
2675
|
+
} else if (isSilent()) {
|
|
2676
|
+
printResult(`QR Payload: ${qrPayload}`);
|
|
2677
|
+
} else {
|
|
2678
|
+
importSpinner.stop();
|
|
2679
|
+
info("\nScan this QR code with your Vultisig mobile app:");
|
|
2680
|
+
import_qrcode_terminal3.default.generate(qrPayload, { small: true });
|
|
2681
|
+
info(`
|
|
2682
|
+
Or use this URL: ${qrPayload}
|
|
2683
|
+
`);
|
|
2684
|
+
info(chalk5.gray("(Press Ctrl+C to cancel)\n"));
|
|
2685
|
+
importSpinner.start(`Waiting for ${totalShares} devices to join...`);
|
|
2686
|
+
}
|
|
2687
|
+
},
|
|
2688
|
+
onDeviceJoined: (deviceId, totalJoined, required) => {
|
|
2689
|
+
if (!isSilent()) {
|
|
2690
|
+
importSpinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
|
|
2691
|
+
} else if (!isJsonOutput()) {
|
|
2692
|
+
printResult(`Device joined: ${totalJoined}/${required}`);
|
|
2693
|
+
}
|
|
2694
|
+
}
|
|
2695
|
+
}),
|
|
2696
|
+
signal
|
|
2697
|
+
);
|
|
2698
|
+
setupVaultEvents(result.vault);
|
|
2699
|
+
await ctx2.setActiveVault(result.vault);
|
|
2700
|
+
importSpinner.succeed(`Secure vault imported: ${name} (${threshold}-of-${totalShares})`);
|
|
2701
|
+
if (isJsonOutput()) {
|
|
2702
|
+
outputJson({
|
|
2703
|
+
vault: {
|
|
2704
|
+
id: result.vaultId,
|
|
2705
|
+
name,
|
|
2706
|
+
type: "secure",
|
|
2707
|
+
threshold,
|
|
2708
|
+
totalSigners: totalShares
|
|
2709
|
+
},
|
|
2710
|
+
sessionId: result.sessionId
|
|
2711
|
+
});
|
|
2712
|
+
return result.vault;
|
|
2713
|
+
}
|
|
2714
|
+
warn(`
|
|
2715
|
+
Important: Save your vault backup file (.vult) in a secure location.`);
|
|
2716
|
+
warn(`This is a ${threshold}-of-${totalShares} vault. You'll need ${threshold} devices to sign transactions.`);
|
|
2717
|
+
success("\n+ Vault created from seedphrase!");
|
|
2718
|
+
return result.vault;
|
|
2719
|
+
} catch (err) {
|
|
2720
|
+
importSpinner.fail("Secure vault creation failed");
|
|
2721
|
+
if (err.message?.includes("not implemented")) {
|
|
2722
|
+
warn("\nSecure vault creation from seedphrase is not yet implemented in the SDK");
|
|
2723
|
+
}
|
|
2724
|
+
throw err;
|
|
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
|
+
}
|
|
2476
2773
|
|
|
2477
2774
|
// src/commands/swap.ts
|
|
2478
2775
|
async function executeSwapChains(ctx2) {
|
|
@@ -2790,7 +3087,9 @@ var COMMANDS = [
|
|
|
2790
3087
|
"vaults",
|
|
2791
3088
|
"vault",
|
|
2792
3089
|
"import",
|
|
3090
|
+
"create-from-seedphrase",
|
|
2793
3091
|
"create",
|
|
3092
|
+
"join",
|
|
2794
3093
|
"info",
|
|
2795
3094
|
"export",
|
|
2796
3095
|
// Wallet operations
|
|
@@ -2848,6 +3147,20 @@ function createCompleter(ctx2) {
|
|
|
2848
3147
|
const partial = parts[1] || "";
|
|
2849
3148
|
return completeChainName(partial);
|
|
2850
3149
|
}
|
|
3150
|
+
if ((command === "create" || command === "create-from-seedphrase") && parts.length === 2) {
|
|
3151
|
+
const types = ["fast", "secure"];
|
|
3152
|
+
const partial = parts[1] || "";
|
|
3153
|
+
const partialLower = partial.toLowerCase();
|
|
3154
|
+
const matches = types.filter((t) => t.startsWith(partialLower));
|
|
3155
|
+
return [matches.length ? matches : types, partial];
|
|
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
|
+
}
|
|
2851
3164
|
const hits = COMMANDS.filter((c) => c.startsWith(line));
|
|
2852
3165
|
const show = hits.length ? hits : COMMANDS;
|
|
2853
3166
|
return [show, line];
|
|
@@ -3605,6 +3918,9 @@ Error: ${error2.message}`));
|
|
|
3605
3918
|
case "create":
|
|
3606
3919
|
await this.createVault(args);
|
|
3607
3920
|
break;
|
|
3921
|
+
case "create-from-seedphrase":
|
|
3922
|
+
await this.importSeedphrase(args);
|
|
3923
|
+
break;
|
|
3608
3924
|
case "info":
|
|
3609
3925
|
await executeInfo(this.ctx);
|
|
3610
3926
|
break;
|
|
@@ -3785,6 +4101,92 @@ Error: ${error2.message}`));
|
|
|
3785
4101
|
this.eventBuffer.setupVaultListeners(vault);
|
|
3786
4102
|
}
|
|
3787
4103
|
}
|
|
4104
|
+
async importSeedphrase(args) {
|
|
4105
|
+
const type = args[0]?.toLowerCase();
|
|
4106
|
+
if (!type || type !== "fast" && type !== "secure") {
|
|
4107
|
+
console.log(chalk9.cyan("Usage: create-from-seedphrase <fast|secure>"));
|
|
4108
|
+
console.log(chalk9.gray(" fast - Import with VultiServer (2-of-2)"));
|
|
4109
|
+
console.log(chalk9.gray(" secure - Import with device coordination (N-of-M)"));
|
|
4110
|
+
return;
|
|
4111
|
+
}
|
|
4112
|
+
console.log(chalk9.cyan("\nEnter your recovery phrase (words separated by spaces):"));
|
|
4113
|
+
const mnemonic = await this.promptPassword("Seedphrase");
|
|
4114
|
+
const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
|
|
4115
|
+
if (!validation.valid) {
|
|
4116
|
+
console.log(chalk9.red(`Invalid seedphrase: ${validation.error}`));
|
|
4117
|
+
if (validation.invalidWords?.length) {
|
|
4118
|
+
console.log(chalk9.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
|
|
4119
|
+
}
|
|
4120
|
+
return;
|
|
4121
|
+
}
|
|
4122
|
+
console.log(chalk9.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
|
|
4123
|
+
let vault;
|
|
4124
|
+
if (type === "fast") {
|
|
4125
|
+
const name = await this.prompt("Vault name");
|
|
4126
|
+
if (!name) {
|
|
4127
|
+
console.log(chalk9.red("Name is required"));
|
|
4128
|
+
return;
|
|
4129
|
+
}
|
|
4130
|
+
const password = await this.promptPassword("Vault password");
|
|
4131
|
+
if (!password) {
|
|
4132
|
+
console.log(chalk9.red("Password is required"));
|
|
4133
|
+
return;
|
|
4134
|
+
}
|
|
4135
|
+
const email = await this.prompt("Email for verification");
|
|
4136
|
+
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4137
|
+
console.log(chalk9.red("Valid email is required"));
|
|
4138
|
+
return;
|
|
4139
|
+
}
|
|
4140
|
+
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
4141
|
+
const discoverChains = discoverStr.toLowerCase() === "y";
|
|
4142
|
+
vault = await this.withCancellation(
|
|
4143
|
+
(signal) => executeCreateFromSeedphraseFast(this.ctx, {
|
|
4144
|
+
mnemonic,
|
|
4145
|
+
name,
|
|
4146
|
+
password,
|
|
4147
|
+
email,
|
|
4148
|
+
discoverChains,
|
|
4149
|
+
signal
|
|
4150
|
+
})
|
|
4151
|
+
);
|
|
4152
|
+
} else {
|
|
4153
|
+
const name = await this.prompt("Vault name");
|
|
4154
|
+
if (!name) {
|
|
4155
|
+
console.log(chalk9.red("Name is required"));
|
|
4156
|
+
return;
|
|
4157
|
+
}
|
|
4158
|
+
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
4159
|
+
const shares = parseInt(sharesStr, 10);
|
|
4160
|
+
if (isNaN(shares) || shares < 2) {
|
|
4161
|
+
console.log(chalk9.red("Must have at least 2 shares"));
|
|
4162
|
+
return;
|
|
4163
|
+
}
|
|
4164
|
+
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
4165
|
+
const threshold = parseInt(thresholdStr, 10);
|
|
4166
|
+
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
4167
|
+
console.log(chalk9.red(`Threshold must be between 1 and ${shares}`));
|
|
4168
|
+
return;
|
|
4169
|
+
}
|
|
4170
|
+
const password = await this.promptPassword("Vault password (optional, Enter to skip)");
|
|
4171
|
+
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
4172
|
+
const discoverChains = discoverStr.toLowerCase() === "y";
|
|
4173
|
+
vault = await this.withCancellation(
|
|
4174
|
+
(signal) => executeCreateFromSeedphraseSecure(this.ctx, {
|
|
4175
|
+
mnemonic,
|
|
4176
|
+
name,
|
|
4177
|
+
password: password || void 0,
|
|
4178
|
+
threshold,
|
|
4179
|
+
shares,
|
|
4180
|
+
discoverChains,
|
|
4181
|
+
signal
|
|
4182
|
+
})
|
|
4183
|
+
);
|
|
4184
|
+
}
|
|
4185
|
+
if (vault) {
|
|
4186
|
+
this.ctx.addVault(vault);
|
|
4187
|
+
this.eventBuffer.setupVaultListeners(vault);
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
3788
4190
|
async runBalance(args) {
|
|
3789
4191
|
const chainStr = args[0];
|
|
3790
4192
|
const includeTokens = args.includes("-t") || args.includes("--tokens");
|
|
@@ -4040,7 +4442,7 @@ var cachedVersion = null;
|
|
|
4040
4442
|
function getVersion() {
|
|
4041
4443
|
if (cachedVersion) return cachedVersion;
|
|
4042
4444
|
if (true) {
|
|
4043
|
-
cachedVersion = "0.
|
|
4445
|
+
cachedVersion = "0.3.0";
|
|
4044
4446
|
return cachedVersion;
|
|
4045
4447
|
}
|
|
4046
4448
|
try {
|
|
@@ -4559,16 +4961,150 @@ program.command("import <file>").description("Import vault from .vult file").act
|
|
|
4559
4961
|
await executeImport(context, file);
|
|
4560
4962
|
})
|
|
4561
4963
|
);
|
|
4562
|
-
program.command("
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4964
|
+
var createFromSeedphraseCmd = program.command("create-from-seedphrase").description("Create vault from BIP39 seedphrase");
|
|
4965
|
+
async function promptSeedphrase() {
|
|
4966
|
+
info("\nEnter your 12 or 24-word recovery phrase.");
|
|
4967
|
+
info("Words will be hidden as you type.\n");
|
|
4968
|
+
const answer = await inquirer8.prompt([
|
|
4969
|
+
{
|
|
4970
|
+
type: "password",
|
|
4971
|
+
name: "mnemonic",
|
|
4972
|
+
message: "Seedphrase:",
|
|
4973
|
+
mask: "*",
|
|
4974
|
+
validate: (input) => {
|
|
4975
|
+
const words = input.trim().split(/\s+/);
|
|
4976
|
+
if (words.length !== 12 && words.length !== 24) {
|
|
4977
|
+
return `Expected 12 or 24 words, got ${words.length}`;
|
|
4978
|
+
}
|
|
4979
|
+
return true;
|
|
4980
|
+
}
|
|
4570
4981
|
}
|
|
4571
|
-
|
|
4982
|
+
]);
|
|
4983
|
+
return answer.mnemonic.trim().toLowerCase();
|
|
4984
|
+
}
|
|
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(
|
|
5005
|
+
withExit(
|
|
5006
|
+
async (options) => {
|
|
5007
|
+
const context = await init(program.opts().vault);
|
|
5008
|
+
let mnemonic = options.mnemonic;
|
|
5009
|
+
if (!mnemonic) {
|
|
5010
|
+
mnemonic = await promptSeedphrase();
|
|
5011
|
+
}
|
|
5012
|
+
let chains;
|
|
5013
|
+
if (options.chains) {
|
|
5014
|
+
const chainNames = options.chains.split(",").map((c) => c.trim());
|
|
5015
|
+
chains = [];
|
|
5016
|
+
for (const name of chainNames) {
|
|
5017
|
+
const chain = findChainByName(name);
|
|
5018
|
+
if (chain) {
|
|
5019
|
+
chains.push(chain);
|
|
5020
|
+
} else {
|
|
5021
|
+
console.warn(`Warning: Unknown chain "${name}" - skipping`);
|
|
5022
|
+
}
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
await executeCreateFromSeedphraseFast(context, {
|
|
5026
|
+
mnemonic,
|
|
5027
|
+
name: options.name,
|
|
5028
|
+
password: options.password,
|
|
5029
|
+
email: options.email,
|
|
5030
|
+
discoverChains: options.discoverChains,
|
|
5031
|
+
chains
|
|
5032
|
+
});
|
|
5033
|
+
}
|
|
5034
|
+
)
|
|
5035
|
+
);
|
|
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(
|
|
5037
|
+
withExit(
|
|
5038
|
+
async (options) => {
|
|
5039
|
+
const context = await init(program.opts().vault);
|
|
5040
|
+
let mnemonic = options.mnemonic;
|
|
5041
|
+
if (!mnemonic) {
|
|
5042
|
+
mnemonic = await promptSeedphrase();
|
|
5043
|
+
}
|
|
5044
|
+
let chains;
|
|
5045
|
+
if (options.chains) {
|
|
5046
|
+
const chainNames = options.chains.split(",").map((c) => c.trim());
|
|
5047
|
+
chains = [];
|
|
5048
|
+
for (const name of chainNames) {
|
|
5049
|
+
const chain = findChainByName(name);
|
|
5050
|
+
if (chain) {
|
|
5051
|
+
chains.push(chain);
|
|
5052
|
+
} else {
|
|
5053
|
+
console.warn(`Warning: Unknown chain "${name}" - skipping`);
|
|
5054
|
+
}
|
|
5055
|
+
}
|
|
5056
|
+
}
|
|
5057
|
+
await executeCreateFromSeedphraseSecure(context, {
|
|
5058
|
+
mnemonic,
|
|
5059
|
+
name: options.name,
|
|
5060
|
+
password: options.password,
|
|
5061
|
+
threshold: parseInt(options.threshold, 10),
|
|
5062
|
+
shares: parseInt(options.shares, 10),
|
|
5063
|
+
discoverChains: options.discoverChains,
|
|
5064
|
+
chains
|
|
5065
|
+
});
|
|
5066
|
+
}
|
|
5067
|
+
)
|
|
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
|
+
);
|
|
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(
|
|
5097
|
+
withExit(
|
|
5098
|
+
async (vaultId, options) => {
|
|
5099
|
+
const context = await init(program.opts().vault);
|
|
5100
|
+
const verified = await executeVerify(context, vaultId, options);
|
|
5101
|
+
if (!verified) {
|
|
5102
|
+
const err = new Error("Verification failed");
|
|
5103
|
+
err.exitCode = 1;
|
|
5104
|
+
throw err;
|
|
5105
|
+
}
|
|
5106
|
+
}
|
|
5107
|
+
)
|
|
4572
5108
|
);
|
|
4573
5109
|
program.command("balance [chain]").description("Show balance for a chain or all chains").option("-t, --tokens", "Include token balances").action(
|
|
4574
5110
|
withExit(async (chainStr, options) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vultisig/cli",
|
|
3
|
-
"version": "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.
|
|
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",
|