@vultisig/cli 0.2.0-beta.8 → 0.2.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 +146 -0
- package/README.md +265 -0
- package/dist/index.js +597 -31
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,151 @@
|
|
|
1
1
|
# @vultisig/cli
|
|
2
2
|
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#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
|
|
8
|
+
|
|
9
|
+
Adds `broadcastRawTx()` method supporting all chain families:
|
|
10
|
+
- EVM: Ethereum, Polygon, BSC, Arbitrum, Base, etc. (hex-encoded)
|
|
11
|
+
- UTXO: Bitcoin, Litecoin, Dogecoin, etc. (hex-encoded)
|
|
12
|
+
- Solana: Base58 or Base64 encoded transaction bytes
|
|
13
|
+
- Cosmos: JSON `{tx_bytes}` or raw base64 protobuf (10 chains)
|
|
14
|
+
- TON: BOC as base64 string
|
|
15
|
+
- Polkadot: Hex-encoded extrinsic
|
|
16
|
+
- Ripple: Hex-encoded transaction blob
|
|
17
|
+
- Sui: JSON `{unsignedTx, signature}`
|
|
18
|
+
- Tron: JSON transaction object
|
|
19
|
+
|
|
20
|
+
CLI commands added:
|
|
21
|
+
- `vultisig sign --chain <chain> --bytes <base64>` - sign pre-hashed data
|
|
22
|
+
- `vultisig broadcast --chain <chain> --raw-tx <data>` - broadcast raw tx
|
|
23
|
+
|
|
24
|
+
Documentation updated with complete workflow examples for EVM, UTXO, Solana, and Sui.
|
|
25
|
+
|
|
26
|
+
- [#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
|
|
27
|
+
|
|
28
|
+
The export command now has two distinct password options:
|
|
29
|
+
- `--password`: Unlocks the vault (decrypts stored keyshares for encrypted vaults)
|
|
30
|
+
- `--exportPassword`: Encrypts the exported file (defaults to `--password` if not specified)
|
|
31
|
+
|
|
32
|
+
This fixes the "Password required but callback returned empty value" error when exporting encrypted vaults.
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
- [#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
|
|
37
|
+
- Implement SecureVault.create() for multi-device keygen ceremony
|
|
38
|
+
- Add RelaySigningService for coordinated signing via relay server
|
|
39
|
+
- Implement SecureVault.sign() and signBytes() methods
|
|
40
|
+
- Add QR code generation for mobile app pairing (compatible with Vultisig iOS/Android)
|
|
41
|
+
- CLI: Add `vault create --type secure` with terminal QR display
|
|
42
|
+
- CLI: Support secure vault signing with device coordination
|
|
43
|
+
- Add comprehensive unit, integration, and E2E tests
|
|
44
|
+
|
|
45
|
+
- [#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
|
|
46
|
+
|
|
47
|
+
This release adds the ability to import existing wallets from BIP39 mnemonic phrases (12 or 24 words) into Vultisig vaults, mirroring the iOS implementation.
|
|
48
|
+
|
|
49
|
+
**New SDK Methods:**
|
|
50
|
+
- `sdk.validateSeedphrase()` - Validate a BIP39 mnemonic phrase
|
|
51
|
+
- `sdk.discoverChainsFromSeedphrase()` - Discover chains with balances before import
|
|
52
|
+
- `sdk.importSeedphraseAsFastVault()` - Import as FastVault (2-of-2 with VultiServer)
|
|
53
|
+
- `sdk.importSeedphraseAsSecureVault()` - Import as SecureVault (N-of-M multi-device)
|
|
54
|
+
|
|
55
|
+
**Features:**
|
|
56
|
+
- Chain discovery with progress callbacks to find existing balances
|
|
57
|
+
- Auto-enable chains with balances during import
|
|
58
|
+
- EdDSA key transformation using SHA-512 clamping for Schnorr TSS compatibility
|
|
59
|
+
- Full ECDSA (secp256k1) and EdDSA (ed25519) master key derivation
|
|
60
|
+
|
|
61
|
+
**New exported types:**
|
|
62
|
+
- `SeedphraseValidation`, `ChainDiscoveryProgress`, `ChainDiscoveryResult`
|
|
63
|
+
- `ChainDiscoveryPhase`, `DerivedMasterKeys`
|
|
64
|
+
- `ImportSeedphraseAsFastVaultOptions`, `ImportSeedphraseAsSecureVaultOptions`
|
|
65
|
+
- `SeedphraseImportResult`
|
|
66
|
+
|
|
67
|
+
**New services:**
|
|
68
|
+
- `SeedphraseValidator` - BIP39 validation using WalletCore
|
|
69
|
+
- `MasterKeyDeriver` - Key derivation from mnemonic
|
|
70
|
+
- `ChainDiscoveryService` - Balance scanning across chains
|
|
71
|
+
- `FastVaultSeedphraseImportService` - FastVault import orchestration
|
|
72
|
+
- `SecureVaultSeedphraseImportService` - SecureVault import orchestration
|
|
73
|
+
|
|
74
|
+
**New CLI Commands:**
|
|
75
|
+
- `vultisig import-seedphrase fast` - Import as FastVault (2-of-2 with VultiServer)
|
|
76
|
+
- `vultisig import-seedphrase secure` - Import as SecureVault (N-of-M multi-device)
|
|
77
|
+
|
|
78
|
+
**CLI Features:**
|
|
79
|
+
- Secure seedphrase input (masked with `*`)
|
|
80
|
+
- `--discover-chains` flag to scan for existing balances
|
|
81
|
+
- `--chains` flag to specify chains (comma-separated)
|
|
82
|
+
- Interactive shell support with tab completion
|
|
83
|
+
- Progress spinners during import
|
|
84
|
+
|
|
85
|
+
### Patch Changes
|
|
86
|
+
|
|
87
|
+
- [#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
|
|
88
|
+
- Add terser minification (~60% bundle size reduction)
|
|
89
|
+
- Add clean script to remove stale dist files before builds
|
|
90
|
+
- Centralize duplicated onwarn handler in rollup config
|
|
91
|
+
- Add package.json exports for react-native and electron platforms
|
|
92
|
+
|
|
93
|
+
- [`cc96f64`](https://github.com/vultisig/vultisig-sdk/commit/cc96f64622a651eb6156f279afbbfe0aa4219179) - fix: re-release as alpha (0.1.0 was accidentally published as stable)
|
|
94
|
+
|
|
95
|
+
- [#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.
|
|
96
|
+
|
|
97
|
+
- [#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
|
|
98
|
+
|
|
99
|
+
- [#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
|
|
100
|
+
- Add `--password` option to `send` and `swap` commands for non-interactive use
|
|
101
|
+
- Pre-unlock vault before signing spinner starts to prevent prompt interference
|
|
102
|
+
- Password prompt now appears before spinner when not provided via CLI flag
|
|
103
|
+
|
|
104
|
+
- [`9dcfb8b`](https://github.com/vultisig/vultisig-sdk/commit/9dcfb8b4b29de73b1301f791b50dc417a8f899f3) - fix: use yarn npm publish to properly resolve workspace protocols
|
|
105
|
+
|
|
106
|
+
- [`0985a37`](https://github.com/vultisig/vultisig-sdk/commit/0985a375c6009e2550231d759e84b576454ce759) - fix: use workspace:^ for SDK dependency to resolve correctly when publishing
|
|
107
|
+
|
|
108
|
+
- [#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).
|
|
109
|
+
|
|
110
|
+
- [#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
|
|
111
|
+
- Updated to use new `createFastVault()` that returns just the vaultId
|
|
112
|
+
- Updated to use new `verifyVault()` that returns the FastVault
|
|
113
|
+
- Removed `code` from CLI `CreateVaultOptions` (verification code always prompted interactively)
|
|
114
|
+
- Removed `--code` option from CLI create command
|
|
115
|
+
|
|
116
|
+
- [#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.
|
|
117
|
+
|
|
118
|
+
- 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)]:
|
|
119
|
+
- @vultisig/sdk@0.2.0
|
|
120
|
+
|
|
121
|
+
## 0.2.0-beta.9
|
|
122
|
+
|
|
123
|
+
### Minor Changes
|
|
124
|
+
|
|
125
|
+
- [#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
|
|
126
|
+
|
|
127
|
+
Adds `broadcastRawTx()` method supporting all chain families:
|
|
128
|
+
- EVM: Ethereum, Polygon, BSC, Arbitrum, Base, etc. (hex-encoded)
|
|
129
|
+
- UTXO: Bitcoin, Litecoin, Dogecoin, etc. (hex-encoded)
|
|
130
|
+
- Solana: Base58 or Base64 encoded transaction bytes
|
|
131
|
+
- Cosmos: JSON `{tx_bytes}` or raw base64 protobuf (10 chains)
|
|
132
|
+
- TON: BOC as base64 string
|
|
133
|
+
- Polkadot: Hex-encoded extrinsic
|
|
134
|
+
- Ripple: Hex-encoded transaction blob
|
|
135
|
+
- Sui: JSON `{unsignedTx, signature}`
|
|
136
|
+
- Tron: JSON transaction object
|
|
137
|
+
|
|
138
|
+
CLI commands added:
|
|
139
|
+
- `vultisig sign --chain <chain> --bytes <base64>` - sign pre-hashed data
|
|
140
|
+
- `vultisig broadcast --chain <chain> --raw-tx <data>` - broadcast raw tx
|
|
141
|
+
|
|
142
|
+
Documentation updated with complete workflow examples for EVM, UTXO, Solana, and Sui.
|
|
143
|
+
|
|
144
|
+
### Patch Changes
|
|
145
|
+
|
|
146
|
+
- Updated dependencies [[`a36a7f6`](https://github.com/vultisig/vultisig-sdk/commit/a36a7f614c03e32ebc7e843cbf1ab30b6be0d4af), [`91990d3`](https://github.com/vultisig/vultisig-sdk/commit/91990d3fc7ef1a8d7068f5cbae8f8f3dda5b68f3)]:
|
|
147
|
+
- @vultisig/sdk@0.2.0-beta.9
|
|
148
|
+
|
|
3
149
|
## 0.2.0-beta.8
|
|
4
150
|
|
|
5
151
|
### 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
|
|
@@ -244,6 +309,204 @@ vultisig swap ethereum bitcoin 0.1 --password mypassword
|
|
|
244
309
|
vultisig swap ethereum bitcoin 0.1 -y --password mypassword
|
|
245
310
|
```
|
|
246
311
|
|
|
312
|
+
### Advanced Operations
|
|
313
|
+
|
|
314
|
+
| Command | Description |
|
|
315
|
+
|---------|-------------|
|
|
316
|
+
| `sign` | Sign pre-hashed bytes for custom transactions |
|
|
317
|
+
| `broadcast` | Broadcast a pre-signed raw transaction |
|
|
318
|
+
|
|
319
|
+
#### Signing Arbitrary Bytes
|
|
320
|
+
|
|
321
|
+
Sign pre-hashed data for externally constructed transactions:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
# Sign a pre-hashed message (base64 encoded)
|
|
325
|
+
vultisig sign --chain ethereum --bytes "aGVsbG8gd29ybGQ="
|
|
326
|
+
|
|
327
|
+
# With password
|
|
328
|
+
vultisig sign --chain bitcoin --bytes "..." --password mypassword
|
|
329
|
+
|
|
330
|
+
# JSON output
|
|
331
|
+
vultisig sign --chain ethereum --bytes "..." -o json
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
**Output:**
|
|
335
|
+
```
|
|
336
|
+
Signature: <base64-encoded signature>
|
|
337
|
+
Recovery: 0
|
|
338
|
+
Format: ecdsa
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**JSON output:**
|
|
342
|
+
```json
|
|
343
|
+
{
|
|
344
|
+
"signature": "<base64>",
|
|
345
|
+
"recovery": 0,
|
|
346
|
+
"format": "ecdsa"
|
|
347
|
+
}
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
#### Broadcasting Raw Transactions
|
|
351
|
+
|
|
352
|
+
Broadcast pre-signed transactions to the network:
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
# EVM transaction (hex)
|
|
356
|
+
vultisig broadcast --chain ethereum --raw-tx "0x02f8..."
|
|
357
|
+
|
|
358
|
+
# Bitcoin transaction (hex)
|
|
359
|
+
vultisig broadcast --chain bitcoin --raw-tx "0200000001..."
|
|
360
|
+
|
|
361
|
+
# Solana transaction (base64)
|
|
362
|
+
vultisig broadcast --chain solana --raw-tx "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABAwIAAA..."
|
|
363
|
+
|
|
364
|
+
# Sui transaction (JSON)
|
|
365
|
+
vultisig broadcast --chain sui --raw-tx '{"unsignedTx":"...","signature":"..."}'
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
**Output:**
|
|
369
|
+
```
|
|
370
|
+
TX Hash: 0x9f8e7d6c...
|
|
371
|
+
Explorer: https://etherscan.io/tx/0x9f8e7d6c...
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
**Supported broadcast formats by chain:**
|
|
375
|
+
|
|
376
|
+
| Chain | `--raw-tx` Format |
|
|
377
|
+
|-------|-------------------|
|
|
378
|
+
| EVM (Ethereum, Polygon, etc.) | Hex-encoded signed tx |
|
|
379
|
+
| UTXO (Bitcoin, Litecoin, etc.) | Hex-encoded raw tx |
|
|
380
|
+
| Solana | Base64-encoded tx bytes |
|
|
381
|
+
| Sui | JSON: `{"unsignedTx":"...","signature":"..."}` |
|
|
382
|
+
| Cosmos | JSON: `{"tx_bytes":"..."}` or base64 |
|
|
383
|
+
| TON | Base64 BOC |
|
|
384
|
+
| Polkadot | Hex-encoded extrinsic |
|
|
385
|
+
| Ripple | Hex-encoded tx blob |
|
|
386
|
+
| Tron | JSON tx object |
|
|
387
|
+
|
|
388
|
+
#### Example: Custom EVM Transaction
|
|
389
|
+
|
|
390
|
+
Build and sign a transaction with ethers.js, broadcast with CLI:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
# 1. Build transaction externally (save as build-evm-tx.js)
|
|
394
|
+
cat > build-evm-tx.js << 'EOF'
|
|
395
|
+
const { keccak256, Transaction, parseEther } = require('ethers');
|
|
396
|
+
const tx = Transaction.from({
|
|
397
|
+
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
|
|
398
|
+
value: parseEther('0.01'),
|
|
399
|
+
gasLimit: 21000n,
|
|
400
|
+
maxFeePerGas: 50000000000n,
|
|
401
|
+
maxPriorityFeePerGas: 2000000000n,
|
|
402
|
+
nonce: 0,
|
|
403
|
+
chainId: 1,
|
|
404
|
+
type: 2
|
|
405
|
+
});
|
|
406
|
+
const hash = keccak256(tx.unsignedSerialized);
|
|
407
|
+
console.log('HASH:', Buffer.from(hash.slice(2), 'hex').toString('base64'));
|
|
408
|
+
console.log('UNSIGNED:', tx.unsignedSerialized);
|
|
409
|
+
EOF
|
|
410
|
+
node build-evm-tx.js
|
|
411
|
+
|
|
412
|
+
# 2. Sign the hash with Vultisig
|
|
413
|
+
vultisig sign --chain ethereum --bytes "<base64-hash-from-step-1>" -o json > sig.json
|
|
414
|
+
|
|
415
|
+
# 3. Assemble signed transaction (use r,s,v from sig.json)
|
|
416
|
+
# The signature field contains r||s (64 bytes hex), recovery is v
|
|
417
|
+
|
|
418
|
+
# 4. Broadcast the assembled signed transaction
|
|
419
|
+
vultisig broadcast --chain ethereum --raw-tx "0x02f8..."
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
#### Example: Custom Bitcoin Transaction
|
|
423
|
+
|
|
424
|
+
Build a PSBT with bitcoinjs-lib, sign with CLI:
|
|
425
|
+
|
|
426
|
+
```bash
|
|
427
|
+
# 1. Build PSBT and get sighash (save as build-btc-tx.js)
|
|
428
|
+
cat > build-btc-tx.js << 'EOF'
|
|
429
|
+
const bitcoin = require('bitcoinjs-lib');
|
|
430
|
+
const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });
|
|
431
|
+
// Add your inputs and outputs
|
|
432
|
+
psbt.addInput({
|
|
433
|
+
hash: '<previous-txid>',
|
|
434
|
+
index: 0,
|
|
435
|
+
witnessUtxo: { script: Buffer.from('...'), value: 100000 }
|
|
436
|
+
});
|
|
437
|
+
psbt.addOutput({ address: 'bc1q...', value: 90000 });
|
|
438
|
+
// Get sighash for signing
|
|
439
|
+
const sighash = psbt.getTxForSigning().hashForWitnessV0(0, scriptCode, 100000, 0x01);
|
|
440
|
+
console.log('SIGHASH:', sighash.toString('base64'));
|
|
441
|
+
EOF
|
|
442
|
+
node build-btc-tx.js
|
|
443
|
+
|
|
444
|
+
# 2. Sign with Vultisig
|
|
445
|
+
vultisig sign --chain bitcoin --bytes "<base64-sighash>" -o json > sig.json
|
|
446
|
+
|
|
447
|
+
# 3. Apply signature to PSBT and finalize (use signature from sig.json)
|
|
448
|
+
|
|
449
|
+
# 4. Broadcast
|
|
450
|
+
vultisig broadcast --chain bitcoin --raw-tx "0200000001..."
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
#### Example: Custom Solana Transaction
|
|
454
|
+
|
|
455
|
+
Build with @solana/web3.js, sign with CLI:
|
|
456
|
+
|
|
457
|
+
```bash
|
|
458
|
+
# 1. Build transaction (save as build-sol-tx.js)
|
|
459
|
+
cat > build-sol-tx.js << 'EOF'
|
|
460
|
+
const { Transaction, SystemProgram, PublicKey, Connection } = require('@solana/web3.js');
|
|
461
|
+
const connection = new Connection('https://api.mainnet-beta.solana.com');
|
|
462
|
+
const fromPubkey = new PublicKey('<your-pubkey>');
|
|
463
|
+
const toPubkey = new PublicKey('<recipient-pubkey>');
|
|
464
|
+
|
|
465
|
+
const tx = new Transaction().add(
|
|
466
|
+
SystemProgram.transfer({ fromPubkey, toPubkey, lamports: 1000000 })
|
|
467
|
+
);
|
|
468
|
+
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
469
|
+
tx.feePayer = fromPubkey;
|
|
470
|
+
|
|
471
|
+
const message = tx.serializeMessage();
|
|
472
|
+
console.log('MESSAGE:', message.toString('base64'));
|
|
473
|
+
EOF
|
|
474
|
+
node build-sol-tx.js
|
|
475
|
+
|
|
476
|
+
# 2. Sign the message with Vultisig (EdDSA)
|
|
477
|
+
vultisig sign --chain solana --bytes "<base64-message>" -o json > sig.json
|
|
478
|
+
|
|
479
|
+
# 3. Assemble signed transaction (attach signature to message)
|
|
480
|
+
|
|
481
|
+
# 4. Broadcast (base64 encoded signed transaction)
|
|
482
|
+
vultisig broadcast --chain solana --raw-tx "<base64-signed-tx>"
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
#### Example: Custom Sui Transaction
|
|
486
|
+
|
|
487
|
+
Build with @mysten/sui, sign with CLI:
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
# 1. Build transaction (save as build-sui-tx.js)
|
|
491
|
+
cat > build-sui-tx.js << 'EOF'
|
|
492
|
+
const { SuiClient, getFullnodeUrl } = require('@mysten/sui/client');
|
|
493
|
+
const { Transaction } = require('@mysten/sui/transactions');
|
|
494
|
+
|
|
495
|
+
const client = new SuiClient({ url: getFullnodeUrl('mainnet') });
|
|
496
|
+
const tx = new Transaction();
|
|
497
|
+
tx.transferObjects([tx.gas], '<recipient-address>');
|
|
498
|
+
const bytes = await tx.build({ client });
|
|
499
|
+
console.log('TX_BYTES:', Buffer.from(bytes).toString('base64'));
|
|
500
|
+
EOF
|
|
501
|
+
node build-sui-tx.js
|
|
502
|
+
|
|
503
|
+
# 2. Sign the transaction bytes with Vultisig (EdDSA)
|
|
504
|
+
vultisig sign --chain sui --bytes "<base64-tx-bytes>" -o json > sig.json
|
|
505
|
+
|
|
506
|
+
# 3. Broadcast (requires JSON with both unsigned tx and signature)
|
|
507
|
+
vultisig broadcast --chain sui --raw-tx '{"unsignedTx":"<base64-tx-bytes>","signature":"<base64-signature-from-sig.json>"}'
|
|
508
|
+
```
|
|
509
|
+
|
|
247
510
|
### Settings
|
|
248
511
|
|
|
249
512
|
| Command | Description |
|
|
@@ -265,6 +528,8 @@ vultisig swap ethereum bitcoin 0.1 -y --password mypassword
|
|
|
265
528
|
| Command | Description |
|
|
266
529
|
|---------|-------------|
|
|
267
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 |
|
|
268
533
|
| `lock` | Lock vault (clear cached password) |
|
|
269
534
|
| `unlock` | Unlock vault (cache password) |
|
|
270
535
|
| `status` | Show vault status |
|
package/dist/index.js
CHANGED
|
@@ -1042,14 +1042,14 @@ var require_main = __commonJS({
|
|
|
1042
1042
|
cb = opts;
|
|
1043
1043
|
opts = {};
|
|
1044
1044
|
}
|
|
1045
|
-
var
|
|
1046
|
-
|
|
1047
|
-
|
|
1045
|
+
var qrcode4 = new QRCode(-1, this.error);
|
|
1046
|
+
qrcode4.addData(input);
|
|
1047
|
+
qrcode4.make();
|
|
1048
1048
|
var output = "";
|
|
1049
1049
|
if (opts && opts.small) {
|
|
1050
1050
|
var BLACK = true, WHITE = false;
|
|
1051
|
-
var moduleCount =
|
|
1052
|
-
var moduleData =
|
|
1051
|
+
var moduleCount = qrcode4.getModuleCount();
|
|
1052
|
+
var moduleData = qrcode4.modules.slice();
|
|
1053
1053
|
var oddRow = moduleCount % 2 === 1;
|
|
1054
1054
|
if (oddRow) {
|
|
1055
1055
|
moduleData.push(fill(moduleCount, WHITE));
|
|
@@ -1082,9 +1082,9 @@ var require_main = __commonJS({
|
|
|
1082
1082
|
output += borderBottom;
|
|
1083
1083
|
}
|
|
1084
1084
|
} else {
|
|
1085
|
-
var border = repeat(white).times(
|
|
1085
|
+
var border = repeat(white).times(qrcode4.getModuleCount() + 3);
|
|
1086
1086
|
output += border + "\n";
|
|
1087
|
-
|
|
1087
|
+
qrcode4.modules.forEach(function(row2) {
|
|
1088
1088
|
output += white;
|
|
1089
1089
|
output += row2.map(toCell).join("");
|
|
1090
1090
|
output += white + "\n";
|
|
@@ -1103,9 +1103,10 @@ var require_main = __commonJS({
|
|
|
1103
1103
|
|
|
1104
1104
|
// src/index.ts
|
|
1105
1105
|
import "dotenv/config";
|
|
1106
|
-
import { Vultisig as
|
|
1106
|
+
import { Vultisig as Vultisig4 } from "@vultisig/sdk";
|
|
1107
1107
|
import chalk12 from "chalk";
|
|
1108
1108
|
import { program } from "commander";
|
|
1109
|
+
import inquirer8 from "inquirer";
|
|
1109
1110
|
|
|
1110
1111
|
// src/core/command-context.ts
|
|
1111
1112
|
var DEFAULT_PASSWORD_CACHE_TTL = 5 * 60 * 1e3;
|
|
@@ -2010,8 +2011,120 @@ Or use this URL: ${qrPayload}
|
|
|
2010
2011
|
}
|
|
2011
2012
|
}
|
|
2012
2013
|
|
|
2013
|
-
// src/commands/
|
|
2014
|
+
// src/commands/sign.ts
|
|
2014
2015
|
var import_qrcode_terminal2 = __toESM(require_main(), 1);
|
|
2016
|
+
import { Chain as Chain2 } from "@vultisig/sdk";
|
|
2017
|
+
async function executeSignBytes(ctx2, params) {
|
|
2018
|
+
const vault = await ctx2.ensureActiveVault();
|
|
2019
|
+
if (!Object.values(Chain2).includes(params.chain)) {
|
|
2020
|
+
throw new Error(`Invalid chain: ${params.chain}`);
|
|
2021
|
+
}
|
|
2022
|
+
return signBytes(vault, params);
|
|
2023
|
+
}
|
|
2024
|
+
async function signBytes(vault, params) {
|
|
2025
|
+
const hashBytes = Buffer.from(params.bytes, "base64");
|
|
2026
|
+
await ensureVaultUnlocked(vault, params.password);
|
|
2027
|
+
const isSecureVault = vault.type === "secure";
|
|
2028
|
+
const signSpinner = createSpinner(isSecureVault ? "Preparing secure signing session..." : "Signing bytes...");
|
|
2029
|
+
vault.on("signingProgress", ({ step }) => {
|
|
2030
|
+
signSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2031
|
+
});
|
|
2032
|
+
if (isSecureVault) {
|
|
2033
|
+
vault.on("qrCodeReady", ({ qrPayload }) => {
|
|
2034
|
+
if (isJsonOutput()) {
|
|
2035
|
+
printResult(JSON.stringify({ qrPayload }));
|
|
2036
|
+
} else if (isSilent()) {
|
|
2037
|
+
printResult(`QR Payload: ${qrPayload}`);
|
|
2038
|
+
} else {
|
|
2039
|
+
signSpinner.stop();
|
|
2040
|
+
info("\nScan this QR code with your Vultisig mobile app to sign:");
|
|
2041
|
+
import_qrcode_terminal2.default.generate(qrPayload, { small: true });
|
|
2042
|
+
info(`
|
|
2043
|
+
Or use this URL: ${qrPayload}
|
|
2044
|
+
`);
|
|
2045
|
+
signSpinner.start("Waiting for devices to join signing session...");
|
|
2046
|
+
}
|
|
2047
|
+
});
|
|
2048
|
+
vault.on(
|
|
2049
|
+
"deviceJoined",
|
|
2050
|
+
({ deviceId, totalJoined, required }) => {
|
|
2051
|
+
if (!isSilent()) {
|
|
2052
|
+
signSpinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
|
|
2053
|
+
} else if (!isJsonOutput()) {
|
|
2054
|
+
printResult(`Device joined: ${totalJoined}/${required}`);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
);
|
|
2058
|
+
}
|
|
2059
|
+
try {
|
|
2060
|
+
const signature = await vault.signBytes(
|
|
2061
|
+
{
|
|
2062
|
+
data: hashBytes,
|
|
2063
|
+
chain: params.chain
|
|
2064
|
+
},
|
|
2065
|
+
{ signal: params.signal }
|
|
2066
|
+
);
|
|
2067
|
+
signSpinner.succeed("Bytes signed");
|
|
2068
|
+
const sigHex = signature.signature.startsWith("0x") ? signature.signature.slice(2) : signature.signature;
|
|
2069
|
+
const sigBase64 = Buffer.from(sigHex, "hex").toString("base64");
|
|
2070
|
+
const result = {
|
|
2071
|
+
signature: sigBase64,
|
|
2072
|
+
recovery: signature.recovery,
|
|
2073
|
+
format: signature.format
|
|
2074
|
+
};
|
|
2075
|
+
if (isJsonOutput()) {
|
|
2076
|
+
outputJson(result);
|
|
2077
|
+
} else {
|
|
2078
|
+
printResult(`Signature: ${result.signature}`);
|
|
2079
|
+
if (result.recovery !== void 0) {
|
|
2080
|
+
printResult(`Recovery: ${result.recovery}`);
|
|
2081
|
+
}
|
|
2082
|
+
printResult(`Format: ${result.format}`);
|
|
2083
|
+
}
|
|
2084
|
+
return result;
|
|
2085
|
+
} finally {
|
|
2086
|
+
vault.removeAllListeners("signingProgress");
|
|
2087
|
+
if (isSecureVault) {
|
|
2088
|
+
vault.removeAllListeners("qrCodeReady");
|
|
2089
|
+
vault.removeAllListeners("deviceJoined");
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
|
|
2094
|
+
// src/commands/broadcast.ts
|
|
2095
|
+
import { Chain as Chain3, Vultisig as Vultisig3 } from "@vultisig/sdk";
|
|
2096
|
+
async function executeBroadcast(ctx2, params) {
|
|
2097
|
+
const vault = await ctx2.ensureActiveVault();
|
|
2098
|
+
if (!Object.values(Chain3).includes(params.chain)) {
|
|
2099
|
+
throw new Error(`Invalid chain: ${params.chain}`);
|
|
2100
|
+
}
|
|
2101
|
+
const broadcastSpinner = createSpinner("Broadcasting transaction...");
|
|
2102
|
+
try {
|
|
2103
|
+
const txHash = await vault.broadcastRawTx({
|
|
2104
|
+
chain: params.chain,
|
|
2105
|
+
rawTx: params.rawTx
|
|
2106
|
+
});
|
|
2107
|
+
broadcastSpinner.succeed(`Transaction broadcast: ${txHash}`);
|
|
2108
|
+
const result = {
|
|
2109
|
+
txHash,
|
|
2110
|
+
chain: params.chain,
|
|
2111
|
+
explorerUrl: Vultisig3.getTxExplorerUrl(params.chain, txHash)
|
|
2112
|
+
};
|
|
2113
|
+
if (isJsonOutput()) {
|
|
2114
|
+
outputJson(result);
|
|
2115
|
+
} else {
|
|
2116
|
+
printResult(`TX Hash: ${result.txHash}`);
|
|
2117
|
+
printResult(`Explorer: ${result.explorerUrl}`);
|
|
2118
|
+
}
|
|
2119
|
+
return result;
|
|
2120
|
+
} catch (error2) {
|
|
2121
|
+
broadcastSpinner.fail("Broadcast failed");
|
|
2122
|
+
throw error2;
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// src/commands/vault-management.ts
|
|
2127
|
+
var import_qrcode_terminal3 = __toESM(require_main(), 1);
|
|
2015
2128
|
import chalk5 from "chalk";
|
|
2016
2129
|
import { promises as fs } from "fs";
|
|
2017
2130
|
import inquirer4 from "inquirer";
|
|
@@ -2099,7 +2212,7 @@ async function executeCreateFast(ctx2, options) {
|
|
|
2099
2212
|
if (action === "resend") {
|
|
2100
2213
|
const resendSpinner = createSpinner("Resending verification email...");
|
|
2101
2214
|
try {
|
|
2102
|
-
await ctx2.sdk.resendVaultVerification(vaultId);
|
|
2215
|
+
await ctx2.sdk.resendVaultVerification({ vaultId, email, password });
|
|
2103
2216
|
resendSpinner.succeed("Verification email sent!");
|
|
2104
2217
|
info("Check your inbox for the new code. Note: There may be a ~3 minute cooldown between resends.");
|
|
2105
2218
|
} catch (resendErr) {
|
|
@@ -2132,7 +2245,7 @@ async function executeCreateSecure(ctx2, options) {
|
|
|
2132
2245
|
} else {
|
|
2133
2246
|
spinner.stop();
|
|
2134
2247
|
info("\nScan this QR code with your Vultisig mobile app:");
|
|
2135
|
-
|
|
2248
|
+
import_qrcode_terminal3.default.generate(qrPayload, { small: true });
|
|
2136
2249
|
info(`
|
|
2137
2250
|
Or use this URL: ${qrPayload}
|
|
2138
2251
|
`);
|
|
@@ -2200,10 +2313,42 @@ async function executeImport(ctx2, file) {
|
|
|
2200
2313
|
}
|
|
2201
2314
|
async function executeVerify(ctx2, vaultId, options = {}) {
|
|
2202
2315
|
if (options.resend) {
|
|
2316
|
+
let email = options.email;
|
|
2317
|
+
let password = options.password;
|
|
2318
|
+
if (!email || !password) {
|
|
2319
|
+
info("Email and password are required to resend verification.");
|
|
2320
|
+
const answers = await inquirer4.prompt([
|
|
2321
|
+
...!email ? [
|
|
2322
|
+
{
|
|
2323
|
+
type: "input",
|
|
2324
|
+
name: "email",
|
|
2325
|
+
message: "Email address:",
|
|
2326
|
+
validate: (input) => input.includes("@") || "Please enter a valid email"
|
|
2327
|
+
}
|
|
2328
|
+
] : [],
|
|
2329
|
+
...!password ? [
|
|
2330
|
+
{
|
|
2331
|
+
type: "password",
|
|
2332
|
+
name: "password",
|
|
2333
|
+
message: "Vault password:",
|
|
2334
|
+
mask: "*",
|
|
2335
|
+
validate: (input) => input.length >= 8 || "Password must be at least 8 characters"
|
|
2336
|
+
}
|
|
2337
|
+
] : []
|
|
2338
|
+
]);
|
|
2339
|
+
email = email || answers.email;
|
|
2340
|
+
password = password || answers.password;
|
|
2341
|
+
}
|
|
2203
2342
|
const spinner2 = createSpinner("Resending verification email...");
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2343
|
+
try {
|
|
2344
|
+
await ctx2.sdk.resendVaultVerification({ vaultId, email, password });
|
|
2345
|
+
spinner2.succeed("Verification email sent!");
|
|
2346
|
+
info("Check your inbox for the new verification code.");
|
|
2347
|
+
} catch (resendErr) {
|
|
2348
|
+
spinner2.fail("Failed to resend verification email");
|
|
2349
|
+
error(resendErr.message || "Could not resend email. You may need to wait a few minutes.");
|
|
2350
|
+
return false;
|
|
2351
|
+
}
|
|
2207
2352
|
}
|
|
2208
2353
|
let code = options.code;
|
|
2209
2354
|
if (!code) {
|
|
@@ -2361,6 +2506,223 @@ async function executeInfo(ctx2) {
|
|
|
2361
2506
|
}
|
|
2362
2507
|
displayVaultInfo(vault);
|
|
2363
2508
|
}
|
|
2509
|
+
async function executeImportSeedphraseFast(ctx2, options) {
|
|
2510
|
+
const { mnemonic, name, password, email, discoverChains, chains, signal } = options;
|
|
2511
|
+
const validateSpinner = createSpinner("Validating seedphrase...");
|
|
2512
|
+
const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
|
|
2513
|
+
if (!validation.valid) {
|
|
2514
|
+
validateSpinner.fail("Invalid seedphrase");
|
|
2515
|
+
if (validation.invalidWords?.length) {
|
|
2516
|
+
warn(`Invalid words: ${validation.invalidWords.join(", ")}`);
|
|
2517
|
+
}
|
|
2518
|
+
throw new Error(validation.error || "Invalid mnemonic phrase");
|
|
2519
|
+
}
|
|
2520
|
+
validateSpinner.succeed(`Valid ${validation.wordCount}-word seedphrase`);
|
|
2521
|
+
if (discoverChains) {
|
|
2522
|
+
const discoverSpinner = createSpinner("Discovering chains with balances...");
|
|
2523
|
+
try {
|
|
2524
|
+
const discovered = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2525
|
+
discoverSpinner.text = `Discovering: ${p.chain || "scanning"} (${p.chainsProcessed}/${p.chainsTotal})`;
|
|
2526
|
+
});
|
|
2527
|
+
const chainsWithBalance = discovered.filter((c) => c.hasBalance);
|
|
2528
|
+
discoverSpinner.succeed(`Found ${chainsWithBalance.length} chains with balances`);
|
|
2529
|
+
if (chainsWithBalance.length > 0 && !isSilent()) {
|
|
2530
|
+
info("\nChains with balances:");
|
|
2531
|
+
for (const result of chainsWithBalance) {
|
|
2532
|
+
info(` ${result.chain}: ${result.balance} ${result.symbol}`);
|
|
2533
|
+
}
|
|
2534
|
+
info("");
|
|
2535
|
+
}
|
|
2536
|
+
} catch {
|
|
2537
|
+
discoverSpinner.warn("Chain discovery failed, continuing with import...");
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
const importSpinner = createSpinner("Importing seedphrase...");
|
|
2541
|
+
const vaultId = await withAbortSignal(
|
|
2542
|
+
ctx2.sdk.importSeedphraseAsFastVault({
|
|
2543
|
+
mnemonic,
|
|
2544
|
+
name,
|
|
2545
|
+
password,
|
|
2546
|
+
email,
|
|
2547
|
+
// Don't pass discoverChains - CLI handles discovery above
|
|
2548
|
+
chains,
|
|
2549
|
+
onProgress: (step) => {
|
|
2550
|
+
importSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2551
|
+
}
|
|
2552
|
+
}),
|
|
2553
|
+
signal
|
|
2554
|
+
);
|
|
2555
|
+
importSpinner.succeed("Keys generated, email verification required");
|
|
2556
|
+
warn("\nA verification code has been sent to your email.");
|
|
2557
|
+
info("Please check your inbox and enter the code.");
|
|
2558
|
+
const MAX_VERIFY_ATTEMPTS = 5;
|
|
2559
|
+
let attempts = 0;
|
|
2560
|
+
while (attempts < MAX_VERIFY_ATTEMPTS) {
|
|
2561
|
+
attempts++;
|
|
2562
|
+
const codeAnswer = await inquirer4.prompt([
|
|
2563
|
+
{
|
|
2564
|
+
type: "input",
|
|
2565
|
+
name: "code",
|
|
2566
|
+
message: `Verification code sent to ${email}. Enter code:`,
|
|
2567
|
+
validate: (input) => /^\d{4,6}$/.test(input) || "Code must be 4-6 digits"
|
|
2568
|
+
}
|
|
2569
|
+
]);
|
|
2570
|
+
const verifySpinner = createSpinner("Verifying email code...");
|
|
2571
|
+
try {
|
|
2572
|
+
const vault = await ctx2.sdk.verifyVault(vaultId, codeAnswer.code);
|
|
2573
|
+
verifySpinner.succeed("Email verified successfully!");
|
|
2574
|
+
setupVaultEvents(vault);
|
|
2575
|
+
await ctx2.setActiveVault(vault);
|
|
2576
|
+
success("\n+ Vault imported from seedphrase!");
|
|
2577
|
+
info("\nYour vault is ready. Run the following commands:");
|
|
2578
|
+
printResult(chalk5.cyan(" vultisig balance ") + "- View balances");
|
|
2579
|
+
printResult(chalk5.cyan(" vultisig addresses ") + "- View addresses");
|
|
2580
|
+
printResult(chalk5.cyan(" vultisig portfolio ") + "- View portfolio value");
|
|
2581
|
+
return vault;
|
|
2582
|
+
} catch (err) {
|
|
2583
|
+
verifySpinner.fail("Verification failed");
|
|
2584
|
+
error(`
|
|
2585
|
+
\u2717 ${err.message || "Invalid verification code"}`);
|
|
2586
|
+
if (attempts >= MAX_VERIFY_ATTEMPTS) {
|
|
2587
|
+
warn("\nMaximum attempts reached.");
|
|
2588
|
+
warn("\nTo retry verification later, use:");
|
|
2589
|
+
info(` vultisig verify ${vaultId}`);
|
|
2590
|
+
err.exitCode = 1;
|
|
2591
|
+
throw err;
|
|
2592
|
+
}
|
|
2593
|
+
const { action } = await inquirer4.prompt([
|
|
2594
|
+
{
|
|
2595
|
+
type: "list",
|
|
2596
|
+
name: "action",
|
|
2597
|
+
message: `What would you like to do? (${MAX_VERIFY_ATTEMPTS - attempts} attempts remaining)`,
|
|
2598
|
+
choices: [
|
|
2599
|
+
{ name: "Enter a different code", value: "retry" },
|
|
2600
|
+
{ name: "Resend verification email (rate limited)", value: "resend" },
|
|
2601
|
+
{ name: "Abort and verify later", value: "abort" }
|
|
2602
|
+
]
|
|
2603
|
+
}
|
|
2604
|
+
]);
|
|
2605
|
+
if (action === "abort") {
|
|
2606
|
+
warn("\nSeedphrase import paused. To complete verification, use:");
|
|
2607
|
+
info(` vultisig verify ${vaultId}`);
|
|
2608
|
+
warn("\nNote: The pending vault is stored in memory only and will be lost if you exit.");
|
|
2609
|
+
return void 0;
|
|
2610
|
+
}
|
|
2611
|
+
if (action === "resend") {
|
|
2612
|
+
const resendSpinner = createSpinner("Resending verification email...");
|
|
2613
|
+
try {
|
|
2614
|
+
await ctx2.sdk.resendVaultVerification({ vaultId, email, password });
|
|
2615
|
+
resendSpinner.succeed("Verification email sent!");
|
|
2616
|
+
info("Check your inbox for the new code.");
|
|
2617
|
+
} catch (resendErr) {
|
|
2618
|
+
resendSpinner.fail("Failed to resend");
|
|
2619
|
+
warn(resendErr.message || "Could not resend email.");
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
throw new Error("Verification loop exited unexpectedly");
|
|
2625
|
+
}
|
|
2626
|
+
async function executeImportSeedphraseSecure(ctx2, options) {
|
|
2627
|
+
const { mnemonic, name, password, threshold, shares: totalShares, discoverChains, chains, signal } = options;
|
|
2628
|
+
const validateSpinner = createSpinner("Validating seedphrase...");
|
|
2629
|
+
const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
|
|
2630
|
+
if (!validation.valid) {
|
|
2631
|
+
validateSpinner.fail("Invalid seedphrase");
|
|
2632
|
+
if (validation.invalidWords?.length) {
|
|
2633
|
+
warn(`Invalid words: ${validation.invalidWords.join(", ")}`);
|
|
2634
|
+
}
|
|
2635
|
+
throw new Error(validation.error || "Invalid mnemonic phrase");
|
|
2636
|
+
}
|
|
2637
|
+
validateSpinner.succeed(`Valid ${validation.wordCount}-word seedphrase`);
|
|
2638
|
+
if (discoverChains) {
|
|
2639
|
+
const discoverSpinner = createSpinner("Discovering chains with balances...");
|
|
2640
|
+
try {
|
|
2641
|
+
const discovered = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2642
|
+
discoverSpinner.text = `Discovering: ${p.chain || "scanning"} (${p.chainsProcessed}/${p.chainsTotal})`;
|
|
2643
|
+
});
|
|
2644
|
+
const chainsWithBalance = discovered.filter((c) => c.hasBalance);
|
|
2645
|
+
discoverSpinner.succeed(`Found ${chainsWithBalance.length} chains with balances`);
|
|
2646
|
+
if (chainsWithBalance.length > 0 && !isSilent()) {
|
|
2647
|
+
info("\nChains with balances:");
|
|
2648
|
+
for (const result of chainsWithBalance) {
|
|
2649
|
+
info(` ${result.chain}: ${result.balance} ${result.symbol}`);
|
|
2650
|
+
}
|
|
2651
|
+
info("");
|
|
2652
|
+
}
|
|
2653
|
+
} catch {
|
|
2654
|
+
discoverSpinner.warn("Chain discovery failed, continuing with import...");
|
|
2655
|
+
}
|
|
2656
|
+
}
|
|
2657
|
+
const importSpinner = createSpinner("Importing seedphrase as secure vault...");
|
|
2658
|
+
try {
|
|
2659
|
+
const result = await withAbortSignal(
|
|
2660
|
+
ctx2.sdk.importSeedphraseAsSecureVault({
|
|
2661
|
+
mnemonic,
|
|
2662
|
+
name,
|
|
2663
|
+
password,
|
|
2664
|
+
devices: totalShares,
|
|
2665
|
+
threshold,
|
|
2666
|
+
// Don't pass discoverChains - CLI handles discovery above
|
|
2667
|
+
chains,
|
|
2668
|
+
onProgress: (step) => {
|
|
2669
|
+
importSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2670
|
+
},
|
|
2671
|
+
onQRCodeReady: (qrPayload) => {
|
|
2672
|
+
if (isJsonOutput()) {
|
|
2673
|
+
printResult(qrPayload);
|
|
2674
|
+
} else if (isSilent()) {
|
|
2675
|
+
printResult(`QR Payload: ${qrPayload}`);
|
|
2676
|
+
} else {
|
|
2677
|
+
importSpinner.stop();
|
|
2678
|
+
info("\nScan this QR code with your Vultisig mobile app:");
|
|
2679
|
+
import_qrcode_terminal3.default.generate(qrPayload, { small: true });
|
|
2680
|
+
info(`
|
|
2681
|
+
Or use this URL: ${qrPayload}
|
|
2682
|
+
`);
|
|
2683
|
+
info(chalk5.gray("(Press Ctrl+C to cancel)\n"));
|
|
2684
|
+
importSpinner.start(`Waiting for ${totalShares} devices to join...`);
|
|
2685
|
+
}
|
|
2686
|
+
},
|
|
2687
|
+
onDeviceJoined: (deviceId, totalJoined, required) => {
|
|
2688
|
+
if (!isSilent()) {
|
|
2689
|
+
importSpinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
|
|
2690
|
+
} else if (!isJsonOutput()) {
|
|
2691
|
+
printResult(`Device joined: ${totalJoined}/${required}`);
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
}),
|
|
2695
|
+
signal
|
|
2696
|
+
);
|
|
2697
|
+
setupVaultEvents(result.vault);
|
|
2698
|
+
await ctx2.setActiveVault(result.vault);
|
|
2699
|
+
importSpinner.succeed(`Secure vault imported: ${name} (${threshold}-of-${totalShares})`);
|
|
2700
|
+
if (isJsonOutput()) {
|
|
2701
|
+
outputJson({
|
|
2702
|
+
vault: {
|
|
2703
|
+
id: result.vaultId,
|
|
2704
|
+
name,
|
|
2705
|
+
type: "secure",
|
|
2706
|
+
threshold,
|
|
2707
|
+
totalSigners: totalShares
|
|
2708
|
+
},
|
|
2709
|
+
sessionId: result.sessionId
|
|
2710
|
+
});
|
|
2711
|
+
return result.vault;
|
|
2712
|
+
}
|
|
2713
|
+
warn(`
|
|
2714
|
+
Important: Save your vault backup file (.vult) in a secure location.`);
|
|
2715
|
+
warn(`This is a ${threshold}-of-${totalShares} vault. You'll need ${threshold} devices to sign transactions.`);
|
|
2716
|
+
success("\n+ Vault imported from seedphrase!");
|
|
2717
|
+
return result.vault;
|
|
2718
|
+
} catch (err) {
|
|
2719
|
+
importSpinner.fail("Secure vault import failed");
|
|
2720
|
+
if (err.message?.includes("not implemented")) {
|
|
2721
|
+
warn("\nSecure vault seedphrase import is not yet implemented in the SDK");
|
|
2722
|
+
}
|
|
2723
|
+
throw err;
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2364
2726
|
|
|
2365
2727
|
// src/commands/swap.ts
|
|
2366
2728
|
async function executeSwapChains(ctx2) {
|
|
@@ -2525,7 +2887,7 @@ async function executeSwap(ctx2, options) {
|
|
|
2525
2887
|
}
|
|
2526
2888
|
|
|
2527
2889
|
// src/commands/settings.ts
|
|
2528
|
-
import { Chain as
|
|
2890
|
+
import { Chain as Chain4, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
|
|
2529
2891
|
import chalk6 from "chalk";
|
|
2530
2892
|
import inquirer5 from "inquirer";
|
|
2531
2893
|
async function executeCurrency(ctx2, newCurrency) {
|
|
@@ -2593,7 +2955,7 @@ async function executeAddressBook(ctx2, options = {}) {
|
|
|
2593
2955
|
type: "list",
|
|
2594
2956
|
name: "chain",
|
|
2595
2957
|
message: "Select chain:",
|
|
2596
|
-
choices: Object.values(
|
|
2958
|
+
choices: Object.values(Chain4)
|
|
2597
2959
|
});
|
|
2598
2960
|
}
|
|
2599
2961
|
if (!address) {
|
|
@@ -2670,7 +3032,7 @@ Address Book${options.chain ? ` (${options.chain})` : ""}:
|
|
|
2670
3032
|
}
|
|
2671
3033
|
|
|
2672
3034
|
// src/interactive/completer.ts
|
|
2673
|
-
import { Chain as
|
|
3035
|
+
import { Chain as Chain5 } from "@vultisig/sdk";
|
|
2674
3036
|
import fs2 from "fs";
|
|
2675
3037
|
import path2 from "path";
|
|
2676
3038
|
var COMMANDS = [
|
|
@@ -2678,6 +3040,7 @@ var COMMANDS = [
|
|
|
2678
3040
|
"vaults",
|
|
2679
3041
|
"vault",
|
|
2680
3042
|
"import",
|
|
3043
|
+
"import-seedphrase",
|
|
2681
3044
|
"create",
|
|
2682
3045
|
"info",
|
|
2683
3046
|
"export",
|
|
@@ -2736,6 +3099,13 @@ function createCompleter(ctx2) {
|
|
|
2736
3099
|
const partial = parts[1] || "";
|
|
2737
3100
|
return completeChainName(partial);
|
|
2738
3101
|
}
|
|
3102
|
+
if ((command === "create" || command === "import-seedphrase") && parts.length === 2) {
|
|
3103
|
+
const types = ["fast", "secure"];
|
|
3104
|
+
const partial = parts[1] || "";
|
|
3105
|
+
const partialLower = partial.toLowerCase();
|
|
3106
|
+
const matches = types.filter((t) => t.startsWith(partialLower));
|
|
3107
|
+
return [matches.length ? matches : types, partial];
|
|
3108
|
+
}
|
|
2739
3109
|
const hits = COMMANDS.filter((c) => c.startsWith(line));
|
|
2740
3110
|
const show = hits.length ? hits : COMMANDS;
|
|
2741
3111
|
return [show, line];
|
|
@@ -2793,7 +3163,7 @@ function completeVaultName(ctx2, partial) {
|
|
|
2793
3163
|
return [show, partial];
|
|
2794
3164
|
}
|
|
2795
3165
|
function completeChainName(partial) {
|
|
2796
|
-
const allChains = Object.values(
|
|
3166
|
+
const allChains = Object.values(Chain5);
|
|
2797
3167
|
const partialLower = partial.toLowerCase();
|
|
2798
3168
|
const matches = allChains.filter((chain) => chain.toLowerCase().startsWith(partialLower));
|
|
2799
3169
|
matches.sort();
|
|
@@ -2801,7 +3171,7 @@ function completeChainName(partial) {
|
|
|
2801
3171
|
return [show, partial];
|
|
2802
3172
|
}
|
|
2803
3173
|
function findChainByName(name) {
|
|
2804
|
-
const allChains = Object.values(
|
|
3174
|
+
const allChains = Object.values(Chain5);
|
|
2805
3175
|
const nameLower = name.toLowerCase();
|
|
2806
3176
|
const found = allChains.find((chain) => chain.toLowerCase() === nameLower);
|
|
2807
3177
|
return found ? found : null;
|
|
@@ -3493,6 +3863,9 @@ Error: ${error2.message}`));
|
|
|
3493
3863
|
case "create":
|
|
3494
3864
|
await this.createVault(args);
|
|
3495
3865
|
break;
|
|
3866
|
+
case "import-seedphrase":
|
|
3867
|
+
await this.importSeedphrase(args);
|
|
3868
|
+
break;
|
|
3496
3869
|
case "info":
|
|
3497
3870
|
await executeInfo(this.ctx);
|
|
3498
3871
|
break;
|
|
@@ -3673,6 +4046,92 @@ Error: ${error2.message}`));
|
|
|
3673
4046
|
this.eventBuffer.setupVaultListeners(vault);
|
|
3674
4047
|
}
|
|
3675
4048
|
}
|
|
4049
|
+
async importSeedphrase(args) {
|
|
4050
|
+
const type = args[0]?.toLowerCase();
|
|
4051
|
+
if (!type || type !== "fast" && type !== "secure") {
|
|
4052
|
+
console.log(chalk9.cyan("Usage: import-seedphrase <fast|secure>"));
|
|
4053
|
+
console.log(chalk9.gray(" fast - Import with VultiServer (2-of-2)"));
|
|
4054
|
+
console.log(chalk9.gray(" secure - Import with device coordination (N-of-M)"));
|
|
4055
|
+
return;
|
|
4056
|
+
}
|
|
4057
|
+
console.log(chalk9.cyan("\nEnter your recovery phrase (words separated by spaces):"));
|
|
4058
|
+
const mnemonic = await this.promptPassword("Seedphrase");
|
|
4059
|
+
const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
|
|
4060
|
+
if (!validation.valid) {
|
|
4061
|
+
console.log(chalk9.red(`Invalid seedphrase: ${validation.error}`));
|
|
4062
|
+
if (validation.invalidWords?.length) {
|
|
4063
|
+
console.log(chalk9.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
|
|
4064
|
+
}
|
|
4065
|
+
return;
|
|
4066
|
+
}
|
|
4067
|
+
console.log(chalk9.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
|
|
4068
|
+
let vault;
|
|
4069
|
+
if (type === "fast") {
|
|
4070
|
+
const name = await this.prompt("Vault name");
|
|
4071
|
+
if (!name) {
|
|
4072
|
+
console.log(chalk9.red("Name is required"));
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
const password = await this.promptPassword("Vault password");
|
|
4076
|
+
if (!password) {
|
|
4077
|
+
console.log(chalk9.red("Password is required"));
|
|
4078
|
+
return;
|
|
4079
|
+
}
|
|
4080
|
+
const email = await this.prompt("Email for verification");
|
|
4081
|
+
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4082
|
+
console.log(chalk9.red("Valid email is required"));
|
|
4083
|
+
return;
|
|
4084
|
+
}
|
|
4085
|
+
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
4086
|
+
const discoverChains = discoverStr.toLowerCase() === "y";
|
|
4087
|
+
vault = await this.withCancellation(
|
|
4088
|
+
(signal) => executeImportSeedphraseFast(this.ctx, {
|
|
4089
|
+
mnemonic,
|
|
4090
|
+
name,
|
|
4091
|
+
password,
|
|
4092
|
+
email,
|
|
4093
|
+
discoverChains,
|
|
4094
|
+
signal
|
|
4095
|
+
})
|
|
4096
|
+
);
|
|
4097
|
+
} else {
|
|
4098
|
+
const name = await this.prompt("Vault name");
|
|
4099
|
+
if (!name) {
|
|
4100
|
+
console.log(chalk9.red("Name is required"));
|
|
4101
|
+
return;
|
|
4102
|
+
}
|
|
4103
|
+
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
4104
|
+
const shares = parseInt(sharesStr, 10);
|
|
4105
|
+
if (isNaN(shares) || shares < 2) {
|
|
4106
|
+
console.log(chalk9.red("Must have at least 2 shares"));
|
|
4107
|
+
return;
|
|
4108
|
+
}
|
|
4109
|
+
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
4110
|
+
const threshold = parseInt(thresholdStr, 10);
|
|
4111
|
+
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
4112
|
+
console.log(chalk9.red(`Threshold must be between 1 and ${shares}`));
|
|
4113
|
+
return;
|
|
4114
|
+
}
|
|
4115
|
+
const password = await this.promptPassword("Vault password (optional, Enter to skip)");
|
|
4116
|
+
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
4117
|
+
const discoverChains = discoverStr.toLowerCase() === "y";
|
|
4118
|
+
vault = await this.withCancellation(
|
|
4119
|
+
(signal) => executeImportSeedphraseSecure(this.ctx, {
|
|
4120
|
+
mnemonic,
|
|
4121
|
+
name,
|
|
4122
|
+
password: password || void 0,
|
|
4123
|
+
threshold,
|
|
4124
|
+
shares,
|
|
4125
|
+
discoverChains,
|
|
4126
|
+
signal
|
|
4127
|
+
})
|
|
4128
|
+
);
|
|
4129
|
+
}
|
|
4130
|
+
if (vault) {
|
|
4131
|
+
this.ctx.addVault(vault);
|
|
4132
|
+
this.eventBuffer.setupVaultListeners(vault);
|
|
4133
|
+
}
|
|
4134
|
+
}
|
|
3676
4135
|
async runBalance(args) {
|
|
3677
4136
|
const chainStr = args[0];
|
|
3678
4137
|
const includeTokens = args.includes("-t") || args.includes("--tokens");
|
|
@@ -3928,7 +4387,7 @@ var cachedVersion = null;
|
|
|
3928
4387
|
function getVersion() {
|
|
3929
4388
|
if (cachedVersion) return cachedVersion;
|
|
3930
4389
|
if (true) {
|
|
3931
|
-
cachedVersion = "0.2.0
|
|
4390
|
+
cachedVersion = "0.2.0";
|
|
3932
4391
|
return cachedVersion;
|
|
3933
4392
|
}
|
|
3934
4393
|
try {
|
|
@@ -4402,7 +4861,7 @@ async function init(vaultOverride, unlockPassword) {
|
|
|
4402
4861
|
if (unlockPassword && vaultSelector) {
|
|
4403
4862
|
cachePassword(vaultSelector, unlockPassword);
|
|
4404
4863
|
}
|
|
4405
|
-
const sdk = new
|
|
4864
|
+
const sdk = new Vultisig4({
|
|
4406
4865
|
onPasswordRequired: createPasswordCallback()
|
|
4407
4866
|
});
|
|
4408
4867
|
await sdk.initialize();
|
|
@@ -4447,16 +4906,104 @@ program.command("import <file>").description("Import vault from .vult file").act
|
|
|
4447
4906
|
await executeImport(context, file);
|
|
4448
4907
|
})
|
|
4449
4908
|
);
|
|
4450
|
-
program.command("
|
|
4451
|
-
|
|
4452
|
-
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
|
|
4909
|
+
var importSeedphraseCmd = program.command("import-seedphrase").description("Import wallet from BIP39 seedphrase");
|
|
4910
|
+
async function promptSeedphrase() {
|
|
4911
|
+
info("\nEnter your 12 or 24-word recovery phrase.");
|
|
4912
|
+
info("Words will be hidden as you type.\n");
|
|
4913
|
+
const answer = await inquirer8.prompt([
|
|
4914
|
+
{
|
|
4915
|
+
type: "password",
|
|
4916
|
+
name: "mnemonic",
|
|
4917
|
+
message: "Seedphrase:",
|
|
4918
|
+
mask: "*",
|
|
4919
|
+
validate: (input) => {
|
|
4920
|
+
const words = input.trim().split(/\s+/);
|
|
4921
|
+
if (words.length !== 12 && words.length !== 24) {
|
|
4922
|
+
return `Expected 12 or 24 words, got ${words.length}`;
|
|
4923
|
+
}
|
|
4924
|
+
return true;
|
|
4925
|
+
}
|
|
4458
4926
|
}
|
|
4459
|
-
|
|
4927
|
+
]);
|
|
4928
|
+
return answer.mnemonic.trim().toLowerCase();
|
|
4929
|
+
}
|
|
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(
|
|
4931
|
+
withExit(
|
|
4932
|
+
async (options) => {
|
|
4933
|
+
const context = await init(program.opts().vault);
|
|
4934
|
+
let mnemonic = options.mnemonic;
|
|
4935
|
+
if (!mnemonic) {
|
|
4936
|
+
mnemonic = await promptSeedphrase();
|
|
4937
|
+
}
|
|
4938
|
+
let chains;
|
|
4939
|
+
if (options.chains) {
|
|
4940
|
+
const chainNames = options.chains.split(",").map((c) => c.trim());
|
|
4941
|
+
chains = [];
|
|
4942
|
+
for (const name of chainNames) {
|
|
4943
|
+
const chain = findChainByName(name);
|
|
4944
|
+
if (chain) {
|
|
4945
|
+
chains.push(chain);
|
|
4946
|
+
} else {
|
|
4947
|
+
console.warn(`Warning: Unknown chain "${name}" - skipping`);
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
}
|
|
4951
|
+
await executeImportSeedphraseFast(context, {
|
|
4952
|
+
mnemonic,
|
|
4953
|
+
name: options.name,
|
|
4954
|
+
password: options.password,
|
|
4955
|
+
email: options.email,
|
|
4956
|
+
discoverChains: options.discoverChains,
|
|
4957
|
+
chains
|
|
4958
|
+
});
|
|
4959
|
+
}
|
|
4960
|
+
)
|
|
4961
|
+
);
|
|
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(
|
|
4963
|
+
withExit(
|
|
4964
|
+
async (options) => {
|
|
4965
|
+
const context = await init(program.opts().vault);
|
|
4966
|
+
let mnemonic = options.mnemonic;
|
|
4967
|
+
if (!mnemonic) {
|
|
4968
|
+
mnemonic = await promptSeedphrase();
|
|
4969
|
+
}
|
|
4970
|
+
let chains;
|
|
4971
|
+
if (options.chains) {
|
|
4972
|
+
const chainNames = options.chains.split(",").map((c) => c.trim());
|
|
4973
|
+
chains = [];
|
|
4974
|
+
for (const name of chainNames) {
|
|
4975
|
+
const chain = findChainByName(name);
|
|
4976
|
+
if (chain) {
|
|
4977
|
+
chains.push(chain);
|
|
4978
|
+
} else {
|
|
4979
|
+
console.warn(`Warning: Unknown chain "${name}" - skipping`);
|
|
4980
|
+
}
|
|
4981
|
+
}
|
|
4982
|
+
}
|
|
4983
|
+
await executeImportSeedphraseSecure(context, {
|
|
4984
|
+
mnemonic,
|
|
4985
|
+
name: options.name,
|
|
4986
|
+
password: options.password,
|
|
4987
|
+
threshold: parseInt(options.threshold, 10),
|
|
4988
|
+
shares: parseInt(options.shares, 10),
|
|
4989
|
+
discoverChains: options.discoverChains,
|
|
4990
|
+
chains
|
|
4991
|
+
});
|
|
4992
|
+
}
|
|
4993
|
+
)
|
|
4994
|
+
);
|
|
4995
|
+
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
|
+
withExit(
|
|
4997
|
+
async (vaultId, options) => {
|
|
4998
|
+
const context = await init(program.opts().vault);
|
|
4999
|
+
const verified = await executeVerify(context, vaultId, options);
|
|
5000
|
+
if (!verified) {
|
|
5001
|
+
const err = new Error("Verification failed");
|
|
5002
|
+
err.exitCode = 1;
|
|
5003
|
+
throw err;
|
|
5004
|
+
}
|
|
5005
|
+
}
|
|
5006
|
+
)
|
|
4460
5007
|
);
|
|
4461
5008
|
program.command("balance [chain]").description("Show balance for a chain or all chains").option("-t, --tokens", "Include token balances").action(
|
|
4462
5009
|
withExit(async (chainStr, options) => {
|
|
@@ -4491,6 +5038,25 @@ program.command("send <chain> <to> <amount>").description("Send tokens to an add
|
|
|
4491
5038
|
}
|
|
4492
5039
|
)
|
|
4493
5040
|
);
|
|
5041
|
+
program.command("sign").description("Sign pre-hashed bytes (for externally constructed transactions)").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--bytes <base64>", "Base64-encoded pre-hashed data to sign").option("--password <password>", "Vault password for signing").action(
|
|
5042
|
+
withExit(async (options) => {
|
|
5043
|
+
const context = await init(program.opts().vault, options.password);
|
|
5044
|
+
await executeSignBytes(context, {
|
|
5045
|
+
chain: findChainByName(options.chain) || options.chain,
|
|
5046
|
+
bytes: options.bytes,
|
|
5047
|
+
password: options.password
|
|
5048
|
+
});
|
|
5049
|
+
})
|
|
5050
|
+
);
|
|
5051
|
+
program.command("broadcast").description("Broadcast a pre-signed raw transaction").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--raw-tx <hex>", "Hex-encoded signed transaction").action(
|
|
5052
|
+
withExit(async (options) => {
|
|
5053
|
+
const context = await init(program.opts().vault);
|
|
5054
|
+
await executeBroadcast(context, {
|
|
5055
|
+
chain: findChainByName(options.chain) || options.chain,
|
|
5056
|
+
rawTx: options.rawTx
|
|
5057
|
+
});
|
|
5058
|
+
})
|
|
5059
|
+
);
|
|
4494
5060
|
program.command("portfolio").description("Show total portfolio value").option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").action(
|
|
4495
5061
|
withExit(async (options) => {
|
|
4496
5062
|
const context = await init(program.opts().vault);
|
|
@@ -4667,7 +5233,7 @@ program.command("update").description("Check for updates and show update command
|
|
|
4667
5233
|
);
|
|
4668
5234
|
setupCompletionCommand(program);
|
|
4669
5235
|
async function startInteractiveMode() {
|
|
4670
|
-
const sdk = new
|
|
5236
|
+
const sdk = new Vultisig4({
|
|
4671
5237
|
onPasswordRequired: createPasswordCallback()
|
|
4672
5238
|
});
|
|
4673
5239
|
await sdk.initialize();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vultisig/cli",
|
|
3
|
-
"version": "0.2.0
|
|
3
|
+
"version": "0.2.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.2.0",
|
|
52
52
|
"chalk": "^5.3.0",
|
|
53
53
|
"cli-table3": "^0.6.5",
|
|
54
54
|
"commander": "^12.0.0",
|