@vultisig/cli 0.2.0-alpha.7 → 0.2.0-beta.9
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 +49 -0
- package/README.md +221 -0
- package/dist/index.js +224 -62
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# @vultisig/cli
|
|
2
2
|
|
|
3
|
+
## 0.2.0-beta.9
|
|
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
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- Updated dependencies [[`a36a7f6`](https://github.com/vultisig/vultisig-sdk/commit/a36a7f614c03e32ebc7e843cbf1ab30b6be0d4af), [`91990d3`](https://github.com/vultisig/vultisig-sdk/commit/91990d3fc7ef1a8d7068f5cbae8f8f3dda5b68f3)]:
|
|
29
|
+
- @vultisig/sdk@0.2.0-beta.9
|
|
30
|
+
|
|
31
|
+
## 0.2.0-beta.8
|
|
32
|
+
|
|
33
|
+
### Minor Changes
|
|
34
|
+
|
|
35
|
+
- [#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
|
|
36
|
+
|
|
37
|
+
The export command now has two distinct password options:
|
|
38
|
+
- `--password`: Unlocks the vault (decrypts stored keyshares for encrypted vaults)
|
|
39
|
+
- `--exportPassword`: Encrypts the exported file (defaults to `--password` if not specified)
|
|
40
|
+
|
|
41
|
+
This fixes the "Password required but callback returned empty value" error when exporting encrypted vaults.
|
|
42
|
+
|
|
43
|
+
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.
|
|
44
|
+
|
|
45
|
+
### Patch Changes
|
|
46
|
+
|
|
47
|
+
- [#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).
|
|
48
|
+
|
|
49
|
+
- Updated dependencies [[`008db7f`](https://github.com/vultisig/vultisig-sdk/commit/008db7fb27580ec78df3bbc41b25aac24924ffd8)]:
|
|
50
|
+
- @vultisig/sdk@0.2.0-beta.8
|
|
51
|
+
|
|
3
52
|
## 0.2.0-alpha.7
|
|
4
53
|
|
|
5
54
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -186,6 +186,29 @@ vultisig -i
|
|
|
186
186
|
- `--shares <n>` - Number of devices for secure vault (default: 2)
|
|
187
187
|
- `--threshold <n>` - Signing threshold (default: ceil((shares+1)/2))
|
|
188
188
|
|
|
189
|
+
**Export options:**
|
|
190
|
+
- `[path]` - Output file or directory (defaults to SDK-generated filename in current directory)
|
|
191
|
+
- `--password <password>` - Password to unlock encrypted vaults
|
|
192
|
+
- `--exportPassword <password>` - Password to encrypt the export file (defaults to `--password` if provided)
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Export to current directory (prompts for export password)
|
|
196
|
+
vultisig export
|
|
197
|
+
|
|
198
|
+
# Export to specific directory
|
|
199
|
+
vultisig export /path/to/backups/
|
|
200
|
+
|
|
201
|
+
# Export with encryption (same password for unlock and export)
|
|
202
|
+
vultisig export --password mypassword
|
|
203
|
+
|
|
204
|
+
# Export with different passwords for unlock vs export
|
|
205
|
+
vultisig export --password unlockPass --exportPassword exportPass
|
|
206
|
+
|
|
207
|
+
# Export without encryption (leave password prompt empty)
|
|
208
|
+
vultisig export
|
|
209
|
+
# > Enter password for export encryption (leave empty for no encryption): [enter]
|
|
210
|
+
```
|
|
211
|
+
|
|
189
212
|
### Wallet Operations
|
|
190
213
|
|
|
191
214
|
| Command | Description |
|
|
@@ -221,6 +244,204 @@ vultisig swap ethereum bitcoin 0.1 --password mypassword
|
|
|
221
244
|
vultisig swap ethereum bitcoin 0.1 -y --password mypassword
|
|
222
245
|
```
|
|
223
246
|
|
|
247
|
+
### Advanced Operations
|
|
248
|
+
|
|
249
|
+
| Command | Description |
|
|
250
|
+
|---------|-------------|
|
|
251
|
+
| `sign` | Sign pre-hashed bytes for custom transactions |
|
|
252
|
+
| `broadcast` | Broadcast a pre-signed raw transaction |
|
|
253
|
+
|
|
254
|
+
#### Signing Arbitrary Bytes
|
|
255
|
+
|
|
256
|
+
Sign pre-hashed data for externally constructed transactions:
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# Sign a pre-hashed message (base64 encoded)
|
|
260
|
+
vultisig sign --chain ethereum --bytes "aGVsbG8gd29ybGQ="
|
|
261
|
+
|
|
262
|
+
# With password
|
|
263
|
+
vultisig sign --chain bitcoin --bytes "..." --password mypassword
|
|
264
|
+
|
|
265
|
+
# JSON output
|
|
266
|
+
vultisig sign --chain ethereum --bytes "..." -o json
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Output:**
|
|
270
|
+
```
|
|
271
|
+
Signature: <base64-encoded signature>
|
|
272
|
+
Recovery: 0
|
|
273
|
+
Format: ecdsa
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
**JSON output:**
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"signature": "<base64>",
|
|
280
|
+
"recovery": 0,
|
|
281
|
+
"format": "ecdsa"
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
#### Broadcasting Raw Transactions
|
|
286
|
+
|
|
287
|
+
Broadcast pre-signed transactions to the network:
|
|
288
|
+
|
|
289
|
+
```bash
|
|
290
|
+
# EVM transaction (hex)
|
|
291
|
+
vultisig broadcast --chain ethereum --raw-tx "0x02f8..."
|
|
292
|
+
|
|
293
|
+
# Bitcoin transaction (hex)
|
|
294
|
+
vultisig broadcast --chain bitcoin --raw-tx "0200000001..."
|
|
295
|
+
|
|
296
|
+
# Solana transaction (base64)
|
|
297
|
+
vultisig broadcast --chain solana --raw-tx "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAQABAwIAAA..."
|
|
298
|
+
|
|
299
|
+
# Sui transaction (JSON)
|
|
300
|
+
vultisig broadcast --chain sui --raw-tx '{"unsignedTx":"...","signature":"..."}'
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
**Output:**
|
|
304
|
+
```
|
|
305
|
+
TX Hash: 0x9f8e7d6c...
|
|
306
|
+
Explorer: https://etherscan.io/tx/0x9f8e7d6c...
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Supported broadcast formats by chain:**
|
|
310
|
+
|
|
311
|
+
| Chain | `--raw-tx` Format |
|
|
312
|
+
|-------|-------------------|
|
|
313
|
+
| EVM (Ethereum, Polygon, etc.) | Hex-encoded signed tx |
|
|
314
|
+
| UTXO (Bitcoin, Litecoin, etc.) | Hex-encoded raw tx |
|
|
315
|
+
| Solana | Base64-encoded tx bytes |
|
|
316
|
+
| Sui | JSON: `{"unsignedTx":"...","signature":"..."}` |
|
|
317
|
+
| Cosmos | JSON: `{"tx_bytes":"..."}` or base64 |
|
|
318
|
+
| TON | Base64 BOC |
|
|
319
|
+
| Polkadot | Hex-encoded extrinsic |
|
|
320
|
+
| Ripple | Hex-encoded tx blob |
|
|
321
|
+
| Tron | JSON tx object |
|
|
322
|
+
|
|
323
|
+
#### Example: Custom EVM Transaction
|
|
324
|
+
|
|
325
|
+
Build and sign a transaction with ethers.js, broadcast with CLI:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
# 1. Build transaction externally (save as build-evm-tx.js)
|
|
329
|
+
cat > build-evm-tx.js << 'EOF'
|
|
330
|
+
const { keccak256, Transaction, parseEther } = require('ethers');
|
|
331
|
+
const tx = Transaction.from({
|
|
332
|
+
to: '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0',
|
|
333
|
+
value: parseEther('0.01'),
|
|
334
|
+
gasLimit: 21000n,
|
|
335
|
+
maxFeePerGas: 50000000000n,
|
|
336
|
+
maxPriorityFeePerGas: 2000000000n,
|
|
337
|
+
nonce: 0,
|
|
338
|
+
chainId: 1,
|
|
339
|
+
type: 2
|
|
340
|
+
});
|
|
341
|
+
const hash = keccak256(tx.unsignedSerialized);
|
|
342
|
+
console.log('HASH:', Buffer.from(hash.slice(2), 'hex').toString('base64'));
|
|
343
|
+
console.log('UNSIGNED:', tx.unsignedSerialized);
|
|
344
|
+
EOF
|
|
345
|
+
node build-evm-tx.js
|
|
346
|
+
|
|
347
|
+
# 2. Sign the hash with Vultisig
|
|
348
|
+
vultisig sign --chain ethereum --bytes "<base64-hash-from-step-1>" -o json > sig.json
|
|
349
|
+
|
|
350
|
+
# 3. Assemble signed transaction (use r,s,v from sig.json)
|
|
351
|
+
# The signature field contains r||s (64 bytes hex), recovery is v
|
|
352
|
+
|
|
353
|
+
# 4. Broadcast the assembled signed transaction
|
|
354
|
+
vultisig broadcast --chain ethereum --raw-tx "0x02f8..."
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
#### Example: Custom Bitcoin Transaction
|
|
358
|
+
|
|
359
|
+
Build a PSBT with bitcoinjs-lib, sign with CLI:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
# 1. Build PSBT and get sighash (save as build-btc-tx.js)
|
|
363
|
+
cat > build-btc-tx.js << 'EOF'
|
|
364
|
+
const bitcoin = require('bitcoinjs-lib');
|
|
365
|
+
const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin });
|
|
366
|
+
// Add your inputs and outputs
|
|
367
|
+
psbt.addInput({
|
|
368
|
+
hash: '<previous-txid>',
|
|
369
|
+
index: 0,
|
|
370
|
+
witnessUtxo: { script: Buffer.from('...'), value: 100000 }
|
|
371
|
+
});
|
|
372
|
+
psbt.addOutput({ address: 'bc1q...', value: 90000 });
|
|
373
|
+
// Get sighash for signing
|
|
374
|
+
const sighash = psbt.getTxForSigning().hashForWitnessV0(0, scriptCode, 100000, 0x01);
|
|
375
|
+
console.log('SIGHASH:', sighash.toString('base64'));
|
|
376
|
+
EOF
|
|
377
|
+
node build-btc-tx.js
|
|
378
|
+
|
|
379
|
+
# 2. Sign with Vultisig
|
|
380
|
+
vultisig sign --chain bitcoin --bytes "<base64-sighash>" -o json > sig.json
|
|
381
|
+
|
|
382
|
+
# 3. Apply signature to PSBT and finalize (use signature from sig.json)
|
|
383
|
+
|
|
384
|
+
# 4. Broadcast
|
|
385
|
+
vultisig broadcast --chain bitcoin --raw-tx "0200000001..."
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
#### Example: Custom Solana Transaction
|
|
389
|
+
|
|
390
|
+
Build with @solana/web3.js, sign with CLI:
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
# 1. Build transaction (save as build-sol-tx.js)
|
|
394
|
+
cat > build-sol-tx.js << 'EOF'
|
|
395
|
+
const { Transaction, SystemProgram, PublicKey, Connection } = require('@solana/web3.js');
|
|
396
|
+
const connection = new Connection('https://api.mainnet-beta.solana.com');
|
|
397
|
+
const fromPubkey = new PublicKey('<your-pubkey>');
|
|
398
|
+
const toPubkey = new PublicKey('<recipient-pubkey>');
|
|
399
|
+
|
|
400
|
+
const tx = new Transaction().add(
|
|
401
|
+
SystemProgram.transfer({ fromPubkey, toPubkey, lamports: 1000000 })
|
|
402
|
+
);
|
|
403
|
+
tx.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
|
|
404
|
+
tx.feePayer = fromPubkey;
|
|
405
|
+
|
|
406
|
+
const message = tx.serializeMessage();
|
|
407
|
+
console.log('MESSAGE:', message.toString('base64'));
|
|
408
|
+
EOF
|
|
409
|
+
node build-sol-tx.js
|
|
410
|
+
|
|
411
|
+
# 2. Sign the message with Vultisig (EdDSA)
|
|
412
|
+
vultisig sign --chain solana --bytes "<base64-message>" -o json > sig.json
|
|
413
|
+
|
|
414
|
+
# 3. Assemble signed transaction (attach signature to message)
|
|
415
|
+
|
|
416
|
+
# 4. Broadcast (base64 encoded signed transaction)
|
|
417
|
+
vultisig broadcast --chain solana --raw-tx "<base64-signed-tx>"
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
#### Example: Custom Sui Transaction
|
|
421
|
+
|
|
422
|
+
Build with @mysten/sui, sign with CLI:
|
|
423
|
+
|
|
424
|
+
```bash
|
|
425
|
+
# 1. Build transaction (save as build-sui-tx.js)
|
|
426
|
+
cat > build-sui-tx.js << 'EOF'
|
|
427
|
+
const { SuiClient, getFullnodeUrl } = require('@mysten/sui/client');
|
|
428
|
+
const { Transaction } = require('@mysten/sui/transactions');
|
|
429
|
+
|
|
430
|
+
const client = new SuiClient({ url: getFullnodeUrl('mainnet') });
|
|
431
|
+
const tx = new Transaction();
|
|
432
|
+
tx.transferObjects([tx.gas], '<recipient-address>');
|
|
433
|
+
const bytes = await tx.build({ client });
|
|
434
|
+
console.log('TX_BYTES:', Buffer.from(bytes).toString('base64'));
|
|
435
|
+
EOF
|
|
436
|
+
node build-sui-tx.js
|
|
437
|
+
|
|
438
|
+
# 2. Sign the transaction bytes with Vultisig (EdDSA)
|
|
439
|
+
vultisig sign --chain sui --bytes "<base64-tx-bytes>" -o json > sig.json
|
|
440
|
+
|
|
441
|
+
# 3. Broadcast (requires JSON with both unsigned tx and signature)
|
|
442
|
+
vultisig broadcast --chain sui --raw-tx '{"unsignedTx":"<base64-tx-bytes>","signature":"<base64-signature-from-sig.json>"}'
|
|
443
|
+
```
|
|
444
|
+
|
|
224
445
|
### Settings
|
|
225
446
|
|
|
226
447
|
| Command | Description |
|
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,7 +1103,7 @@ 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
1109
|
|
|
@@ -1314,6 +1314,15 @@ function createSpinner(text) {
|
|
|
1314
1314
|
}
|
|
1315
1315
|
|
|
1316
1316
|
// src/core/password-manager.ts
|
|
1317
|
+
var passwordCache = /* @__PURE__ */ new Map();
|
|
1318
|
+
function cachePassword(vaultIdOrName, password) {
|
|
1319
|
+
passwordCache.set(vaultIdOrName, password);
|
|
1320
|
+
}
|
|
1321
|
+
function getCachedPassword(vaultId, vaultName) {
|
|
1322
|
+
if (vaultName && passwordCache.has(vaultName)) return passwordCache.get(vaultName);
|
|
1323
|
+
if (passwordCache.has(vaultId)) return passwordCache.get(vaultId);
|
|
1324
|
+
return null;
|
|
1325
|
+
}
|
|
1317
1326
|
function parseVaultPasswords() {
|
|
1318
1327
|
const passwordMap = /* @__PURE__ */ new Map();
|
|
1319
1328
|
const passwordsEnv = process.env.VAULT_PASSWORDS;
|
|
@@ -1356,14 +1365,23 @@ async function promptForPassword(vaultName, vaultId) {
|
|
|
1356
1365
|
return password;
|
|
1357
1366
|
}
|
|
1358
1367
|
async function getPassword(vaultId, vaultName) {
|
|
1368
|
+
const cachedPassword = getCachedPassword(vaultId, vaultName);
|
|
1369
|
+
if (cachedPassword) {
|
|
1370
|
+
return cachedPassword;
|
|
1371
|
+
}
|
|
1359
1372
|
const envPassword = getPasswordFromEnv(vaultId, vaultName);
|
|
1360
1373
|
if (envPassword) {
|
|
1374
|
+
cachePassword(vaultId, envPassword);
|
|
1375
|
+
if (vaultName) cachePassword(vaultName, envPassword);
|
|
1361
1376
|
return envPassword;
|
|
1362
1377
|
}
|
|
1363
1378
|
if (isSilent() || isJsonOutput()) {
|
|
1364
1379
|
throw new Error("Password required but not provided. Set VAULT_PASSWORD or VAULT_PASSWORDS environment variable.");
|
|
1365
1380
|
}
|
|
1366
|
-
|
|
1381
|
+
const password = await promptForPassword(vaultName, vaultId);
|
|
1382
|
+
cachePassword(vaultId, password);
|
|
1383
|
+
if (vaultName) cachePassword(vaultName, password);
|
|
1384
|
+
return password;
|
|
1367
1385
|
}
|
|
1368
1386
|
function createPasswordCallback() {
|
|
1369
1387
|
return async (vaultId, vaultName) => {
|
|
@@ -1992,11 +2010,124 @@ Or use this URL: ${qrPayload}
|
|
|
1992
2010
|
}
|
|
1993
2011
|
}
|
|
1994
2012
|
|
|
1995
|
-
// src/commands/
|
|
2013
|
+
// src/commands/sign.ts
|
|
1996
2014
|
var import_qrcode_terminal2 = __toESM(require_main(), 1);
|
|
2015
|
+
import { Chain as Chain2 } from "@vultisig/sdk";
|
|
2016
|
+
async function executeSignBytes(ctx2, params) {
|
|
2017
|
+
const vault = await ctx2.ensureActiveVault();
|
|
2018
|
+
if (!Object.values(Chain2).includes(params.chain)) {
|
|
2019
|
+
throw new Error(`Invalid chain: ${params.chain}`);
|
|
2020
|
+
}
|
|
2021
|
+
return signBytes(vault, params);
|
|
2022
|
+
}
|
|
2023
|
+
async function signBytes(vault, params) {
|
|
2024
|
+
const hashBytes = Buffer.from(params.bytes, "base64");
|
|
2025
|
+
await ensureVaultUnlocked(vault, params.password);
|
|
2026
|
+
const isSecureVault = vault.type === "secure";
|
|
2027
|
+
const signSpinner = createSpinner(isSecureVault ? "Preparing secure signing session..." : "Signing bytes...");
|
|
2028
|
+
vault.on("signingProgress", ({ step }) => {
|
|
2029
|
+
signSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2030
|
+
});
|
|
2031
|
+
if (isSecureVault) {
|
|
2032
|
+
vault.on("qrCodeReady", ({ qrPayload }) => {
|
|
2033
|
+
if (isJsonOutput()) {
|
|
2034
|
+
printResult(JSON.stringify({ qrPayload }));
|
|
2035
|
+
} else if (isSilent()) {
|
|
2036
|
+
printResult(`QR Payload: ${qrPayload}`);
|
|
2037
|
+
} else {
|
|
2038
|
+
signSpinner.stop();
|
|
2039
|
+
info("\nScan this QR code with your Vultisig mobile app to sign:");
|
|
2040
|
+
import_qrcode_terminal2.default.generate(qrPayload, { small: true });
|
|
2041
|
+
info(`
|
|
2042
|
+
Or use this URL: ${qrPayload}
|
|
2043
|
+
`);
|
|
2044
|
+
signSpinner.start("Waiting for devices to join signing session...");
|
|
2045
|
+
}
|
|
2046
|
+
});
|
|
2047
|
+
vault.on(
|
|
2048
|
+
"deviceJoined",
|
|
2049
|
+
({ deviceId, totalJoined, required }) => {
|
|
2050
|
+
if (!isSilent()) {
|
|
2051
|
+
signSpinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
|
|
2052
|
+
} else if (!isJsonOutput()) {
|
|
2053
|
+
printResult(`Device joined: ${totalJoined}/${required}`);
|
|
2054
|
+
}
|
|
2055
|
+
}
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
try {
|
|
2059
|
+
const signature = await vault.signBytes(
|
|
2060
|
+
{
|
|
2061
|
+
data: hashBytes,
|
|
2062
|
+
chain: params.chain
|
|
2063
|
+
},
|
|
2064
|
+
{ signal: params.signal }
|
|
2065
|
+
);
|
|
2066
|
+
signSpinner.succeed("Bytes signed");
|
|
2067
|
+
const sigHex = signature.signature.startsWith("0x") ? signature.signature.slice(2) : signature.signature;
|
|
2068
|
+
const sigBase64 = Buffer.from(sigHex, "hex").toString("base64");
|
|
2069
|
+
const result = {
|
|
2070
|
+
signature: sigBase64,
|
|
2071
|
+
recovery: signature.recovery,
|
|
2072
|
+
format: signature.format
|
|
2073
|
+
};
|
|
2074
|
+
if (isJsonOutput()) {
|
|
2075
|
+
outputJson(result);
|
|
2076
|
+
} else {
|
|
2077
|
+
printResult(`Signature: ${result.signature}`);
|
|
2078
|
+
if (result.recovery !== void 0) {
|
|
2079
|
+
printResult(`Recovery: ${result.recovery}`);
|
|
2080
|
+
}
|
|
2081
|
+
printResult(`Format: ${result.format}`);
|
|
2082
|
+
}
|
|
2083
|
+
return result;
|
|
2084
|
+
} finally {
|
|
2085
|
+
vault.removeAllListeners("signingProgress");
|
|
2086
|
+
if (isSecureVault) {
|
|
2087
|
+
vault.removeAllListeners("qrCodeReady");
|
|
2088
|
+
vault.removeAllListeners("deviceJoined");
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
// src/commands/broadcast.ts
|
|
2094
|
+
import { Chain as Chain3, Vultisig as Vultisig3 } from "@vultisig/sdk";
|
|
2095
|
+
async function executeBroadcast(ctx2, params) {
|
|
2096
|
+
const vault = await ctx2.ensureActiveVault();
|
|
2097
|
+
if (!Object.values(Chain3).includes(params.chain)) {
|
|
2098
|
+
throw new Error(`Invalid chain: ${params.chain}`);
|
|
2099
|
+
}
|
|
2100
|
+
const broadcastSpinner = createSpinner("Broadcasting transaction...");
|
|
2101
|
+
try {
|
|
2102
|
+
const txHash = await vault.broadcastRawTx({
|
|
2103
|
+
chain: params.chain,
|
|
2104
|
+
rawTx: params.rawTx
|
|
2105
|
+
});
|
|
2106
|
+
broadcastSpinner.succeed(`Transaction broadcast: ${txHash}`);
|
|
2107
|
+
const result = {
|
|
2108
|
+
txHash,
|
|
2109
|
+
chain: params.chain,
|
|
2110
|
+
explorerUrl: Vultisig3.getTxExplorerUrl(params.chain, txHash)
|
|
2111
|
+
};
|
|
2112
|
+
if (isJsonOutput()) {
|
|
2113
|
+
outputJson(result);
|
|
2114
|
+
} else {
|
|
2115
|
+
printResult(`TX Hash: ${result.txHash}`);
|
|
2116
|
+
printResult(`Explorer: ${result.explorerUrl}`);
|
|
2117
|
+
}
|
|
2118
|
+
return result;
|
|
2119
|
+
} catch (error2) {
|
|
2120
|
+
broadcastSpinner.fail("Broadcast failed");
|
|
2121
|
+
throw error2;
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
// src/commands/vault-management.ts
|
|
2126
|
+
var import_qrcode_terminal3 = __toESM(require_main(), 1);
|
|
1997
2127
|
import chalk5 from "chalk";
|
|
1998
2128
|
import { promises as fs } from "fs";
|
|
1999
2129
|
import inquirer4 from "inquirer";
|
|
2130
|
+
import path from "path";
|
|
2000
2131
|
function withAbortSignal(promise, signal) {
|
|
2001
2132
|
if (!signal) return promise;
|
|
2002
2133
|
return Promise.race([
|
|
@@ -2113,7 +2244,7 @@ async function executeCreateSecure(ctx2, options) {
|
|
|
2113
2244
|
} else {
|
|
2114
2245
|
spinner.stop();
|
|
2115
2246
|
info("\nScan this QR code with your Vultisig mobile app:");
|
|
2116
|
-
|
|
2247
|
+
import_qrcode_terminal3.default.generate(qrPayload, { small: true });
|
|
2117
2248
|
info(`
|
|
2118
2249
|
Or use this URL: ${qrPayload}
|
|
2119
2250
|
`);
|
|
@@ -2218,38 +2349,47 @@ async function executeVerify(ctx2, vaultId, options = {}) {
|
|
|
2218
2349
|
}
|
|
2219
2350
|
async function executeExport(ctx2, options = {}) {
|
|
2220
2351
|
const vault = await ctx2.ensureActiveVault();
|
|
2221
|
-
let
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2352
|
+
let exportPassword = options.exportPassword;
|
|
2353
|
+
if (exportPassword === void 0) {
|
|
2354
|
+
if (options.password !== void 0) {
|
|
2355
|
+
exportPassword = options.password;
|
|
2356
|
+
} else {
|
|
2357
|
+
const answer = await inquirer4.prompt([
|
|
2358
|
+
{
|
|
2359
|
+
type: "password",
|
|
2360
|
+
name: "exportPassword",
|
|
2361
|
+
message: "Enter password for export encryption (leave empty for no encryption):",
|
|
2362
|
+
mask: "*"
|
|
2363
|
+
}
|
|
2364
|
+
]);
|
|
2365
|
+
exportPassword = answer.exportPassword || void 0;
|
|
2366
|
+
}
|
|
2233
2367
|
}
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2368
|
+
const spinner = createSpinner("Exporting vault...");
|
|
2369
|
+
const { data: vultContent, filename: sdkFilename } = await vault.export(exportPassword);
|
|
2370
|
+
let outputPath;
|
|
2371
|
+
if (options.outputPath) {
|
|
2372
|
+
const resolvedPath = path.resolve(options.outputPath);
|
|
2373
|
+
try {
|
|
2374
|
+
const stat = await fs.stat(resolvedPath);
|
|
2375
|
+
if (stat.isDirectory()) {
|
|
2376
|
+
outputPath = path.join(resolvedPath, sdkFilename);
|
|
2377
|
+
} else {
|
|
2378
|
+
outputPath = resolvedPath;
|
|
2241
2379
|
}
|
|
2242
|
-
|
|
2243
|
-
|
|
2380
|
+
} catch {
|
|
2381
|
+
outputPath = resolvedPath;
|
|
2382
|
+
}
|
|
2383
|
+
} else {
|
|
2384
|
+
outputPath = path.resolve(sdkFilename);
|
|
2244
2385
|
}
|
|
2245
|
-
const
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
spinner.succeed(`Vault exported: ${fileName}`);
|
|
2386
|
+
const parentDir = path.dirname(outputPath);
|
|
2387
|
+
await fs.mkdir(parentDir, { recursive: true });
|
|
2388
|
+
await fs.writeFile(outputPath, vultContent, "utf-8");
|
|
2389
|
+
spinner.succeed(`Vault exported: ${outputPath}`);
|
|
2250
2390
|
success("\n+ Vault exported successfully!");
|
|
2251
|
-
info(`File: ${
|
|
2252
|
-
return
|
|
2391
|
+
info(`File: ${outputPath}`);
|
|
2392
|
+
return outputPath;
|
|
2253
2393
|
}
|
|
2254
2394
|
async function executeVaults(ctx2) {
|
|
2255
2395
|
const spinner = createSpinner("Loading vaults...");
|
|
@@ -2497,7 +2637,7 @@ async function executeSwap(ctx2, options) {
|
|
|
2497
2637
|
}
|
|
2498
2638
|
|
|
2499
2639
|
// src/commands/settings.ts
|
|
2500
|
-
import { Chain as
|
|
2640
|
+
import { Chain as Chain4, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
|
|
2501
2641
|
import chalk6 from "chalk";
|
|
2502
2642
|
import inquirer5 from "inquirer";
|
|
2503
2643
|
async function executeCurrency(ctx2, newCurrency) {
|
|
@@ -2565,7 +2705,7 @@ async function executeAddressBook(ctx2, options = {}) {
|
|
|
2565
2705
|
type: "list",
|
|
2566
2706
|
name: "chain",
|
|
2567
2707
|
message: "Select chain:",
|
|
2568
|
-
choices: Object.values(
|
|
2708
|
+
choices: Object.values(Chain4)
|
|
2569
2709
|
});
|
|
2570
2710
|
}
|
|
2571
2711
|
if (!address) {
|
|
@@ -2642,9 +2782,9 @@ Address Book${options.chain ? ` (${options.chain})` : ""}:
|
|
|
2642
2782
|
}
|
|
2643
2783
|
|
|
2644
2784
|
// src/interactive/completer.ts
|
|
2645
|
-
import { Chain as
|
|
2785
|
+
import { Chain as Chain5 } from "@vultisig/sdk";
|
|
2646
2786
|
import fs2 from "fs";
|
|
2647
|
-
import
|
|
2787
|
+
import path2 from "path";
|
|
2648
2788
|
var COMMANDS = [
|
|
2649
2789
|
// Vault management
|
|
2650
2790
|
"vaults",
|
|
@@ -2718,28 +2858,28 @@ function createCompleter(ctx2) {
|
|
|
2718
2858
|
}
|
|
2719
2859
|
function completeFilePath(partial, filterVult) {
|
|
2720
2860
|
try {
|
|
2721
|
-
const endsWithSeparator = partial.endsWith("/") || partial.endsWith(
|
|
2861
|
+
const endsWithSeparator = partial.endsWith("/") || partial.endsWith(path2.sep);
|
|
2722
2862
|
let dir;
|
|
2723
2863
|
let basename;
|
|
2724
2864
|
if (endsWithSeparator) {
|
|
2725
2865
|
dir = partial;
|
|
2726
2866
|
basename = "";
|
|
2727
2867
|
} else {
|
|
2728
|
-
dir =
|
|
2729
|
-
basename =
|
|
2868
|
+
dir = path2.dirname(partial);
|
|
2869
|
+
basename = path2.basename(partial);
|
|
2730
2870
|
if (fs2.existsSync(partial) && fs2.statSync(partial).isDirectory()) {
|
|
2731
2871
|
dir = partial;
|
|
2732
2872
|
basename = "";
|
|
2733
2873
|
}
|
|
2734
2874
|
}
|
|
2735
|
-
const resolvedDir =
|
|
2875
|
+
const resolvedDir = path2.resolve(dir);
|
|
2736
2876
|
if (!fs2.existsSync(resolvedDir) || !fs2.statSync(resolvedDir).isDirectory()) {
|
|
2737
2877
|
return [[], partial];
|
|
2738
2878
|
}
|
|
2739
2879
|
const files = fs2.readdirSync(resolvedDir);
|
|
2740
2880
|
const matches = files.filter((file) => file.startsWith(basename)).map((file) => {
|
|
2741
|
-
const fullPath =
|
|
2742
|
-
const stats = fs2.statSync(
|
|
2881
|
+
const fullPath = path2.join(dir, file);
|
|
2882
|
+
const stats = fs2.statSync(path2.join(resolvedDir, file));
|
|
2743
2883
|
if (stats.isDirectory()) {
|
|
2744
2884
|
return fullPath + "/";
|
|
2745
2885
|
}
|
|
@@ -2765,7 +2905,7 @@ function completeVaultName(ctx2, partial) {
|
|
|
2765
2905
|
return [show, partial];
|
|
2766
2906
|
}
|
|
2767
2907
|
function completeChainName(partial) {
|
|
2768
|
-
const allChains = Object.values(
|
|
2908
|
+
const allChains = Object.values(Chain5);
|
|
2769
2909
|
const partialLower = partial.toLowerCase();
|
|
2770
2910
|
const matches = allChains.filter((chain) => chain.toLowerCase().startsWith(partialLower));
|
|
2771
2911
|
matches.sort();
|
|
@@ -2773,7 +2913,7 @@ function completeChainName(partial) {
|
|
|
2773
2913
|
return [show, partial];
|
|
2774
2914
|
}
|
|
2775
2915
|
function findChainByName(name) {
|
|
2776
|
-
const allChains = Object.values(
|
|
2916
|
+
const allChains = Object.values(Chain5);
|
|
2777
2917
|
const nameLower = name.toLowerCase();
|
|
2778
2918
|
const found = allChains.find((chain) => chain.toLowerCase() === nameLower);
|
|
2779
2919
|
return found ? found : null;
|
|
@@ -3900,7 +4040,7 @@ var cachedVersion = null;
|
|
|
3900
4040
|
function getVersion() {
|
|
3901
4041
|
if (cachedVersion) return cachedVersion;
|
|
3902
4042
|
if (true) {
|
|
3903
|
-
cachedVersion = "0.2.0-
|
|
4043
|
+
cachedVersion = "0.2.0-beta.9";
|
|
3904
4044
|
return cachedVersion;
|
|
3905
4045
|
}
|
|
3906
4046
|
try {
|
|
@@ -4368,14 +4508,17 @@ async function findVaultByNameOrId(sdk, nameOrId) {
|
|
|
4368
4508
|
if (byPartialId) return byPartialId;
|
|
4369
4509
|
return null;
|
|
4370
4510
|
}
|
|
4371
|
-
async function init(vaultOverride) {
|
|
4511
|
+
async function init(vaultOverride, unlockPassword) {
|
|
4372
4512
|
if (!ctx) {
|
|
4373
|
-
const
|
|
4513
|
+
const vaultSelector = vaultOverride || process.env.VULTISIG_VAULT;
|
|
4514
|
+
if (unlockPassword && vaultSelector) {
|
|
4515
|
+
cachePassword(vaultSelector, unlockPassword);
|
|
4516
|
+
}
|
|
4517
|
+
const sdk = new Vultisig4({
|
|
4374
4518
|
onPasswordRequired: createPasswordCallback()
|
|
4375
4519
|
});
|
|
4376
4520
|
await sdk.initialize();
|
|
4377
4521
|
ctx = new CLIContext(sdk);
|
|
4378
|
-
const vaultSelector = vaultOverride || process.env.VULTISIG_VAULT;
|
|
4379
4522
|
let vault = null;
|
|
4380
4523
|
if (vaultSelector) {
|
|
4381
4524
|
vault = await findVaultByNameOrId(sdk, vaultSelector);
|
|
@@ -4460,6 +4603,25 @@ program.command("send <chain> <to> <amount>").description("Send tokens to an add
|
|
|
4460
4603
|
}
|
|
4461
4604
|
)
|
|
4462
4605
|
);
|
|
4606
|
+
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(
|
|
4607
|
+
withExit(async (options) => {
|
|
4608
|
+
const context = await init(program.opts().vault, options.password);
|
|
4609
|
+
await executeSignBytes(context, {
|
|
4610
|
+
chain: findChainByName(options.chain) || options.chain,
|
|
4611
|
+
bytes: options.bytes,
|
|
4612
|
+
password: options.password
|
|
4613
|
+
});
|
|
4614
|
+
})
|
|
4615
|
+
);
|
|
4616
|
+
program.command("broadcast").description("Broadcast a pre-signed raw transaction").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--raw-tx <hex>", "Hex-encoded signed transaction").action(
|
|
4617
|
+
withExit(async (options) => {
|
|
4618
|
+
const context = await init(program.opts().vault);
|
|
4619
|
+
await executeBroadcast(context, {
|
|
4620
|
+
chain: findChainByName(options.chain) || options.chain,
|
|
4621
|
+
rawTx: options.rawTx
|
|
4622
|
+
});
|
|
4623
|
+
})
|
|
4624
|
+
);
|
|
4463
4625
|
program.command("portfolio").description("Show total portfolio value").option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").action(
|
|
4464
4626
|
withExit(async (options) => {
|
|
4465
4627
|
const context = await init(program.opts().vault);
|
|
@@ -4478,13 +4640,13 @@ program.command("server").description("Check server connectivity and status").ac
|
|
|
4478
4640
|
await executeServer(context);
|
|
4479
4641
|
})
|
|
4480
4642
|
);
|
|
4481
|
-
program.command("export [path]").description("Export vault to file").option("--
|
|
4482
|
-
withExit(async (
|
|
4483
|
-
const context = await init(program.opts().vault);
|
|
4643
|
+
program.command("export [path]").description("Export vault to file").option("--password <password>", "Password to unlock the vault (for encrypted vaults)").option("--exportPassword <password>", "Password to encrypt the exported file (defaults to --password)").action(
|
|
4644
|
+
withExit(async (path3, options) => {
|
|
4645
|
+
const context = await init(program.opts().vault, options.password);
|
|
4484
4646
|
await executeExport(context, {
|
|
4485
|
-
outputPath:
|
|
4486
|
-
|
|
4487
|
-
|
|
4647
|
+
outputPath: path3,
|
|
4648
|
+
password: options.password,
|
|
4649
|
+
exportPassword: options.exportPassword
|
|
4488
4650
|
});
|
|
4489
4651
|
})
|
|
4490
4652
|
);
|
|
@@ -4636,7 +4798,7 @@ program.command("update").description("Check for updates and show update command
|
|
|
4636
4798
|
);
|
|
4637
4799
|
setupCompletionCommand(program);
|
|
4638
4800
|
async function startInteractiveMode() {
|
|
4639
|
-
const sdk = new
|
|
4801
|
+
const sdk = new Vultisig4({
|
|
4640
4802
|
onPasswordRequired: createPasswordCallback()
|
|
4641
4803
|
});
|
|
4642
4804
|
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-beta.9",
|
|
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-beta.9",
|
|
52
52
|
"chalk": "^5.3.0",
|
|
53
53
|
"cli-table3": "^0.6.5",
|
|
54
54
|
"commander": "^12.0.0",
|