@vultisig/cli 0.2.0 → 0.4.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 +110 -0
- package/README.md +112 -29
- package/dist/index.js +543 -168
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1103,9 +1103,10 @@ var require_main = __commonJS({
|
|
|
1103
1103
|
|
|
1104
1104
|
// src/index.ts
|
|
1105
1105
|
import "dotenv/config";
|
|
1106
|
-
import { Vultisig as Vultisig4 } from "@vultisig/sdk";
|
|
1107
|
-
import
|
|
1106
|
+
import { parseKeygenQR, Vultisig as Vultisig4 } from "@vultisig/sdk";
|
|
1107
|
+
import chalk13 from "chalk";
|
|
1108
1108
|
import { program } from "commander";
|
|
1109
|
+
import { promises as fs3 } from "fs";
|
|
1109
1110
|
import inquirer8 from "inquirer";
|
|
1110
1111
|
|
|
1111
1112
|
// src/core/command-context.ts
|
|
@@ -1445,25 +1446,26 @@ import { fiatCurrencies, fiatCurrencyNameRecord as fiatCurrencyNameRecord2 } fro
|
|
|
1445
1446
|
import { fiatCurrencyNameRecord, Vultisig } from "@vultisig/sdk";
|
|
1446
1447
|
import chalk2 from "chalk";
|
|
1447
1448
|
import inquirer2 from "inquirer";
|
|
1448
|
-
function displayBalance(chain, balance) {
|
|
1449
|
+
function displayBalance(chain, balance, raw = false) {
|
|
1449
1450
|
printResult(chalk2.cyan(`
|
|
1450
1451
|
${chain} Balance:`));
|
|
1451
|
-
|
|
1452
|
+
const displayAmount = raw ? balance.amount : formatBalanceAmount(balance.amount, balance.decimals);
|
|
1453
|
+
printResult(` Amount: ${displayAmount} ${balance.symbol}`);
|
|
1452
1454
|
if (balance.fiatValue && balance.fiatCurrency) {
|
|
1453
1455
|
printResult(` Value: ${balance.fiatValue.toFixed(2)} ${balance.fiatCurrency}`);
|
|
1454
1456
|
}
|
|
1455
1457
|
}
|
|
1456
|
-
function displayBalancesTable(balances) {
|
|
1458
|
+
function displayBalancesTable(balances, raw = false) {
|
|
1457
1459
|
printResult(chalk2.cyan("\nPortfolio Balances:\n"));
|
|
1458
1460
|
const tableData = Object.entries(balances).map(([chain, balance]) => ({
|
|
1459
1461
|
Chain: chain,
|
|
1460
|
-
Amount: balance.amount,
|
|
1462
|
+
Amount: raw ? balance.amount : formatBalanceAmount(balance.amount, balance.decimals),
|
|
1461
1463
|
Symbol: balance.symbol,
|
|
1462
1464
|
Value: balance.fiatValue && balance.fiatCurrency ? `${balance.fiatValue.toFixed(2)} ${balance.fiatCurrency}` : "N/A"
|
|
1463
1465
|
}));
|
|
1464
1466
|
printTable(tableData);
|
|
1465
1467
|
}
|
|
1466
|
-
function displayPortfolio(portfolio, currency) {
|
|
1468
|
+
function displayPortfolio(portfolio, currency, raw = false) {
|
|
1467
1469
|
const currencyName = fiatCurrencyNameRecord[currency];
|
|
1468
1470
|
printResult(chalk2.cyan("\n+----------------------------------------+"));
|
|
1469
1471
|
printResult(chalk2.cyan(`| Portfolio Total Value (${currencyName}) |`));
|
|
@@ -1474,7 +1476,7 @@ function displayPortfolio(portfolio, currency) {
|
|
|
1474
1476
|
printResult(chalk2.bold("Chain Breakdown:\n"));
|
|
1475
1477
|
const table = portfolio.chainBalances.map(({ chain, balance, value }) => ({
|
|
1476
1478
|
Chain: chain,
|
|
1477
|
-
Amount: balance.amount,
|
|
1479
|
+
Amount: raw ? balance.amount : formatBalanceAmount(balance.amount, balance.decimals),
|
|
1478
1480
|
Symbol: balance.symbol,
|
|
1479
1481
|
Value: value ? `${value.amount} ${value.currency.toUpperCase()}` : "N/A"
|
|
1480
1482
|
}));
|
|
@@ -1610,6 +1612,14 @@ function formatBigintAmount(amount, decimals) {
|
|
|
1610
1612
|
const trimmed = fractionStr.replace(/0+$/, "");
|
|
1611
1613
|
return `${whole}.${trimmed}`;
|
|
1612
1614
|
}
|
|
1615
|
+
function formatBalanceAmount(amount, decimals) {
|
|
1616
|
+
if (!amount || amount === "0") return "0";
|
|
1617
|
+
try {
|
|
1618
|
+
return formatBigintAmount(BigInt(amount), decimals);
|
|
1619
|
+
} catch {
|
|
1620
|
+
return amount;
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1613
1623
|
function displaySwapPreview(quote, fromAmount, fromSymbol, toSymbol, options) {
|
|
1614
1624
|
const estimatedOutputFormatted = formatBigintAmount(quote.estimatedOutput, options.toDecimals);
|
|
1615
1625
|
printResult(chalk2.cyan("\nSwap Preview:"));
|
|
@@ -1633,6 +1643,10 @@ function displaySwapPreview(quote, fromAmount, fromSymbol, toSymbol, options) {
|
|
|
1633
1643
|
if (quote.feesFiat?.affiliate) {
|
|
1634
1644
|
printResult(` (~$${quote.feesFiat.affiliate.toFixed(2)})`);
|
|
1635
1645
|
}
|
|
1646
|
+
if (options.discountTier) {
|
|
1647
|
+
const tierDisplay = options.discountTier.charAt(0).toUpperCase() + options.discountTier.slice(1);
|
|
1648
|
+
printResult(chalk2.green(` (${tierDisplay} tier discount applied)`));
|
|
1649
|
+
}
|
|
1636
1650
|
}
|
|
1637
1651
|
printResult(` Total: ${totalFeeFormatted} ${options.feeSymbol}`);
|
|
1638
1652
|
if (quote.feesFiat) {
|
|
@@ -1687,6 +1701,7 @@ async function confirmSwap() {
|
|
|
1687
1701
|
async function executeBalance(ctx2, options = {}) {
|
|
1688
1702
|
const vault = await ctx2.ensureActiveVault();
|
|
1689
1703
|
const spinner = createSpinner("Loading balances...");
|
|
1704
|
+
const raw = options.raw ?? false;
|
|
1690
1705
|
if (options.chain) {
|
|
1691
1706
|
const balance = await vault.balance(options.chain);
|
|
1692
1707
|
spinner.succeed("Balance loaded");
|
|
@@ -1694,7 +1709,7 @@ async function executeBalance(ctx2, options = {}) {
|
|
|
1694
1709
|
outputJson({ chain: options.chain, balance });
|
|
1695
1710
|
return;
|
|
1696
1711
|
}
|
|
1697
|
-
displayBalance(options.chain, balance);
|
|
1712
|
+
displayBalance(options.chain, balance, raw);
|
|
1698
1713
|
} else {
|
|
1699
1714
|
const balances = await vault.balances(void 0, options.includeTokens);
|
|
1700
1715
|
spinner.succeed("Balances loaded");
|
|
@@ -1702,7 +1717,7 @@ async function executeBalance(ctx2, options = {}) {
|
|
|
1702
1717
|
outputJson({ balances });
|
|
1703
1718
|
return;
|
|
1704
1719
|
}
|
|
1705
|
-
displayBalancesTable(balances);
|
|
1720
|
+
displayBalancesTable(balances, raw);
|
|
1706
1721
|
}
|
|
1707
1722
|
}
|
|
1708
1723
|
async function executePortfolio(ctx2, options = {}) {
|
|
@@ -1737,13 +1752,27 @@ async function executePortfolio(ctx2, options = {}) {
|
|
|
1737
1752
|
outputJson({ portfolio, currency });
|
|
1738
1753
|
return;
|
|
1739
1754
|
}
|
|
1740
|
-
displayPortfolio(portfolio, currency);
|
|
1755
|
+
displayPortfolio(portfolio, currency, options.raw ?? false);
|
|
1741
1756
|
}
|
|
1742
1757
|
|
|
1743
1758
|
// src/commands/chains.ts
|
|
1759
|
+
import { SUPPORTED_CHAINS } from "@vultisig/sdk";
|
|
1744
1760
|
import chalk3 from "chalk";
|
|
1745
1761
|
async function executeChains(ctx2, options = {}) {
|
|
1746
1762
|
const vault = await ctx2.ensureActiveVault();
|
|
1763
|
+
if (options.addAll) {
|
|
1764
|
+
const currentCount = vault.chains.length;
|
|
1765
|
+
const spinner = createSpinner(`Adding all ${SUPPORTED_CHAINS.length} supported chains...`);
|
|
1766
|
+
await vault.setChains([...SUPPORTED_CHAINS]);
|
|
1767
|
+
const addedCount = SUPPORTED_CHAINS.length - currentCount;
|
|
1768
|
+
spinner.succeed(`Added ${addedCount} chains (${SUPPORTED_CHAINS.length} total)`);
|
|
1769
|
+
if (isJsonOutput()) {
|
|
1770
|
+
outputJson({ chains: [...vault.chains], added: addedCount, total: SUPPORTED_CHAINS.length });
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
info(chalk3.gray("\nAll supported chains are now enabled."));
|
|
1774
|
+
return;
|
|
1775
|
+
}
|
|
1747
1776
|
if (options.add) {
|
|
1748
1777
|
await vault.addChain(options.add);
|
|
1749
1778
|
success(`
|
|
@@ -1764,7 +1793,9 @@ async function executeChains(ctx2, options = {}) {
|
|
|
1764
1793
|
chains.forEach((chain) => {
|
|
1765
1794
|
printResult(` - ${chain}`);
|
|
1766
1795
|
});
|
|
1767
|
-
info(chalk3.gray(
|
|
1796
|
+
info(chalk3.gray(`
|
|
1797
|
+
${chains.length} of ${SUPPORTED_CHAINS.length} chains enabled`));
|
|
1798
|
+
info(chalk3.gray("Use --add <chain>, --add-all, or --remove <chain>"));
|
|
1768
1799
|
}
|
|
1769
1800
|
}
|
|
1770
1801
|
async function executeAddresses(ctx2) {
|
|
@@ -2506,8 +2537,8 @@ async function executeInfo(ctx2) {
|
|
|
2506
2537
|
}
|
|
2507
2538
|
displayVaultInfo(vault);
|
|
2508
2539
|
}
|
|
2509
|
-
async function
|
|
2510
|
-
const { mnemonic, name, password, email, discoverChains, chains, signal } = options;
|
|
2540
|
+
async function executeCreateFromSeedphraseFast(ctx2, options) {
|
|
2541
|
+
const { mnemonic, name, password, email, discoverChains, chains, signal, usePhantomSolanaPath } = options;
|
|
2511
2542
|
const validateSpinner = createSpinner("Validating seedphrase...");
|
|
2512
2543
|
const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
|
|
2513
2544
|
if (!validation.valid) {
|
|
@@ -2518,19 +2549,26 @@ async function executeImportSeedphraseFast(ctx2, options) {
|
|
|
2518
2549
|
throw new Error(validation.error || "Invalid mnemonic phrase");
|
|
2519
2550
|
}
|
|
2520
2551
|
validateSpinner.succeed(`Valid ${validation.wordCount}-word seedphrase`);
|
|
2552
|
+
let detectedUsePhantomSolanaPath = usePhantomSolanaPath;
|
|
2521
2553
|
if (discoverChains) {
|
|
2522
2554
|
const discoverSpinner = createSpinner("Discovering chains with balances...");
|
|
2523
2555
|
try {
|
|
2524
|
-
const discovered = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2556
|
+
const { results: discovered, usePhantomSolanaPath: detectedPhantomPath } = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2525
2557
|
discoverSpinner.text = `Discovering: ${p.chain || "scanning"} (${p.chainsProcessed}/${p.chainsTotal})`;
|
|
2526
2558
|
});
|
|
2527
2559
|
const chainsWithBalance = discovered.filter((c) => c.hasBalance);
|
|
2528
2560
|
discoverSpinner.succeed(`Found ${chainsWithBalance.length} chains with balances`);
|
|
2561
|
+
if (usePhantomSolanaPath === void 0) {
|
|
2562
|
+
detectedUsePhantomSolanaPath = detectedPhantomPath;
|
|
2563
|
+
}
|
|
2529
2564
|
if (chainsWithBalance.length > 0 && !isSilent()) {
|
|
2530
2565
|
info("\nChains with balances:");
|
|
2531
2566
|
for (const result of chainsWithBalance) {
|
|
2532
2567
|
info(` ${result.chain}: ${result.balance} ${result.symbol}`);
|
|
2533
2568
|
}
|
|
2569
|
+
if (detectedUsePhantomSolanaPath) {
|
|
2570
|
+
info(" (Using Phantom wallet derivation path for Solana)");
|
|
2571
|
+
}
|
|
2534
2572
|
info("");
|
|
2535
2573
|
}
|
|
2536
2574
|
} catch {
|
|
@@ -2539,13 +2577,14 @@ async function executeImportSeedphraseFast(ctx2, options) {
|
|
|
2539
2577
|
}
|
|
2540
2578
|
const importSpinner = createSpinner("Importing seedphrase...");
|
|
2541
2579
|
const vaultId = await withAbortSignal(
|
|
2542
|
-
ctx2.sdk.
|
|
2580
|
+
ctx2.sdk.createFastVaultFromSeedphrase({
|
|
2543
2581
|
mnemonic,
|
|
2544
2582
|
name,
|
|
2545
2583
|
password,
|
|
2546
2584
|
email,
|
|
2547
2585
|
// Don't pass discoverChains - CLI handles discovery above
|
|
2548
2586
|
chains,
|
|
2587
|
+
usePhantomSolanaPath: detectedUsePhantomSolanaPath,
|
|
2549
2588
|
onProgress: (step) => {
|
|
2550
2589
|
importSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2551
2590
|
}
|
|
@@ -2573,7 +2612,7 @@ async function executeImportSeedphraseFast(ctx2, options) {
|
|
|
2573
2612
|
verifySpinner.succeed("Email verified successfully!");
|
|
2574
2613
|
setupVaultEvents(vault);
|
|
2575
2614
|
await ctx2.setActiveVault(vault);
|
|
2576
|
-
success("\n+ Vault
|
|
2615
|
+
success("\n+ Vault created from seedphrase!");
|
|
2577
2616
|
info("\nYour vault is ready. Run the following commands:");
|
|
2578
2617
|
printResult(chalk5.cyan(" vultisig balance ") + "- View balances");
|
|
2579
2618
|
printResult(chalk5.cyan(" vultisig addresses ") + "- View addresses");
|
|
@@ -2623,8 +2662,18 @@ async function executeImportSeedphraseFast(ctx2, options) {
|
|
|
2623
2662
|
}
|
|
2624
2663
|
throw new Error("Verification loop exited unexpectedly");
|
|
2625
2664
|
}
|
|
2626
|
-
async function
|
|
2627
|
-
const {
|
|
2665
|
+
async function executeCreateFromSeedphraseSecure(ctx2, options) {
|
|
2666
|
+
const {
|
|
2667
|
+
mnemonic,
|
|
2668
|
+
name,
|
|
2669
|
+
password,
|
|
2670
|
+
threshold,
|
|
2671
|
+
shares: totalShares,
|
|
2672
|
+
discoverChains,
|
|
2673
|
+
chains,
|
|
2674
|
+
signal,
|
|
2675
|
+
usePhantomSolanaPath
|
|
2676
|
+
} = options;
|
|
2628
2677
|
const validateSpinner = createSpinner("Validating seedphrase...");
|
|
2629
2678
|
const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
|
|
2630
2679
|
if (!validation.valid) {
|
|
@@ -2635,19 +2684,26 @@ async function executeImportSeedphraseSecure(ctx2, options) {
|
|
|
2635
2684
|
throw new Error(validation.error || "Invalid mnemonic phrase");
|
|
2636
2685
|
}
|
|
2637
2686
|
validateSpinner.succeed(`Valid ${validation.wordCount}-word seedphrase`);
|
|
2687
|
+
let detectedUsePhantomSolanaPath = usePhantomSolanaPath;
|
|
2638
2688
|
if (discoverChains) {
|
|
2639
2689
|
const discoverSpinner = createSpinner("Discovering chains with balances...");
|
|
2640
2690
|
try {
|
|
2641
|
-
const discovered = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2691
|
+
const { results: discovered, usePhantomSolanaPath: detectedPhantomPath } = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2642
2692
|
discoverSpinner.text = `Discovering: ${p.chain || "scanning"} (${p.chainsProcessed}/${p.chainsTotal})`;
|
|
2643
2693
|
});
|
|
2644
2694
|
const chainsWithBalance = discovered.filter((c) => c.hasBalance);
|
|
2645
2695
|
discoverSpinner.succeed(`Found ${chainsWithBalance.length} chains with balances`);
|
|
2696
|
+
if (usePhantomSolanaPath === void 0) {
|
|
2697
|
+
detectedUsePhantomSolanaPath = detectedPhantomPath;
|
|
2698
|
+
}
|
|
2646
2699
|
if (chainsWithBalance.length > 0 && !isSilent()) {
|
|
2647
2700
|
info("\nChains with balances:");
|
|
2648
2701
|
for (const result of chainsWithBalance) {
|
|
2649
2702
|
info(` ${result.chain}: ${result.balance} ${result.symbol}`);
|
|
2650
2703
|
}
|
|
2704
|
+
if (detectedUsePhantomSolanaPath) {
|
|
2705
|
+
info(" (Using Phantom wallet derivation path for Solana)");
|
|
2706
|
+
}
|
|
2651
2707
|
info("");
|
|
2652
2708
|
}
|
|
2653
2709
|
} catch {
|
|
@@ -2657,7 +2713,7 @@ async function executeImportSeedphraseSecure(ctx2, options) {
|
|
|
2657
2713
|
const importSpinner = createSpinner("Importing seedphrase as secure vault...");
|
|
2658
2714
|
try {
|
|
2659
2715
|
const result = await withAbortSignal(
|
|
2660
|
-
ctx2.sdk.
|
|
2716
|
+
ctx2.sdk.createSecureVaultFromSeedphrase({
|
|
2661
2717
|
mnemonic,
|
|
2662
2718
|
name,
|
|
2663
2719
|
password,
|
|
@@ -2665,6 +2721,7 @@ async function executeImportSeedphraseSecure(ctx2, options) {
|
|
|
2665
2721
|
threshold,
|
|
2666
2722
|
// Don't pass discoverChains - CLI handles discovery above
|
|
2667
2723
|
chains,
|
|
2724
|
+
usePhantomSolanaPath: detectedUsePhantomSolanaPath,
|
|
2668
2725
|
onProgress: (step) => {
|
|
2669
2726
|
importSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2670
2727
|
},
|
|
@@ -2713,16 +2770,138 @@ Or use this URL: ${qrPayload}
|
|
|
2713
2770
|
warn(`
|
|
2714
2771
|
Important: Save your vault backup file (.vult) in a secure location.`);
|
|
2715
2772
|
warn(`This is a ${threshold}-of-${totalShares} vault. You'll need ${threshold} devices to sign transactions.`);
|
|
2716
|
-
success("\n+ Vault
|
|
2773
|
+
success("\n+ Vault created from seedphrase!");
|
|
2717
2774
|
return result.vault;
|
|
2718
2775
|
} catch (err) {
|
|
2719
|
-
importSpinner.fail("Secure vault
|
|
2776
|
+
importSpinner.fail("Secure vault creation failed");
|
|
2720
2777
|
if (err.message?.includes("not implemented")) {
|
|
2721
|
-
warn("\nSecure vault seedphrase
|
|
2778
|
+
warn("\nSecure vault creation from seedphrase is not yet implemented in the SDK");
|
|
2779
|
+
}
|
|
2780
|
+
throw err;
|
|
2781
|
+
}
|
|
2782
|
+
}
|
|
2783
|
+
async function executeJoinSecure(ctx2, options) {
|
|
2784
|
+
const { qrPayload, mnemonic, password, devices, signal } = options;
|
|
2785
|
+
if (!devices || devices < 2) {
|
|
2786
|
+
throw new Error("devices is required when joining a SecureVault (minimum 2)");
|
|
2787
|
+
}
|
|
2788
|
+
const spinner = createSpinner("Joining SecureVault session...");
|
|
2789
|
+
try {
|
|
2790
|
+
const result = await withAbortSignal(
|
|
2791
|
+
ctx2.sdk.joinSecureVault(qrPayload, {
|
|
2792
|
+
mnemonic,
|
|
2793
|
+
password,
|
|
2794
|
+
devices,
|
|
2795
|
+
onProgress: (step) => {
|
|
2796
|
+
spinner.text = `${step.message} (${step.progress}%)`;
|
|
2797
|
+
},
|
|
2798
|
+
onDeviceJoined: (deviceId, totalJoined, required) => {
|
|
2799
|
+
if (!isSilent()) {
|
|
2800
|
+
spinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
|
|
2801
|
+
} else if (!isJsonOutput()) {
|
|
2802
|
+
printResult(`Device joined: ${totalJoined}/${required}`);
|
|
2803
|
+
}
|
|
2804
|
+
}
|
|
2805
|
+
}),
|
|
2806
|
+
signal
|
|
2807
|
+
);
|
|
2808
|
+
setupVaultEvents(result.vault);
|
|
2809
|
+
await ctx2.setActiveVault(result.vault);
|
|
2810
|
+
spinner.succeed(`Joined SecureVault: ${result.vault.name}`);
|
|
2811
|
+
if (isJsonOutput()) {
|
|
2812
|
+
outputJson({
|
|
2813
|
+
vault: {
|
|
2814
|
+
id: result.vaultId,
|
|
2815
|
+
name: result.vault.name,
|
|
2816
|
+
type: "secure"
|
|
2817
|
+
}
|
|
2818
|
+
});
|
|
2819
|
+
return result.vault;
|
|
2722
2820
|
}
|
|
2821
|
+
warn("\nImportant: Save your vault backup file (.vult) in a secure location.");
|
|
2822
|
+
success("\n+ Successfully joined vault!");
|
|
2823
|
+
return result.vault;
|
|
2824
|
+
} catch (err) {
|
|
2825
|
+
spinner.fail("Failed to join SecureVault session");
|
|
2723
2826
|
throw err;
|
|
2724
2827
|
}
|
|
2725
2828
|
}
|
|
2829
|
+
async function executeDelete(ctx2, options = {}) {
|
|
2830
|
+
let vault;
|
|
2831
|
+
if (options.vaultId) {
|
|
2832
|
+
const vaults = await ctx2.sdk.listVaults();
|
|
2833
|
+
vault = findVaultByIdOrName({ vaults, idOrName: options.vaultId });
|
|
2834
|
+
} else {
|
|
2835
|
+
vault = await ctx2.ensureActiveVault();
|
|
2836
|
+
}
|
|
2837
|
+
if (isJsonOutput()) {
|
|
2838
|
+
const spinner2 = createSpinner("Deleting vault...");
|
|
2839
|
+
await ctx2.sdk.deleteVault(vault);
|
|
2840
|
+
spinner2.succeed("Vault deleted");
|
|
2841
|
+
outputJson({
|
|
2842
|
+
deleted: true,
|
|
2843
|
+
vault: {
|
|
2844
|
+
id: vault.id,
|
|
2845
|
+
name: vault.name,
|
|
2846
|
+
type: vault.type
|
|
2847
|
+
}
|
|
2848
|
+
});
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
info("\n" + chalk5.bold("Vault to delete:"));
|
|
2852
|
+
info(` Name: ${chalk5.cyan(vault.name)}`);
|
|
2853
|
+
info(` Type: ${chalk5.yellow(vault.type)}`);
|
|
2854
|
+
info(` ID: ${vault.id.slice(0, 16)}...`);
|
|
2855
|
+
info(` Chains: ${vault.chains.length}`);
|
|
2856
|
+
info("");
|
|
2857
|
+
if (!options.skipConfirmation) {
|
|
2858
|
+
warn(chalk5.red.bold("WARNING: This action cannot be undone!"));
|
|
2859
|
+
warn("Make sure you have a backup of your vault (.vult file) before proceeding.");
|
|
2860
|
+
info("");
|
|
2861
|
+
const { confirmed } = await inquirer4.prompt([
|
|
2862
|
+
{
|
|
2863
|
+
type: "confirm",
|
|
2864
|
+
name: "confirmed",
|
|
2865
|
+
message: `Are you sure you want to delete vault "${vault.name}"?`,
|
|
2866
|
+
default: false
|
|
2867
|
+
}
|
|
2868
|
+
]);
|
|
2869
|
+
if (!confirmed) {
|
|
2870
|
+
info("Deletion cancelled.");
|
|
2871
|
+
return;
|
|
2872
|
+
}
|
|
2873
|
+
}
|
|
2874
|
+
const spinner = createSpinner("Deleting vault...");
|
|
2875
|
+
await ctx2.sdk.deleteVault(vault);
|
|
2876
|
+
spinner.succeed(`Vault deleted: ${vault.name}`);
|
|
2877
|
+
success("\n+ Vault deleted successfully");
|
|
2878
|
+
info(chalk5.gray('\nTip: Run "vultisig vaults" to see remaining vaults'));
|
|
2879
|
+
}
|
|
2880
|
+
function findVaultByIdOrName({ vaults, idOrName }) {
|
|
2881
|
+
const byId = vaults.find((v) => v.id === idOrName);
|
|
2882
|
+
if (byId) return byId;
|
|
2883
|
+
const byName = vaults.filter((v) => v.name.toLowerCase() === idOrName.toLowerCase());
|
|
2884
|
+
if (byName.length === 1) return byName[0];
|
|
2885
|
+
if (byName.length > 1) {
|
|
2886
|
+
const matches = byName.map((v) => ` - ${v.name} (${v.id.slice(0, 8)}...)`).join("\n");
|
|
2887
|
+
throw new Error(
|
|
2888
|
+
`Multiple vaults match "${idOrName}":
|
|
2889
|
+
${matches}
|
|
2890
|
+
Please specify the full vault ID to be more specific.`
|
|
2891
|
+
);
|
|
2892
|
+
}
|
|
2893
|
+
const byPartialId = vaults.filter((v) => v.id.startsWith(idOrName));
|
|
2894
|
+
if (byPartialId.length === 1) return byPartialId[0];
|
|
2895
|
+
if (byPartialId.length > 1) {
|
|
2896
|
+
const matches = byPartialId.map((v) => ` - ${v.name} (${v.id.slice(0, 8)}...)`).join("\n");
|
|
2897
|
+
throw new Error(
|
|
2898
|
+
`Multiple vaults match prefix "${idOrName}":
|
|
2899
|
+
${matches}
|
|
2900
|
+
Please specify more characters of the vault ID.`
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
throw new Error(`Vault not found: "${idOrName}"`);
|
|
2904
|
+
}
|
|
2726
2905
|
|
|
2727
2906
|
// src/commands/swap.ts
|
|
2728
2907
|
async function executeSwapChains(ctx2) {
|
|
@@ -2765,11 +2944,13 @@ async function executeSwapQuote(ctx2, options) {
|
|
|
2765
2944
|
return quote;
|
|
2766
2945
|
}
|
|
2767
2946
|
const feeBalance = await vault.balance(options.fromChain);
|
|
2947
|
+
const discountTier = await vault.getDiscountTier();
|
|
2768
2948
|
displaySwapPreview(quote, String(options.amount), quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
2769
2949
|
fromDecimals: quote.fromCoin.decimals,
|
|
2770
2950
|
toDecimals: quote.toCoin.decimals,
|
|
2771
2951
|
feeDecimals: feeBalance.decimals,
|
|
2772
|
-
feeSymbol: feeBalance.symbol
|
|
2952
|
+
feeSymbol: feeBalance.symbol,
|
|
2953
|
+
discountTier
|
|
2773
2954
|
});
|
|
2774
2955
|
info('\nTo execute this swap, use the "swap" command');
|
|
2775
2956
|
return quote;
|
|
@@ -2793,12 +2974,14 @@ async function executeSwap(ctx2, options) {
|
|
|
2793
2974
|
});
|
|
2794
2975
|
quoteSpinner.succeed("Quote received");
|
|
2795
2976
|
const feeBalance = await vault.balance(options.fromChain);
|
|
2977
|
+
const discountTier = await vault.getDiscountTier();
|
|
2796
2978
|
if (!isJsonOutput()) {
|
|
2797
2979
|
displaySwapPreview(quote, String(options.amount), quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
2798
2980
|
fromDecimals: quote.fromCoin.decimals,
|
|
2799
2981
|
toDecimals: quote.toCoin.decimals,
|
|
2800
2982
|
feeDecimals: feeBalance.decimals,
|
|
2801
|
-
feeSymbol: feeBalance.symbol
|
|
2983
|
+
feeSymbol: feeBalance.symbol,
|
|
2984
|
+
discountTier
|
|
2802
2985
|
});
|
|
2803
2986
|
}
|
|
2804
2987
|
if (!options.yes && !isJsonOutput()) {
|
|
@@ -3031,6 +3214,104 @@ Address Book${options.chain ? ` (${options.chain})` : ""}:
|
|
|
3031
3214
|
return allEntries;
|
|
3032
3215
|
}
|
|
3033
3216
|
|
|
3217
|
+
// src/commands/discount.ts
|
|
3218
|
+
import {
|
|
3219
|
+
baseAffiliateBps,
|
|
3220
|
+
vultDiscountTierBps,
|
|
3221
|
+
vultDiscountTierMinBalances
|
|
3222
|
+
} from "@vultisig/sdk";
|
|
3223
|
+
import chalk7 from "chalk";
|
|
3224
|
+
var TIER_CONFIG = {
|
|
3225
|
+
none: { bps: baseAffiliateBps, discount: 0 },
|
|
3226
|
+
...Object.fromEntries(
|
|
3227
|
+
Object.entries(vultDiscountTierMinBalances).map(([tier, minVult]) => [
|
|
3228
|
+
tier,
|
|
3229
|
+
{
|
|
3230
|
+
bps: baseAffiliateBps - vultDiscountTierBps[tier],
|
|
3231
|
+
discount: vultDiscountTierBps[tier],
|
|
3232
|
+
minVult
|
|
3233
|
+
}
|
|
3234
|
+
])
|
|
3235
|
+
)
|
|
3236
|
+
};
|
|
3237
|
+
function getTierColor(tier) {
|
|
3238
|
+
const colors = {
|
|
3239
|
+
none: chalk7.gray,
|
|
3240
|
+
bronze: chalk7.hex("#CD7F32"),
|
|
3241
|
+
silver: chalk7.hex("#C0C0C0"),
|
|
3242
|
+
gold: chalk7.hex("#FFD700"),
|
|
3243
|
+
platinum: chalk7.hex("#E5E4E2"),
|
|
3244
|
+
diamond: chalk7.hex("#B9F2FF"),
|
|
3245
|
+
ultimate: chalk7.hex("#FF00FF")
|
|
3246
|
+
};
|
|
3247
|
+
return colors[tier] || chalk7.white;
|
|
3248
|
+
}
|
|
3249
|
+
function getNextTier(currentTier) {
|
|
3250
|
+
const tierOrder = ["none", "bronze", "silver", "gold", "platinum", "diamond", "ultimate"];
|
|
3251
|
+
const currentIndex = tierOrder.indexOf(currentTier);
|
|
3252
|
+
if (currentIndex === -1 || currentIndex >= tierOrder.length - 1) {
|
|
3253
|
+
return null;
|
|
3254
|
+
}
|
|
3255
|
+
const nextTierName = tierOrder[currentIndex + 1];
|
|
3256
|
+
const config = TIER_CONFIG[nextTierName];
|
|
3257
|
+
if ("minVult" in config) {
|
|
3258
|
+
return { name: nextTierName, vultRequired: config.minVult };
|
|
3259
|
+
}
|
|
3260
|
+
return null;
|
|
3261
|
+
}
|
|
3262
|
+
async function executeDiscount(ctx2, options = {}) {
|
|
3263
|
+
const vault = await ctx2.ensureActiveVault();
|
|
3264
|
+
const spinner = createSpinner(options.refresh ? "Refreshing discount tier..." : "Loading discount tier...");
|
|
3265
|
+
const tierResult = options.refresh ? await vault.updateDiscountTier() : await vault.getDiscountTier();
|
|
3266
|
+
const tier = tierResult || "none";
|
|
3267
|
+
const config = TIER_CONFIG[tier];
|
|
3268
|
+
const nextTier = getNextTier(tier);
|
|
3269
|
+
const tierInfo = {
|
|
3270
|
+
tier,
|
|
3271
|
+
feeBps: config.bps,
|
|
3272
|
+
discountBps: config.discount,
|
|
3273
|
+
nextTier
|
|
3274
|
+
};
|
|
3275
|
+
spinner.succeed("Discount tier loaded");
|
|
3276
|
+
if (isJsonOutput()) {
|
|
3277
|
+
outputJson({
|
|
3278
|
+
tier: tierInfo.tier,
|
|
3279
|
+
feeBps: tierInfo.feeBps,
|
|
3280
|
+
discountBps: tierInfo.discountBps,
|
|
3281
|
+
nextTier: tierInfo.nextTier
|
|
3282
|
+
});
|
|
3283
|
+
return tierInfo;
|
|
3284
|
+
}
|
|
3285
|
+
displayDiscountTier(tierInfo);
|
|
3286
|
+
return tierInfo;
|
|
3287
|
+
}
|
|
3288
|
+
function displayDiscountTier(tierInfo) {
|
|
3289
|
+
const tierColor = getTierColor(tierInfo.tier);
|
|
3290
|
+
printResult(chalk7.cyan("\n+----------------------------------------+"));
|
|
3291
|
+
printResult(chalk7.cyan("| VULT Discount Tier |"));
|
|
3292
|
+
printResult(chalk7.cyan("+----------------------------------------+\n"));
|
|
3293
|
+
const tierDisplay = tierInfo.tier === "none" ? chalk7.gray("No Tier") : tierColor(tierInfo.tier.charAt(0).toUpperCase() + tierInfo.tier.slice(1));
|
|
3294
|
+
printResult(` Current Tier: ${tierDisplay}`);
|
|
3295
|
+
if (tierInfo.tier === "none") {
|
|
3296
|
+
printResult(` Swap Fee: ${chalk7.gray("50 bps (0.50%)")}`);
|
|
3297
|
+
printResult(` Discount: ${chalk7.gray("None")}`);
|
|
3298
|
+
} else {
|
|
3299
|
+
printResult(` Swap Fee: ${chalk7.green(`${tierInfo.feeBps} bps (${(tierInfo.feeBps / 100).toFixed(2)}%)`)}`);
|
|
3300
|
+
printResult(` Discount: ${chalk7.green(`${tierInfo.discountBps} bps saved`)}`);
|
|
3301
|
+
}
|
|
3302
|
+
if (tierInfo.nextTier) {
|
|
3303
|
+
const nextTierColor = getTierColor(tierInfo.nextTier.name);
|
|
3304
|
+
printResult(chalk7.bold("\n Next Tier:"));
|
|
3305
|
+
printResult(
|
|
3306
|
+
` ${nextTierColor(tierInfo.nextTier.name.charAt(0).toUpperCase() + tierInfo.nextTier.name.slice(1))} - requires ${tierInfo.nextTier.vultRequired.toLocaleString()} VULT`
|
|
3307
|
+
);
|
|
3308
|
+
} else if (tierInfo.tier === "ultimate") {
|
|
3309
|
+
printResult(chalk7.bold("\n ") + chalk7.magenta("You have the highest tier! 0% swap fees."));
|
|
3310
|
+
}
|
|
3311
|
+
info(chalk7.gray("\n Tip: Thorguard NFT holders get +1 tier upgrade (up to gold)"));
|
|
3312
|
+
printResult("");
|
|
3313
|
+
}
|
|
3314
|
+
|
|
3034
3315
|
// src/interactive/completer.ts
|
|
3035
3316
|
import { Chain as Chain5 } from "@vultisig/sdk";
|
|
3036
3317
|
import fs2 from "fs";
|
|
@@ -3040,8 +3321,10 @@ var COMMANDS = [
|
|
|
3040
3321
|
"vaults",
|
|
3041
3322
|
"vault",
|
|
3042
3323
|
"import",
|
|
3043
|
-
"
|
|
3324
|
+
"delete",
|
|
3325
|
+
"create-from-seedphrase",
|
|
3044
3326
|
"create",
|
|
3327
|
+
"join",
|
|
3045
3328
|
"info",
|
|
3046
3329
|
"export",
|
|
3047
3330
|
// Wallet operations
|
|
@@ -3086,26 +3369,36 @@ function createCompleter(ctx2) {
|
|
|
3086
3369
|
return completeVaultName(ctx2, partial);
|
|
3087
3370
|
}
|
|
3088
3371
|
if (command === "chains" && parts.length >= 2) {
|
|
3372
|
+
const lastPart = parts[parts.length - 1] || "";
|
|
3373
|
+
const lastPartLower = lastPart.toLowerCase();
|
|
3374
|
+
if (lastPartLower.startsWith("-")) {
|
|
3375
|
+
const flags = ["--add", "--add-all", "--remove"];
|
|
3376
|
+
const matches = flags.filter((f) => f.startsWith(lastPartLower));
|
|
3377
|
+
return [matches.length ? matches : flags, lastPart];
|
|
3378
|
+
}
|
|
3089
3379
|
const flag = parts[parts.length - 2]?.toLowerCase();
|
|
3090
3380
|
if (flag === "--add" || flag === "--remove") {
|
|
3091
|
-
|
|
3092
|
-
return completeChainName(partial);
|
|
3093
|
-
}
|
|
3094
|
-
if (parts[parts.length - 1]?.toLowerCase() === "--add" || parts[parts.length - 1]?.toLowerCase() === "--remove") {
|
|
3095
|
-
return completeChainName("");
|
|
3381
|
+
return completeChainName(lastPart);
|
|
3096
3382
|
}
|
|
3097
3383
|
}
|
|
3098
3384
|
if (["balance", "bal", "tokens", "send", "swap", "swap-quote"].includes(command) && parts.length === 2) {
|
|
3099
3385
|
const partial = parts[1] || "";
|
|
3100
3386
|
return completeChainName(partial);
|
|
3101
3387
|
}
|
|
3102
|
-
if ((command === "create" || command === "
|
|
3388
|
+
if ((command === "create" || command === "create-from-seedphrase") && parts.length === 2) {
|
|
3103
3389
|
const types = ["fast", "secure"];
|
|
3104
3390
|
const partial = parts[1] || "";
|
|
3105
3391
|
const partialLower = partial.toLowerCase();
|
|
3106
3392
|
const matches = types.filter((t) => t.startsWith(partialLower));
|
|
3107
3393
|
return [matches.length ? matches : types, partial];
|
|
3108
3394
|
}
|
|
3395
|
+
if (command === "join" && parts.length === 2) {
|
|
3396
|
+
const types = ["secure"];
|
|
3397
|
+
const partial = parts[1] || "";
|
|
3398
|
+
const partialLower = partial.toLowerCase();
|
|
3399
|
+
const matches = types.filter((t) => t.startsWith(partialLower));
|
|
3400
|
+
return [matches.length ? matches : types, partial];
|
|
3401
|
+
}
|
|
3109
3402
|
const hits = COMMANDS.filter((c) => c.startsWith(line));
|
|
3110
3403
|
const show = hits.length ? hits : COMMANDS;
|
|
3111
3404
|
return [show, line];
|
|
@@ -3178,7 +3471,7 @@ function findChainByName(name) {
|
|
|
3178
3471
|
}
|
|
3179
3472
|
|
|
3180
3473
|
// src/interactive/event-buffer.ts
|
|
3181
|
-
import
|
|
3474
|
+
import chalk8 from "chalk";
|
|
3182
3475
|
var EventBuffer = class {
|
|
3183
3476
|
eventBuffer = [];
|
|
3184
3477
|
isCommandRunning = false;
|
|
@@ -3218,17 +3511,17 @@ var EventBuffer = class {
|
|
|
3218
3511
|
displayEvent(message, type) {
|
|
3219
3512
|
switch (type) {
|
|
3220
3513
|
case "success":
|
|
3221
|
-
console.log(
|
|
3514
|
+
console.log(chalk8.green(message));
|
|
3222
3515
|
break;
|
|
3223
3516
|
case "warning":
|
|
3224
|
-
console.log(
|
|
3517
|
+
console.log(chalk8.yellow(message));
|
|
3225
3518
|
break;
|
|
3226
3519
|
case "error":
|
|
3227
|
-
console.error(
|
|
3520
|
+
console.error(chalk8.red(message));
|
|
3228
3521
|
break;
|
|
3229
3522
|
case "info":
|
|
3230
3523
|
default:
|
|
3231
|
-
console.log(
|
|
3524
|
+
console.log(chalk8.blue(message));
|
|
3232
3525
|
break;
|
|
3233
3526
|
}
|
|
3234
3527
|
}
|
|
@@ -3239,13 +3532,13 @@ var EventBuffer = class {
|
|
|
3239
3532
|
if (this.eventBuffer.length === 0) {
|
|
3240
3533
|
return;
|
|
3241
3534
|
}
|
|
3242
|
-
console.log(
|
|
3535
|
+
console.log(chalk8.gray("\n--- Background Events ---"));
|
|
3243
3536
|
this.eventBuffer.forEach((event) => {
|
|
3244
3537
|
const timeStr = event.timestamp.toLocaleTimeString();
|
|
3245
3538
|
const message = `[${timeStr}] ${event.message}`;
|
|
3246
3539
|
this.displayEvent(message, event.type);
|
|
3247
3540
|
});
|
|
3248
|
-
console.log(
|
|
3541
|
+
console.log(chalk8.gray("--- End Events ---\n"));
|
|
3249
3542
|
}
|
|
3250
3543
|
/**
|
|
3251
3544
|
* Setup all vault event listeners
|
|
@@ -3347,12 +3640,12 @@ var EventBuffer = class {
|
|
|
3347
3640
|
|
|
3348
3641
|
// src/interactive/session.ts
|
|
3349
3642
|
import { fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
|
|
3350
|
-
import
|
|
3643
|
+
import chalk10 from "chalk";
|
|
3351
3644
|
import ora3 from "ora";
|
|
3352
3645
|
import * as readline from "readline";
|
|
3353
3646
|
|
|
3354
3647
|
// src/interactive/shell-commands.ts
|
|
3355
|
-
import
|
|
3648
|
+
import chalk9 from "chalk";
|
|
3356
3649
|
import Table from "cli-table3";
|
|
3357
3650
|
import inquirer6 from "inquirer";
|
|
3358
3651
|
import ora2 from "ora";
|
|
@@ -3369,25 +3662,25 @@ function formatTimeRemaining(ms) {
|
|
|
3369
3662
|
async function executeLock(ctx2) {
|
|
3370
3663
|
const vault = ctx2.getActiveVault();
|
|
3371
3664
|
if (!vault) {
|
|
3372
|
-
console.log(
|
|
3373
|
-
console.log(
|
|
3665
|
+
console.log(chalk9.red("No active vault."));
|
|
3666
|
+
console.log(chalk9.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
3374
3667
|
return;
|
|
3375
3668
|
}
|
|
3376
3669
|
ctx2.lockVault(vault.id);
|
|
3377
|
-
console.log(
|
|
3378
|
-
console.log(
|
|
3670
|
+
console.log(chalk9.green("\n+ Vault locked"));
|
|
3671
|
+
console.log(chalk9.gray("Password cache cleared. You will need to enter the password again."));
|
|
3379
3672
|
}
|
|
3380
3673
|
async function executeUnlock(ctx2) {
|
|
3381
3674
|
const vault = ctx2.getActiveVault();
|
|
3382
3675
|
if (!vault) {
|
|
3383
|
-
console.log(
|
|
3384
|
-
console.log(
|
|
3676
|
+
console.log(chalk9.red("No active vault."));
|
|
3677
|
+
console.log(chalk9.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
3385
3678
|
return;
|
|
3386
3679
|
}
|
|
3387
3680
|
if (ctx2.isVaultUnlocked(vault.id)) {
|
|
3388
3681
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
3389
|
-
console.log(
|
|
3390
|
-
console.log(
|
|
3682
|
+
console.log(chalk9.yellow("\nVault is already unlocked."));
|
|
3683
|
+
console.log(chalk9.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
|
|
3391
3684
|
return;
|
|
3392
3685
|
}
|
|
3393
3686
|
const { password } = await inquirer6.prompt([
|
|
@@ -3404,19 +3697,19 @@ async function executeUnlock(ctx2) {
|
|
|
3404
3697
|
ctx2.cachePassword(vault.id, password);
|
|
3405
3698
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
3406
3699
|
spinner.succeed("Vault unlocked");
|
|
3407
|
-
console.log(
|
|
3700
|
+
console.log(chalk9.green(`
|
|
3408
3701
|
+ Vault unlocked for ${formatTimeRemaining(timeRemaining)}`));
|
|
3409
3702
|
} catch (err) {
|
|
3410
3703
|
spinner.fail("Failed to unlock vault");
|
|
3411
|
-
console.error(
|
|
3704
|
+
console.error(chalk9.red(`
|
|
3412
3705
|
x ${err.message}`));
|
|
3413
3706
|
}
|
|
3414
3707
|
}
|
|
3415
3708
|
async function executeStatus(ctx2) {
|
|
3416
3709
|
const vault = ctx2.getActiveVault();
|
|
3417
3710
|
if (!vault) {
|
|
3418
|
-
console.log(
|
|
3419
|
-
console.log(
|
|
3711
|
+
console.log(chalk9.red("No active vault."));
|
|
3712
|
+
console.log(chalk9.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
3420
3713
|
return;
|
|
3421
3714
|
}
|
|
3422
3715
|
const isUnlocked = ctx2.isVaultUnlocked(vault.id);
|
|
@@ -3447,30 +3740,30 @@ async function executeStatus(ctx2) {
|
|
|
3447
3740
|
displayStatus(status);
|
|
3448
3741
|
}
|
|
3449
3742
|
function displayStatus(status) {
|
|
3450
|
-
console.log(
|
|
3451
|
-
console.log(
|
|
3452
|
-
console.log(
|
|
3453
|
-
console.log(
|
|
3454
|
-
console.log(` Name: ${
|
|
3743
|
+
console.log(chalk9.cyan("\n+----------------------------------------+"));
|
|
3744
|
+
console.log(chalk9.cyan("| Vault Status |"));
|
|
3745
|
+
console.log(chalk9.cyan("+----------------------------------------+\n"));
|
|
3746
|
+
console.log(chalk9.bold("Vault:"));
|
|
3747
|
+
console.log(` Name: ${chalk9.green(status.name)}`);
|
|
3455
3748
|
console.log(` ID: ${status.id}`);
|
|
3456
|
-
console.log(` Type: ${
|
|
3457
|
-
console.log(
|
|
3749
|
+
console.log(` Type: ${chalk9.yellow(status.type)}`);
|
|
3750
|
+
console.log(chalk9.bold("\nSecurity:"));
|
|
3458
3751
|
if (status.isUnlocked) {
|
|
3459
|
-
console.log(` Status: ${
|
|
3752
|
+
console.log(` Status: ${chalk9.green("Unlocked")} ${chalk9.green("\u{1F513}")}`);
|
|
3460
3753
|
console.log(` Expires: ${status.timeRemainingFormatted}`);
|
|
3461
3754
|
} else {
|
|
3462
|
-
console.log(` Status: ${
|
|
3755
|
+
console.log(` Status: ${chalk9.yellow("Locked")} ${chalk9.yellow("\u{1F512}")}`);
|
|
3463
3756
|
}
|
|
3464
|
-
console.log(` Encrypted: ${status.isEncrypted ?
|
|
3465
|
-
console.log(` Backed Up: ${status.isBackedUp ?
|
|
3466
|
-
console.log(
|
|
3757
|
+
console.log(` Encrypted: ${status.isEncrypted ? chalk9.green("Yes") : chalk9.gray("No")}`);
|
|
3758
|
+
console.log(` Backed Up: ${status.isBackedUp ? chalk9.green("Yes") : chalk9.yellow("No")}`);
|
|
3759
|
+
console.log(chalk9.bold("\nMPC Configuration:"));
|
|
3467
3760
|
console.log(` Library: ${status.libType}`);
|
|
3468
|
-
console.log(` Threshold: ${
|
|
3469
|
-
console.log(
|
|
3761
|
+
console.log(` Threshold: ${chalk9.cyan(status.threshold)} of ${chalk9.cyan(status.totalSigners)}`);
|
|
3762
|
+
console.log(chalk9.bold("\nSigning Modes:"));
|
|
3470
3763
|
status.availableSigningModes.forEach((mode) => {
|
|
3471
3764
|
console.log(` - ${mode}`);
|
|
3472
3765
|
});
|
|
3473
|
-
console.log(
|
|
3766
|
+
console.log(chalk9.bold("\nDetails:"));
|
|
3474
3767
|
console.log(` Chains: ${status.chains}`);
|
|
3475
3768
|
console.log(` Currency: ${status.currency.toUpperCase()}`);
|
|
3476
3769
|
console.log(` Created: ${new Date(status.createdAt).toLocaleString()}`);
|
|
@@ -3479,7 +3772,7 @@ function displayStatus(status) {
|
|
|
3479
3772
|
}
|
|
3480
3773
|
function showHelp() {
|
|
3481
3774
|
const table = new Table({
|
|
3482
|
-
head: [
|
|
3775
|
+
head: [chalk9.bold("Available Commands")],
|
|
3483
3776
|
colWidths: [50],
|
|
3484
3777
|
chars: {
|
|
3485
3778
|
mid: "",
|
|
@@ -3493,38 +3786,39 @@ function showHelp() {
|
|
|
3493
3786
|
}
|
|
3494
3787
|
});
|
|
3495
3788
|
table.push(
|
|
3496
|
-
[
|
|
3789
|
+
[chalk9.bold("Vault Management:")],
|
|
3497
3790
|
[" vaults - List all vaults"],
|
|
3498
3791
|
[" vault <name> - Switch to vault"],
|
|
3499
3792
|
[" import <file> - Import vault from file"],
|
|
3793
|
+
[" delete [name] - Delete vault"],
|
|
3500
3794
|
[" create - Create new vault"],
|
|
3501
3795
|
[" info - Show vault details"],
|
|
3502
3796
|
[" export [path] - Export vault to file"],
|
|
3503
3797
|
[""],
|
|
3504
|
-
[
|
|
3798
|
+
[chalk9.bold("Wallet Operations:")],
|
|
3505
3799
|
[" balance [chain] - Show balances"],
|
|
3506
3800
|
[" send <chain> <to> <amount> - Send transaction"],
|
|
3507
3801
|
[" portfolio [-c usd] - Show portfolio value"],
|
|
3508
3802
|
[" addresses - Show all addresses"],
|
|
3509
|
-
[" chains [--add/--remove] - Manage chains"],
|
|
3803
|
+
[" chains [--add/--remove/--add-all] - Manage chains"],
|
|
3510
3804
|
[" tokens <chain> - Manage tokens"],
|
|
3511
3805
|
[""],
|
|
3512
|
-
[
|
|
3806
|
+
[chalk9.bold("Swap Operations:")],
|
|
3513
3807
|
[" swap-chains - List swap-enabled chains"],
|
|
3514
3808
|
[" swap-quote <from> <to> <amount> - Get quote"],
|
|
3515
3809
|
[" swap <from> <to> <amount> - Execute swap"],
|
|
3516
3810
|
[""],
|
|
3517
|
-
[
|
|
3811
|
+
[chalk9.bold("Session Commands (shell only):")],
|
|
3518
3812
|
[" lock - Lock vault"],
|
|
3519
3813
|
[" unlock - Unlock vault"],
|
|
3520
3814
|
[" status - Show vault status"],
|
|
3521
3815
|
[""],
|
|
3522
|
-
[
|
|
3816
|
+
[chalk9.bold("Settings:")],
|
|
3523
3817
|
[" currency [code] - View/set currency"],
|
|
3524
3818
|
[" server - Check server status"],
|
|
3525
3819
|
[" address-book - Manage saved addresses"],
|
|
3526
3820
|
[""],
|
|
3527
|
-
[
|
|
3821
|
+
[chalk9.bold("Help & Navigation:")],
|
|
3528
3822
|
[" help, ? - Show this help"],
|
|
3529
3823
|
[" .clear - Clear screen"],
|
|
3530
3824
|
[" .exit - Exit shell"]
|
|
@@ -3662,12 +3956,12 @@ var ShellSession = class {
|
|
|
3662
3956
|
*/
|
|
3663
3957
|
async start() {
|
|
3664
3958
|
console.clear();
|
|
3665
|
-
console.log(
|
|
3666
|
-
console.log(
|
|
3667
|
-
console.log(
|
|
3959
|
+
console.log(chalk10.cyan.bold("\n=============================================="));
|
|
3960
|
+
console.log(chalk10.cyan.bold(" Vultisig Interactive Shell"));
|
|
3961
|
+
console.log(chalk10.cyan.bold("==============================================\n"));
|
|
3668
3962
|
await this.loadAllVaults();
|
|
3669
3963
|
this.displayVaultList();
|
|
3670
|
-
console.log(
|
|
3964
|
+
console.log(chalk10.gray('Type "help" for available commands, "exit" to quit\n'));
|
|
3671
3965
|
this.promptLoop().catch(() => {
|
|
3672
3966
|
});
|
|
3673
3967
|
}
|
|
@@ -3701,12 +3995,12 @@ var ShellSession = class {
|
|
|
3701
3995
|
const now = Date.now();
|
|
3702
3996
|
if (now - this.lastSigintTime < this.DOUBLE_CTRL_C_TIMEOUT) {
|
|
3703
3997
|
rl.close();
|
|
3704
|
-
console.log(
|
|
3998
|
+
console.log(chalk10.yellow("\nGoodbye!"));
|
|
3705
3999
|
this.ctx.dispose();
|
|
3706
4000
|
process.exit(0);
|
|
3707
4001
|
}
|
|
3708
4002
|
this.lastSigintTime = now;
|
|
3709
|
-
console.log(
|
|
4003
|
+
console.log(chalk10.yellow("\n(Press Ctrl+C again to exit)"));
|
|
3710
4004
|
rl.close();
|
|
3711
4005
|
resolve("");
|
|
3712
4006
|
});
|
|
@@ -3801,7 +4095,7 @@ var ShellSession = class {
|
|
|
3801
4095
|
stopAllSpinners();
|
|
3802
4096
|
process.stdout.write("\x1B[?25h");
|
|
3803
4097
|
process.stdout.write("\r\x1B[K");
|
|
3804
|
-
console.log(
|
|
4098
|
+
console.log(chalk10.yellow("\nCancelling operation..."));
|
|
3805
4099
|
};
|
|
3806
4100
|
const cleanup = () => {
|
|
3807
4101
|
process.removeListener("SIGINT", onSigint);
|
|
@@ -3838,10 +4132,10 @@ var ShellSession = class {
|
|
|
3838
4132
|
stopAllSpinners();
|
|
3839
4133
|
process.stdout.write("\x1B[?25h");
|
|
3840
4134
|
process.stdout.write("\r\x1B[K");
|
|
3841
|
-
console.log(
|
|
4135
|
+
console.log(chalk10.yellow("Operation cancelled"));
|
|
3842
4136
|
return;
|
|
3843
4137
|
}
|
|
3844
|
-
console.error(
|
|
4138
|
+
console.error(chalk10.red(`
|
|
3845
4139
|
Error: ${error2.message}`));
|
|
3846
4140
|
}
|
|
3847
4141
|
}
|
|
@@ -3863,7 +4157,7 @@ Error: ${error2.message}`));
|
|
|
3863
4157
|
case "create":
|
|
3864
4158
|
await this.createVault(args);
|
|
3865
4159
|
break;
|
|
3866
|
-
case "
|
|
4160
|
+
case "create-from-seedphrase":
|
|
3867
4161
|
await this.importSeedphrase(args);
|
|
3868
4162
|
break;
|
|
3869
4163
|
case "info":
|
|
@@ -3874,11 +4168,14 @@ Error: ${error2.message}`));
|
|
|
3874
4168
|
break;
|
|
3875
4169
|
case "rename":
|
|
3876
4170
|
if (args.length === 0) {
|
|
3877
|
-
console.log(
|
|
4171
|
+
console.log(chalk10.yellow("Usage: rename <newName>"));
|
|
3878
4172
|
return;
|
|
3879
4173
|
}
|
|
3880
4174
|
await executeRename(this.ctx, args.join(" "));
|
|
3881
4175
|
break;
|
|
4176
|
+
case "delete":
|
|
4177
|
+
await this.deleteVault(args);
|
|
4178
|
+
break;
|
|
3882
4179
|
// Balance commands
|
|
3883
4180
|
case "balance":
|
|
3884
4181
|
case "bal":
|
|
@@ -3944,41 +4241,41 @@ Error: ${error2.message}`));
|
|
|
3944
4241
|
// Exit
|
|
3945
4242
|
case "exit":
|
|
3946
4243
|
case "quit":
|
|
3947
|
-
console.log(
|
|
4244
|
+
console.log(chalk10.yellow("\nGoodbye!"));
|
|
3948
4245
|
this.ctx.dispose();
|
|
3949
4246
|
process.exit(0);
|
|
3950
4247
|
break;
|
|
3951
4248
|
// eslint requires break even after process.exit
|
|
3952
4249
|
default:
|
|
3953
|
-
console.log(
|
|
3954
|
-
console.log(
|
|
4250
|
+
console.log(chalk10.yellow(`Unknown command: ${command}`));
|
|
4251
|
+
console.log(chalk10.gray('Type "help" for available commands'));
|
|
3955
4252
|
break;
|
|
3956
4253
|
}
|
|
3957
4254
|
}
|
|
3958
4255
|
// ===== Command Helpers =====
|
|
3959
4256
|
async switchVault(args) {
|
|
3960
4257
|
if (args.length === 0) {
|
|
3961
|
-
console.log(
|
|
3962
|
-
console.log(
|
|
4258
|
+
console.log(chalk10.yellow("Usage: vault <name>"));
|
|
4259
|
+
console.log(chalk10.gray('Run "vaults" to see available vaults'));
|
|
3963
4260
|
return;
|
|
3964
4261
|
}
|
|
3965
4262
|
const vaultName = args.join(" ");
|
|
3966
4263
|
const vault = this.ctx.findVaultByName(vaultName);
|
|
3967
4264
|
if (!vault) {
|
|
3968
|
-
console.log(
|
|
3969
|
-
console.log(
|
|
4265
|
+
console.log(chalk10.red(`Vault not found: ${vaultName}`));
|
|
4266
|
+
console.log(chalk10.gray('Run "vaults" to see available vaults'));
|
|
3970
4267
|
return;
|
|
3971
4268
|
}
|
|
3972
4269
|
await this.ctx.setActiveVault(vault);
|
|
3973
|
-
console.log(
|
|
4270
|
+
console.log(chalk10.green(`
|
|
3974
4271
|
+ Switched to: ${vault.name}`));
|
|
3975
4272
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
3976
|
-
const status = isUnlocked ?
|
|
4273
|
+
const status = isUnlocked ? chalk10.green("Unlocked") : chalk10.yellow("Locked");
|
|
3977
4274
|
console.log(`Status: ${status}`);
|
|
3978
4275
|
}
|
|
3979
4276
|
async importVault(args) {
|
|
3980
4277
|
if (args.length === 0) {
|
|
3981
|
-
console.log(
|
|
4278
|
+
console.log(chalk10.yellow("Usage: import <file>"));
|
|
3982
4279
|
return;
|
|
3983
4280
|
}
|
|
3984
4281
|
const filePath = args.join(" ");
|
|
@@ -3986,48 +4283,52 @@ Error: ${error2.message}`));
|
|
|
3986
4283
|
this.ctx.addVault(vault);
|
|
3987
4284
|
this.eventBuffer.setupVaultListeners(vault);
|
|
3988
4285
|
}
|
|
4286
|
+
async deleteVault(args) {
|
|
4287
|
+
const vaultIdOrName = args.join(" ") || void 0;
|
|
4288
|
+
await executeDelete(this.ctx, { vaultId: vaultIdOrName });
|
|
4289
|
+
}
|
|
3989
4290
|
async createVault(args) {
|
|
3990
4291
|
const type = args[0]?.toLowerCase();
|
|
3991
4292
|
if (!type || type !== "fast" && type !== "secure") {
|
|
3992
|
-
console.log(
|
|
3993
|
-
console.log(
|
|
3994
|
-
console.log(
|
|
4293
|
+
console.log(chalk10.yellow("Usage: create <fast|secure>"));
|
|
4294
|
+
console.log(chalk10.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
|
|
4295
|
+
console.log(chalk10.gray(" create secure - Create a secure vault (multi-device MPC)"));
|
|
3995
4296
|
return;
|
|
3996
4297
|
}
|
|
3997
4298
|
let vault;
|
|
3998
4299
|
if (type === "fast") {
|
|
3999
4300
|
const name = await this.prompt("Vault name");
|
|
4000
4301
|
if (!name) {
|
|
4001
|
-
console.log(
|
|
4302
|
+
console.log(chalk10.red("Name is required"));
|
|
4002
4303
|
return;
|
|
4003
4304
|
}
|
|
4004
4305
|
const password = await this.promptPassword("Vault password");
|
|
4005
4306
|
if (!password) {
|
|
4006
|
-
console.log(
|
|
4307
|
+
console.log(chalk10.red("Password is required"));
|
|
4007
4308
|
return;
|
|
4008
4309
|
}
|
|
4009
4310
|
const email = await this.prompt("Email for verification");
|
|
4010
4311
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4011
|
-
console.log(
|
|
4312
|
+
console.log(chalk10.red("Valid email is required"));
|
|
4012
4313
|
return;
|
|
4013
4314
|
}
|
|
4014
4315
|
vault = await this.withCancellation((signal) => executeCreateFast(this.ctx, { name, password, email, signal }));
|
|
4015
4316
|
} else {
|
|
4016
4317
|
const name = await this.prompt("Vault name");
|
|
4017
4318
|
if (!name) {
|
|
4018
|
-
console.log(
|
|
4319
|
+
console.log(chalk10.red("Name is required"));
|
|
4019
4320
|
return;
|
|
4020
4321
|
}
|
|
4021
4322
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
4022
4323
|
const shares = parseInt(sharesStr, 10);
|
|
4023
4324
|
if (isNaN(shares) || shares < 2) {
|
|
4024
|
-
console.log(
|
|
4325
|
+
console.log(chalk10.red("Must have at least 2 shares"));
|
|
4025
4326
|
return;
|
|
4026
4327
|
}
|
|
4027
4328
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
4028
4329
|
const threshold = parseInt(thresholdStr, 10);
|
|
4029
4330
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
4030
|
-
console.log(
|
|
4331
|
+
console.log(chalk10.red(`Threshold must be between 1 and ${shares}`));
|
|
4031
4332
|
return;
|
|
4032
4333
|
}
|
|
4033
4334
|
const password = await this.promptPassword("Vault password (optional, press Enter to skip)");
|
|
@@ -4049,43 +4350,43 @@ Error: ${error2.message}`));
|
|
|
4049
4350
|
async importSeedphrase(args) {
|
|
4050
4351
|
const type = args[0]?.toLowerCase();
|
|
4051
4352
|
if (!type || type !== "fast" && type !== "secure") {
|
|
4052
|
-
console.log(
|
|
4053
|
-
console.log(
|
|
4054
|
-
console.log(
|
|
4353
|
+
console.log(chalk10.cyan("Usage: create-from-seedphrase <fast|secure>"));
|
|
4354
|
+
console.log(chalk10.gray(" fast - Import with VultiServer (2-of-2)"));
|
|
4355
|
+
console.log(chalk10.gray(" secure - Import with device coordination (N-of-M)"));
|
|
4055
4356
|
return;
|
|
4056
4357
|
}
|
|
4057
|
-
console.log(
|
|
4358
|
+
console.log(chalk10.cyan("\nEnter your recovery phrase (words separated by spaces):"));
|
|
4058
4359
|
const mnemonic = await this.promptPassword("Seedphrase");
|
|
4059
4360
|
const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
|
|
4060
4361
|
if (!validation.valid) {
|
|
4061
|
-
console.log(
|
|
4362
|
+
console.log(chalk10.red(`Invalid seedphrase: ${validation.error}`));
|
|
4062
4363
|
if (validation.invalidWords?.length) {
|
|
4063
|
-
console.log(
|
|
4364
|
+
console.log(chalk10.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
|
|
4064
4365
|
}
|
|
4065
4366
|
return;
|
|
4066
4367
|
}
|
|
4067
|
-
console.log(
|
|
4368
|
+
console.log(chalk10.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
|
|
4068
4369
|
let vault;
|
|
4069
4370
|
if (type === "fast") {
|
|
4070
4371
|
const name = await this.prompt("Vault name");
|
|
4071
4372
|
if (!name) {
|
|
4072
|
-
console.log(
|
|
4373
|
+
console.log(chalk10.red("Name is required"));
|
|
4073
4374
|
return;
|
|
4074
4375
|
}
|
|
4075
4376
|
const password = await this.promptPassword("Vault password");
|
|
4076
4377
|
if (!password) {
|
|
4077
|
-
console.log(
|
|
4378
|
+
console.log(chalk10.red("Password is required"));
|
|
4078
4379
|
return;
|
|
4079
4380
|
}
|
|
4080
4381
|
const email = await this.prompt("Email for verification");
|
|
4081
4382
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4082
|
-
console.log(
|
|
4383
|
+
console.log(chalk10.red("Valid email is required"));
|
|
4083
4384
|
return;
|
|
4084
4385
|
}
|
|
4085
4386
|
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
4086
4387
|
const discoverChains = discoverStr.toLowerCase() === "y";
|
|
4087
4388
|
vault = await this.withCancellation(
|
|
4088
|
-
(signal) =>
|
|
4389
|
+
(signal) => executeCreateFromSeedphraseFast(this.ctx, {
|
|
4089
4390
|
mnemonic,
|
|
4090
4391
|
name,
|
|
4091
4392
|
password,
|
|
@@ -4097,26 +4398,26 @@ Error: ${error2.message}`));
|
|
|
4097
4398
|
} else {
|
|
4098
4399
|
const name = await this.prompt("Vault name");
|
|
4099
4400
|
if (!name) {
|
|
4100
|
-
console.log(
|
|
4401
|
+
console.log(chalk10.red("Name is required"));
|
|
4101
4402
|
return;
|
|
4102
4403
|
}
|
|
4103
4404
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
4104
4405
|
const shares = parseInt(sharesStr, 10);
|
|
4105
4406
|
if (isNaN(shares) || shares < 2) {
|
|
4106
|
-
console.log(
|
|
4407
|
+
console.log(chalk10.red("Must have at least 2 shares"));
|
|
4107
4408
|
return;
|
|
4108
4409
|
}
|
|
4109
4410
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
4110
4411
|
const threshold = parseInt(thresholdStr, 10);
|
|
4111
4412
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
4112
|
-
console.log(
|
|
4413
|
+
console.log(chalk10.red(`Threshold must be between 1 and ${shares}`));
|
|
4113
4414
|
return;
|
|
4114
4415
|
}
|
|
4115
4416
|
const password = await this.promptPassword("Vault password (optional, Enter to skip)");
|
|
4116
4417
|
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
4117
4418
|
const discoverChains = discoverStr.toLowerCase() === "y";
|
|
4118
4419
|
vault = await this.withCancellation(
|
|
4119
|
-
(signal) =>
|
|
4420
|
+
(signal) => executeCreateFromSeedphraseSecure(this.ctx, {
|
|
4120
4421
|
mnemonic,
|
|
4121
4422
|
name,
|
|
4122
4423
|
password: password || void 0,
|
|
@@ -4133,12 +4434,14 @@ Error: ${error2.message}`));
|
|
|
4133
4434
|
}
|
|
4134
4435
|
}
|
|
4135
4436
|
async runBalance(args) {
|
|
4136
|
-
const chainStr = args
|
|
4437
|
+
const chainStr = args.find((arg) => !arg.startsWith("-"));
|
|
4137
4438
|
const includeTokens = args.includes("-t") || args.includes("--tokens");
|
|
4439
|
+
const raw = args.includes("--raw");
|
|
4138
4440
|
await this.withCancellation(
|
|
4139
4441
|
() => executeBalance(this.ctx, {
|
|
4140
4442
|
chain: chainStr ? findChainByName(chainStr) || chainStr : void 0,
|
|
4141
|
-
includeTokens
|
|
4443
|
+
includeTokens,
|
|
4444
|
+
raw
|
|
4142
4445
|
})
|
|
4143
4446
|
);
|
|
4144
4447
|
}
|
|
@@ -4151,15 +4454,16 @@ Error: ${error2.message}`));
|
|
|
4151
4454
|
}
|
|
4152
4455
|
}
|
|
4153
4456
|
if (!fiatCurrencies3.includes(currency)) {
|
|
4154
|
-
console.log(
|
|
4155
|
-
console.log(
|
|
4457
|
+
console.log(chalk10.red(`Invalid currency: ${currency}`));
|
|
4458
|
+
console.log(chalk10.yellow(`Supported currencies: ${fiatCurrencies3.join(", ")}`));
|
|
4156
4459
|
return;
|
|
4157
4460
|
}
|
|
4158
|
-
|
|
4461
|
+
const raw = args.includes("--raw");
|
|
4462
|
+
await this.withCancellation(() => executePortfolio(this.ctx, { currency, raw }));
|
|
4159
4463
|
}
|
|
4160
4464
|
async runSend(args) {
|
|
4161
4465
|
if (args.length < 3) {
|
|
4162
|
-
console.log(
|
|
4466
|
+
console.log(chalk10.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
|
|
4163
4467
|
return;
|
|
4164
4468
|
}
|
|
4165
4469
|
const [chainStr, to, amount, ...rest] = args;
|
|
@@ -4179,7 +4483,7 @@ Error: ${error2.message}`));
|
|
|
4179
4483
|
await this.withAbortHandler((signal) => executeSend(this.ctx, { chain, to, amount, tokenId, memo, signal }));
|
|
4180
4484
|
} catch (err) {
|
|
4181
4485
|
if (err.message === "Transaction cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
4182
|
-
console.log(
|
|
4486
|
+
console.log(chalk10.yellow("\nTransaction cancelled"));
|
|
4183
4487
|
return;
|
|
4184
4488
|
}
|
|
4185
4489
|
throw err;
|
|
@@ -4188,12 +4492,15 @@ Error: ${error2.message}`));
|
|
|
4188
4492
|
async runChains(args) {
|
|
4189
4493
|
let addChain;
|
|
4190
4494
|
let removeChain;
|
|
4495
|
+
let addAll = false;
|
|
4191
4496
|
for (let i = 0; i < args.length; i++) {
|
|
4192
|
-
if (args[i] === "--add"
|
|
4497
|
+
if (args[i] === "--add-all") {
|
|
4498
|
+
addAll = true;
|
|
4499
|
+
} else if (args[i] === "--add" && i + 1 < args.length) {
|
|
4193
4500
|
const chain = findChainByName(args[i + 1]);
|
|
4194
4501
|
if (!chain) {
|
|
4195
|
-
console.log(
|
|
4196
|
-
console.log(
|
|
4502
|
+
console.log(chalk10.red(`Unknown chain: ${args[i + 1]}`));
|
|
4503
|
+
console.log(chalk10.gray("Use tab completion to see available chains"));
|
|
4197
4504
|
return;
|
|
4198
4505
|
}
|
|
4199
4506
|
addChain = chain;
|
|
@@ -4201,19 +4508,19 @@ Error: ${error2.message}`));
|
|
|
4201
4508
|
} else if (args[i] === "--remove" && i + 1 < args.length) {
|
|
4202
4509
|
const chain = findChainByName(args[i + 1]);
|
|
4203
4510
|
if (!chain) {
|
|
4204
|
-
console.log(
|
|
4205
|
-
console.log(
|
|
4511
|
+
console.log(chalk10.red(`Unknown chain: ${args[i + 1]}`));
|
|
4512
|
+
console.log(chalk10.gray("Use tab completion to see available chains"));
|
|
4206
4513
|
return;
|
|
4207
4514
|
}
|
|
4208
4515
|
removeChain = chain;
|
|
4209
4516
|
i++;
|
|
4210
4517
|
}
|
|
4211
4518
|
}
|
|
4212
|
-
await executeChains(this.ctx, { add: addChain, remove: removeChain });
|
|
4519
|
+
await executeChains(this.ctx, { add: addChain, remove: removeChain, addAll });
|
|
4213
4520
|
}
|
|
4214
4521
|
async runTokens(args) {
|
|
4215
4522
|
if (args.length === 0) {
|
|
4216
|
-
console.log(
|
|
4523
|
+
console.log(chalk10.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
|
|
4217
4524
|
return;
|
|
4218
4525
|
}
|
|
4219
4526
|
const chainStr = args[0];
|
|
@@ -4234,7 +4541,7 @@ Error: ${error2.message}`));
|
|
|
4234
4541
|
async runSwapQuote(args) {
|
|
4235
4542
|
if (args.length < 3) {
|
|
4236
4543
|
console.log(
|
|
4237
|
-
|
|
4544
|
+
chalk10.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
|
|
4238
4545
|
);
|
|
4239
4546
|
return;
|
|
4240
4547
|
}
|
|
@@ -4258,7 +4565,7 @@ Error: ${error2.message}`));
|
|
|
4258
4565
|
async runSwap(args) {
|
|
4259
4566
|
if (args.length < 3) {
|
|
4260
4567
|
console.log(
|
|
4261
|
-
|
|
4568
|
+
chalk10.yellow(
|
|
4262
4569
|
"Usage: swap <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>] [--slippage <pct>]"
|
|
4263
4570
|
)
|
|
4264
4571
|
);
|
|
@@ -4289,7 +4596,7 @@ Error: ${error2.message}`));
|
|
|
4289
4596
|
);
|
|
4290
4597
|
} catch (err) {
|
|
4291
4598
|
if (err.message === "Swap cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
4292
|
-
console.log(
|
|
4599
|
+
console.log(chalk10.yellow("\nSwap cancelled"));
|
|
4293
4600
|
return;
|
|
4294
4601
|
}
|
|
4295
4602
|
throw err;
|
|
@@ -4351,24 +4658,24 @@ Error: ${error2.message}`));
|
|
|
4351
4658
|
}
|
|
4352
4659
|
getPrompt() {
|
|
4353
4660
|
const vault = this.ctx.getActiveVault();
|
|
4354
|
-
if (!vault) return
|
|
4661
|
+
if (!vault) return chalk10.cyan("wallet> ");
|
|
4355
4662
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
4356
|
-
const status = isUnlocked ?
|
|
4357
|
-
return
|
|
4663
|
+
const status = isUnlocked ? chalk10.green("\u{1F513}") : chalk10.yellow("\u{1F512}");
|
|
4664
|
+
return chalk10.cyan(`wallet[${vault.name}]${status}> `);
|
|
4358
4665
|
}
|
|
4359
4666
|
displayVaultList() {
|
|
4360
4667
|
const vaults = Array.from(this.ctx.getVaults().values());
|
|
4361
4668
|
const activeVault = this.ctx.getActiveVault();
|
|
4362
4669
|
if (vaults.length === 0) {
|
|
4363
|
-
console.log(
|
|
4670
|
+
console.log(chalk10.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
|
|
4364
4671
|
return;
|
|
4365
4672
|
}
|
|
4366
|
-
console.log(
|
|
4673
|
+
console.log(chalk10.cyan("Loaded Vaults:\n"));
|
|
4367
4674
|
vaults.forEach((vault) => {
|
|
4368
4675
|
const isActive = vault.id === activeVault?.id;
|
|
4369
4676
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
4370
|
-
const activeMarker = isActive ?
|
|
4371
|
-
const lockIcon = isUnlocked ?
|
|
4677
|
+
const activeMarker = isActive ? chalk10.green(" (active)") : "";
|
|
4678
|
+
const lockIcon = isUnlocked ? chalk10.green("\u{1F513}") : chalk10.yellow("\u{1F512}");
|
|
4372
4679
|
console.log(` ${lockIcon} ${vault.name}${activeMarker} - ${vault.type}`);
|
|
4373
4680
|
});
|
|
4374
4681
|
console.log();
|
|
@@ -4376,10 +4683,10 @@ Error: ${error2.message}`));
|
|
|
4376
4683
|
};
|
|
4377
4684
|
|
|
4378
4685
|
// src/lib/errors.ts
|
|
4379
|
-
import
|
|
4686
|
+
import chalk11 from "chalk";
|
|
4380
4687
|
|
|
4381
4688
|
// src/lib/version.ts
|
|
4382
|
-
import
|
|
4689
|
+
import chalk12 from "chalk";
|
|
4383
4690
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
4384
4691
|
import { homedir } from "os";
|
|
4385
4692
|
import { join } from "path";
|
|
@@ -4387,7 +4694,7 @@ var cachedVersion = null;
|
|
|
4387
4694
|
function getVersion() {
|
|
4388
4695
|
if (cachedVersion) return cachedVersion;
|
|
4389
4696
|
if (true) {
|
|
4390
|
-
cachedVersion = "0.
|
|
4697
|
+
cachedVersion = "0.4.0";
|
|
4391
4698
|
return cachedVersion;
|
|
4392
4699
|
}
|
|
4393
4700
|
try {
|
|
@@ -4484,7 +4791,7 @@ function formatVersionShort() {
|
|
|
4484
4791
|
}
|
|
4485
4792
|
function formatVersionDetailed() {
|
|
4486
4793
|
const lines = [];
|
|
4487
|
-
lines.push(
|
|
4794
|
+
lines.push(chalk12.bold(`Vultisig CLI v${getVersion()}`));
|
|
4488
4795
|
lines.push("");
|
|
4489
4796
|
lines.push(` Node.js: ${process.version}`);
|
|
4490
4797
|
lines.push(` Platform: ${process.platform}-${process.arch}`);
|
|
@@ -4906,7 +5213,7 @@ program.command("import <file>").description("Import vault from .vult file").act
|
|
|
4906
5213
|
await executeImport(context, file);
|
|
4907
5214
|
})
|
|
4908
5215
|
);
|
|
4909
|
-
var
|
|
5216
|
+
var createFromSeedphraseCmd = program.command("create-from-seedphrase").description("Create vault from BIP39 seedphrase");
|
|
4910
5217
|
async function promptSeedphrase() {
|
|
4911
5218
|
info("\nEnter your 12 or 24-word recovery phrase.");
|
|
4912
5219
|
info("Words will be hidden as you type.\n");
|
|
@@ -4927,7 +5234,26 @@ async function promptSeedphrase() {
|
|
|
4927
5234
|
]);
|
|
4928
5235
|
return answer.mnemonic.trim().toLowerCase();
|
|
4929
5236
|
}
|
|
4930
|
-
|
|
5237
|
+
async function promptQrPayload() {
|
|
5238
|
+
info("\nEnter the QR code payload from the initiator device.");
|
|
5239
|
+
info('The payload starts with "vultisig://".\n');
|
|
5240
|
+
const answer = await inquirer8.prompt([
|
|
5241
|
+
{
|
|
5242
|
+
type: "input",
|
|
5243
|
+
name: "qrPayload",
|
|
5244
|
+
message: "QR Payload:",
|
|
5245
|
+
validate: (input) => {
|
|
5246
|
+
const trimmed = input.trim();
|
|
5247
|
+
if (!trimmed.startsWith("vultisig://")) {
|
|
5248
|
+
return 'QR payload must start with "vultisig://"';
|
|
5249
|
+
}
|
|
5250
|
+
return true;
|
|
5251
|
+
}
|
|
5252
|
+
}
|
|
5253
|
+
]);
|
|
5254
|
+
return answer.qrPayload.trim();
|
|
5255
|
+
}
|
|
5256
|
+
createFromSeedphraseCmd.command("fast").description("Create FastVault from seedphrase (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--mnemonic <words>", "Seedphrase (12 or 24 words, space-separated)").option("--discover-chains", "Scan chains for existing balances").option("--chains <chains>", "Specific chains to enable (comma-separated)").option("--use-phantom-solana-path", "Use Phantom wallet derivation path for Solana").action(
|
|
4931
5257
|
withExit(
|
|
4932
5258
|
async (options) => {
|
|
4933
5259
|
const context = await init(program.opts().vault);
|
|
@@ -4948,18 +5274,19 @@ importSeedphraseCmd.command("fast").description("Import as FastVault (server-ass
|
|
|
4948
5274
|
}
|
|
4949
5275
|
}
|
|
4950
5276
|
}
|
|
4951
|
-
await
|
|
5277
|
+
await executeCreateFromSeedphraseFast(context, {
|
|
4952
5278
|
mnemonic,
|
|
4953
5279
|
name: options.name,
|
|
4954
5280
|
password: options.password,
|
|
4955
5281
|
email: options.email,
|
|
4956
5282
|
discoverChains: options.discoverChains,
|
|
4957
|
-
chains
|
|
5283
|
+
chains,
|
|
5284
|
+
usePhantomSolanaPath: options.usePhantomSolanaPath
|
|
4958
5285
|
});
|
|
4959
5286
|
}
|
|
4960
5287
|
)
|
|
4961
5288
|
);
|
|
4962
|
-
|
|
5289
|
+
createFromSeedphraseCmd.command("secure").description("Create SecureVault from seedphrase (multi-device MPC)").requiredOption("--name <name>", "Vault name").option("--password <password>", "Vault password (optional)").option("--threshold <m>", "Signing threshold", "2").option("--shares <n>", "Total shares", "3").option("--mnemonic <words>", "Seedphrase (12 or 24 words)").option("--discover-chains", "Scan chains for existing balances").option("--chains <chains>", "Specific chains to enable (comma-separated)").option("--use-phantom-solana-path", "Use Phantom wallet derivation path for Solana").action(
|
|
4963
5290
|
withExit(
|
|
4964
5291
|
async (options) => {
|
|
4965
5292
|
const context = await init(program.opts().vault);
|
|
@@ -4980,14 +5307,42 @@ importSeedphraseCmd.command("secure").description("Import as SecureVault (multi-
|
|
|
4980
5307
|
}
|
|
4981
5308
|
}
|
|
4982
5309
|
}
|
|
4983
|
-
await
|
|
5310
|
+
await executeCreateFromSeedphraseSecure(context, {
|
|
4984
5311
|
mnemonic,
|
|
4985
5312
|
name: options.name,
|
|
4986
5313
|
password: options.password,
|
|
4987
5314
|
threshold: parseInt(options.threshold, 10),
|
|
4988
5315
|
shares: parseInt(options.shares, 10),
|
|
4989
5316
|
discoverChains: options.discoverChains,
|
|
4990
|
-
chains
|
|
5317
|
+
chains,
|
|
5318
|
+
usePhantomSolanaPath: options.usePhantomSolanaPath
|
|
5319
|
+
});
|
|
5320
|
+
}
|
|
5321
|
+
)
|
|
5322
|
+
);
|
|
5323
|
+
var joinCmd = program.command("join").description("Join an existing vault creation session");
|
|
5324
|
+
joinCmd.command("secure").description("Join a SecureVault creation session").option("--qr <payload>", "QR code payload from initiator (vultisig://...)").option("--qr-file <path>", "Read QR payload from file").option("--mnemonic <words>", "Seedphrase (required for seedphrase-based sessions)").option("--password <password>", "Vault password (optional)").option("--devices <n>", "Total devices in session", "2").action(
|
|
5325
|
+
withExit(
|
|
5326
|
+
async (options) => {
|
|
5327
|
+
const context = await init(program.opts().vault);
|
|
5328
|
+
let qrPayload = options.qr;
|
|
5329
|
+
if (!qrPayload && options.qrFile) {
|
|
5330
|
+
qrPayload = (await fs3.readFile(options.qrFile, "utf-8")).trim();
|
|
5331
|
+
}
|
|
5332
|
+
if (!qrPayload) {
|
|
5333
|
+
qrPayload = await promptQrPayload();
|
|
5334
|
+
}
|
|
5335
|
+
const qrParams = await parseKeygenQR(qrPayload);
|
|
5336
|
+
let mnemonic = options.mnemonic;
|
|
5337
|
+
if (qrParams.libType === "KEYIMPORT" && !mnemonic) {
|
|
5338
|
+
info("\nThis session requires a seedphrase to join.");
|
|
5339
|
+
mnemonic = await promptSeedphrase();
|
|
5340
|
+
}
|
|
5341
|
+
await executeJoinSecure(context, {
|
|
5342
|
+
qrPayload,
|
|
5343
|
+
mnemonic,
|
|
5344
|
+
password: options.password,
|
|
5345
|
+
devices: parseInt(options.devices, 10)
|
|
4991
5346
|
});
|
|
4992
5347
|
}
|
|
4993
5348
|
)
|
|
@@ -5005,12 +5360,13 @@ program.command("verify <vaultId>").description("Verify vault with email verific
|
|
|
5005
5360
|
}
|
|
5006
5361
|
)
|
|
5007
5362
|
);
|
|
5008
|
-
program.command("balance [chain]").description("Show balance for a chain or all chains").option("-t, --tokens", "Include token balances").action(
|
|
5363
|
+
program.command("balance [chain]").description("Show balance for a chain or all chains").option("-t, --tokens", "Include token balances").option("--raw", "Show raw values (wei/satoshis) for programmatic use").action(
|
|
5009
5364
|
withExit(async (chainStr, options) => {
|
|
5010
5365
|
const context = await init(program.opts().vault);
|
|
5011
5366
|
await executeBalance(context, {
|
|
5012
5367
|
chain: chainStr ? findChainByName(chainStr) || chainStr : void 0,
|
|
5013
|
-
includeTokens: options.tokens
|
|
5368
|
+
includeTokens: options.tokens,
|
|
5369
|
+
raw: options.raw
|
|
5014
5370
|
});
|
|
5015
5371
|
})
|
|
5016
5372
|
);
|
|
@@ -5057,10 +5413,13 @@ program.command("broadcast").description("Broadcast a pre-signed raw transaction
|
|
|
5057
5413
|
});
|
|
5058
5414
|
})
|
|
5059
5415
|
);
|
|
5060
|
-
program.command("portfolio").description("Show total portfolio value").option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").action(
|
|
5416
|
+
program.command("portfolio").description("Show total portfolio value").option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").option("--raw", "Show raw values (wei/satoshis) for programmatic use").action(
|
|
5061
5417
|
withExit(async (options) => {
|
|
5062
5418
|
const context = await init(program.opts().vault);
|
|
5063
|
-
await executePortfolio(context, {
|
|
5419
|
+
await executePortfolio(context, {
|
|
5420
|
+
currency: options.currency.toLowerCase(),
|
|
5421
|
+
raw: options.raw
|
|
5422
|
+
});
|
|
5064
5423
|
})
|
|
5065
5424
|
);
|
|
5066
5425
|
program.command("currency [newCurrency]").description("View or set the vault currency preference").action(
|
|
@@ -5075,6 +5434,12 @@ program.command("server").description("Check server connectivity and status").ac
|
|
|
5075
5434
|
await executeServer(context);
|
|
5076
5435
|
})
|
|
5077
5436
|
);
|
|
5437
|
+
program.command("discount").description("Show your VULT discount tier for swap fees").option("--refresh", "Force refresh tier from blockchain").action(
|
|
5438
|
+
withExit(async (options) => {
|
|
5439
|
+
const context = await init(program.opts().vault);
|
|
5440
|
+
await executeDiscount(context, { refresh: options.refresh });
|
|
5441
|
+
})
|
|
5442
|
+
);
|
|
5078
5443
|
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(
|
|
5079
5444
|
withExit(async (path3, options) => {
|
|
5080
5445
|
const context = await init(program.opts().vault, options.password);
|
|
@@ -5103,11 +5468,12 @@ program.command("address-book").description("Manage address book entries").optio
|
|
|
5103
5468
|
});
|
|
5104
5469
|
})
|
|
5105
5470
|
);
|
|
5106
|
-
program.command("chains").description("List and manage chains").option("--add <chain>", "Add a chain").option("--remove <chain>", "Remove a chain").action(
|
|
5471
|
+
program.command("chains").description("List and manage chains").option("--add <chain>", "Add a chain").option("--add-all", "Add all supported chains").option("--remove <chain>", "Remove a chain").action(
|
|
5107
5472
|
withExit(async (options) => {
|
|
5108
5473
|
const context = await init(program.opts().vault);
|
|
5109
5474
|
await executeChains(context, {
|
|
5110
5475
|
add: options.add ? findChainByName(options.add) || options.add : void 0,
|
|
5476
|
+
addAll: options.addAll,
|
|
5111
5477
|
remove: options.remove ? findChainByName(options.remove) || options.remove : void 0
|
|
5112
5478
|
});
|
|
5113
5479
|
})
|
|
@@ -5136,6 +5502,15 @@ program.command("info").description("Show detailed vault information").action(
|
|
|
5136
5502
|
await executeInfo(context);
|
|
5137
5503
|
})
|
|
5138
5504
|
);
|
|
5505
|
+
program.command("delete [vault]").description("Delete a vault from local storage").option("-y, --yes", "Skip confirmation prompt").action(
|
|
5506
|
+
withExit(async (vaultIdOrName, options) => {
|
|
5507
|
+
const context = await init(program.opts().vault);
|
|
5508
|
+
await executeDelete(context, {
|
|
5509
|
+
vaultId: vaultIdOrName,
|
|
5510
|
+
skipConfirmation: options.yes
|
|
5511
|
+
});
|
|
5512
|
+
})
|
|
5513
|
+
);
|
|
5139
5514
|
program.command("tokens <chain>").description("List and manage tokens for a chain").option("--add <contractAddress>", "Add a token by contract address").option("--remove <tokenId>", "Remove a token by ID").option("--symbol <symbol>", "Token symbol (for --add)").option("--name <name>", "Token name (for --add)").option("--decimals <decimals>", "Token decimals (for --add)", "18").action(
|
|
5140
5515
|
withExit(
|
|
5141
5516
|
async (chainStr, options) => {
|
|
@@ -5202,8 +5577,8 @@ program.command("version").description("Show detailed version information").acti
|
|
|
5202
5577
|
const result = await checkForUpdates();
|
|
5203
5578
|
if (result?.updateAvailable && result.latestVersion) {
|
|
5204
5579
|
info("");
|
|
5205
|
-
info(
|
|
5206
|
-
info(
|
|
5580
|
+
info(chalk13.yellow(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
|
|
5581
|
+
info(chalk13.gray(`Run "${getUpdateCommand()}" to update`));
|
|
5207
5582
|
}
|
|
5208
5583
|
})
|
|
5209
5584
|
);
|
|
@@ -5212,22 +5587,22 @@ program.command("update").description("Check for updates and show update command
|
|
|
5212
5587
|
info("Checking for updates...");
|
|
5213
5588
|
const result = await checkForUpdates();
|
|
5214
5589
|
if (!result) {
|
|
5215
|
-
printResult(
|
|
5590
|
+
printResult(chalk13.gray("Update checking is disabled"));
|
|
5216
5591
|
return;
|
|
5217
5592
|
}
|
|
5218
5593
|
if (result.updateAvailable && result.latestVersion) {
|
|
5219
5594
|
printResult("");
|
|
5220
|
-
printResult(
|
|
5595
|
+
printResult(chalk13.green(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
|
|
5221
5596
|
printResult("");
|
|
5222
5597
|
if (options.check) {
|
|
5223
5598
|
printResult(`Run "${getUpdateCommand()}" to update`);
|
|
5224
5599
|
} else {
|
|
5225
5600
|
const updateCmd = getUpdateCommand();
|
|
5226
5601
|
printResult(`To update, run:`);
|
|
5227
|
-
printResult(
|
|
5602
|
+
printResult(chalk13.cyan(` ${updateCmd}`));
|
|
5228
5603
|
}
|
|
5229
5604
|
} else {
|
|
5230
|
-
printResult(
|
|
5605
|
+
printResult(chalk13.green(`You're on the latest version (${result.currentVersion})`));
|
|
5231
5606
|
}
|
|
5232
5607
|
})
|
|
5233
5608
|
);
|