@vultisig/cli 0.1.0-alpha.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +6 -0
  2. package/dist/index.js +3185 -361
  3. package/package.json +4 -3
  4. package/dist/adapters/cli-context.js +0 -25
  5. package/dist/adapters/cli-context.js.map +0 -1
  6. package/dist/adapters/cli-runner.js +0 -47
  7. package/dist/adapters/cli-runner.js.map +0 -1
  8. package/dist/adapters/index.js +0 -6
  9. package/dist/adapters/index.js.map +0 -1
  10. package/dist/commands/balance.js +0 -65
  11. package/dist/commands/balance.js.map +0 -1
  12. package/dist/commands/chains.js +0 -46
  13. package/dist/commands/chains.js.map +0 -1
  14. package/dist/commands/index.js +0 -12
  15. package/dist/commands/index.js.map +0 -1
  16. package/dist/commands/settings.js +0 -151
  17. package/dist/commands/settings.js.map +0 -1
  18. package/dist/commands/swap.js +0 -180
  19. package/dist/commands/swap.js.map +0 -1
  20. package/dist/commands/tokens.js +0 -116
  21. package/dist/commands/tokens.js.map +0 -1
  22. package/dist/commands/transaction.js +0 -99
  23. package/dist/commands/transaction.js.map +0 -1
  24. package/dist/commands/vault-management.js +0 -360
  25. package/dist/commands/vault-management.js.map +0 -1
  26. package/dist/core/command-context.js +0 -81
  27. package/dist/core/command-context.js.map +0 -1
  28. package/dist/core/index.js +0 -7
  29. package/dist/core/index.js.map +0 -1
  30. package/dist/core/password-manager.js +0 -92
  31. package/dist/core/password-manager.js.map +0 -1
  32. package/dist/core/types.js +0 -2
  33. package/dist/core/types.js.map +0 -1
  34. package/dist/index.js.map +0 -1
  35. package/dist/interactive/completer.js +0 -170
  36. package/dist/interactive/completer.js.map +0 -1
  37. package/dist/interactive/event-buffer.js +0 -186
  38. package/dist/interactive/event-buffer.js.map +0 -1
  39. package/dist/interactive/index.js +0 -9
  40. package/dist/interactive/index.js.map +0 -1
  41. package/dist/interactive/session.js +0 -525
  42. package/dist/interactive/session.js.map +0 -1
  43. package/dist/interactive/shell-commands.js +0 -167
  44. package/dist/interactive/shell-commands.js.map +0 -1
  45. package/dist/interactive/shell-context.js +0 -112
  46. package/dist/interactive/shell-context.js.map +0 -1
  47. package/dist/lib/completion.js +0 -375
  48. package/dist/lib/completion.js.map +0 -1
  49. package/dist/lib/config.js +0 -172
  50. package/dist/lib/config.js.map +0 -1
  51. package/dist/lib/errors.js +0 -163
  52. package/dist/lib/errors.js.map +0 -1
  53. package/dist/lib/index.js +0 -9
  54. package/dist/lib/index.js.map +0 -1
  55. package/dist/lib/output.js +0 -155
  56. package/dist/lib/output.js.map +0 -1
  57. package/dist/lib/version.js +0 -210
  58. package/dist/lib/version.js.map +0 -1
  59. package/dist/ui.js +0 -286
  60. package/dist/ui.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,440 +1,3264 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
3
- import { Vultisig } from '@vultisig/sdk';
4
- import chalk from 'chalk';
5
- import { program } from 'commander';
6
- import { CLIContext, withExit } from './adapters';
7
- import { executeAddressBook, executeAddresses, executeBalance, executeChains, executeCreate, executeCurrency, executeExport, executeImport, executeInfo, executePortfolio, executeRename, executeSend, executeServer, executeSwap, executeSwapChains, executeSwapQuote, executeSwitch, executeTokens, executeVaults, executeVerify, } from './commands';
8
- import { createPasswordCallback } from './core';
9
- import { findChainByName } from './interactive';
10
- import { ShellSession } from './interactive';
11
- import { checkForUpdates, error, formatVersionDetailed, formatVersionShort, getUpdateCommand, handleCompletion, info, initOutputMode, printResult, setupCompletionCommand, warn, } from './lib';
12
- import { setupVaultEvents } from './ui';
13
- (async () => {
14
- const handled = await handleCompletion();
15
- if (handled)
16
- process.exit(0);
17
- })();
18
- // ============================================================================
19
- // Global State
20
- // ============================================================================
21
- let ctx;
22
- // ============================================================================
23
- // Program Configuration
24
- // ============================================================================
25
- program
26
- .name('vultisig')
27
- .description('Vultisig CLI - Secure multi-party crypto wallet')
28
- .version(formatVersionShort(), '-v, --version', 'Show version')
29
- .option('--debug', 'Enable debug output')
30
- .option('--silent', 'Suppress informational output, show only results')
31
- .option('-o, --output <format>', 'Output format: table, json (default: table)', 'table')
32
- .option('-i, --interactive', 'Start interactive shell mode')
33
- .option('--vault <nameOrId>', 'Specify vault by name or ID')
34
- .hook('preAction', thisCommand => {
35
- const opts = thisCommand.opts();
36
- initOutputMode({ silent: opts.silent, output: opts.output });
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
37
7
  });
38
- // ============================================================================
39
- // SDK Initialization
40
- // ============================================================================
41
- /**
42
- * Find a vault by name or ID
43
- * Tries exact ID match, then case-insensitive name match, then partial ID prefix match
44
- */
45
- async function findVaultByNameOrId(sdk, nameOrId) {
46
- const vaults = await sdk.listVaults();
47
- // Try exact ID match first
48
- const byId = vaults.find(v => v.id === nameOrId);
49
- if (byId)
50
- return byId;
51
- // Try name match (case-insensitive)
52
- const byName = vaults.find(v => v.name.toLowerCase() === nameOrId.toLowerCase());
53
- if (byName)
54
- return byName;
55
- // Try partial ID match (prefix)
56
- const byPartialId = vaults.find(v => v.id.startsWith(nameOrId));
57
- if (byPartialId)
58
- return byPartialId;
8
+
9
+ // src/index.ts
10
+ import "dotenv/config";
11
+ import { Vultisig as Vultisig3 } from "@vultisig/sdk";
12
+ import chalk12 from "chalk";
13
+ import { program } from "commander";
14
+
15
+ // src/core/command-context.ts
16
+ var DEFAULT_PASSWORD_CACHE_TTL = 5 * 60 * 1e3;
17
+ var BaseCommandContext = class {
18
+ _sdk;
19
+ _activeVault = null;
20
+ passwordCache = /* @__PURE__ */ new Map();
21
+ defaultPasswordTtl;
22
+ constructor(sdk, options) {
23
+ this._sdk = sdk;
24
+ this.defaultPasswordTtl = options?.passwordTtlMs ?? DEFAULT_PASSWORD_CACHE_TTL;
25
+ }
26
+ get sdk() {
27
+ return this._sdk;
28
+ }
29
+ getActiveVault() {
30
+ return this._activeVault;
31
+ }
32
+ async setActiveVault(vault) {
33
+ this._activeVault = vault;
34
+ await this._sdk.setActiveVault(vault);
35
+ }
36
+ async ensureActiveVault() {
37
+ if (!this._activeVault) {
38
+ const vault = await this._sdk.getActiveVault();
39
+ if (vault) {
40
+ this._activeVault = vault;
41
+ }
42
+ }
43
+ if (!this._activeVault) {
44
+ throw new Error("No active vault. Create or import a vault first.");
45
+ }
46
+ return this._activeVault;
47
+ }
48
+ cachePassword(vaultId, password, ttlMs) {
49
+ const ttl = ttlMs ?? this.defaultPasswordTtl;
50
+ this.passwordCache.set(vaultId, {
51
+ password,
52
+ expiresAt: Date.now() + ttl
53
+ });
54
+ }
55
+ clearPasswordCache(vaultId) {
56
+ if (vaultId) {
57
+ this.passwordCache.delete(vaultId);
58
+ } else {
59
+ this.passwordCache.clear();
60
+ }
61
+ }
62
+ isPasswordCached(vaultId) {
63
+ const entry = this.passwordCache.get(vaultId);
64
+ if (!entry) return false;
65
+ if (Date.now() > entry.expiresAt) {
66
+ this.passwordCache.delete(vaultId);
67
+ return false;
68
+ }
69
+ return true;
70
+ }
71
+ getCachedPassword(vaultId) {
72
+ const entry = this.passwordCache.get(vaultId);
73
+ if (!entry) return null;
74
+ if (Date.now() > entry.expiresAt) {
75
+ this.passwordCache.delete(vaultId);
76
+ return null;
77
+ }
78
+ return entry.password;
79
+ }
80
+ dispose() {
81
+ this.passwordCache.clear();
82
+ this._sdk.dispose();
83
+ }
84
+ };
85
+
86
+ // src/core/password-manager.ts
87
+ import inquirer from "inquirer";
88
+
89
+ // src/lib/output.ts
90
+ import chalk from "chalk";
91
+ import ora from "ora";
92
+ var silentMode = false;
93
+ var outputFormat = "table";
94
+ function isSilent() {
95
+ return silentMode;
96
+ }
97
+ function initOutputMode(options) {
98
+ outputFormat = options.output ?? "table";
99
+ silentMode = options.silent ?? process.env.VULTISIG_SILENT === "1";
100
+ if (outputFormat === "json") {
101
+ silentMode = true;
102
+ }
103
+ }
104
+ function isJsonOutput() {
105
+ return outputFormat === "json";
106
+ }
107
+ function bigIntReplacer(_key, value) {
108
+ return typeof value === "bigint" ? value.toString() : value;
109
+ }
110
+ function outputJson(data) {
111
+ console.log(JSON.stringify({ success: true, data }, bigIntReplacer, 2));
112
+ }
113
+ function outputJsonError(message, code) {
114
+ console.log(JSON.stringify({ success: false, error: { message, code } }, bigIntReplacer, 2));
115
+ }
116
+ function info(message) {
117
+ if (!silentMode) {
118
+ console.log(chalk.blue(message));
119
+ }
120
+ }
121
+ function success(message) {
122
+ if (!silentMode) {
123
+ console.log(chalk.green(`
124
+ ${message}`));
125
+ }
126
+ }
127
+ function warn(message) {
128
+ if (!silentMode) {
129
+ console.log(chalk.yellow(message));
130
+ }
131
+ }
132
+ function error(message) {
133
+ console.error(chalk.red(`
134
+ ${message}`));
135
+ }
136
+ function printResult(message) {
137
+ console.log(message);
138
+ }
139
+ function printTable(data) {
140
+ console.table(data);
141
+ }
142
+ function printError(message) {
143
+ console.error(chalk.red(message));
144
+ }
145
+ function createNoopSpinner(text) {
146
+ const noopSpinner = {
147
+ text,
148
+ start() {
149
+ return this;
150
+ },
151
+ stop() {
152
+ return this;
153
+ },
154
+ succeed() {
155
+ return this;
156
+ },
157
+ fail(text2) {
158
+ if (text2) printError(text2);
159
+ return this;
160
+ },
161
+ warn() {
162
+ return this;
163
+ },
164
+ info() {
165
+ return this;
166
+ }
167
+ };
168
+ return noopSpinner;
169
+ }
170
+ function createSpinner(text) {
171
+ if (silentMode) {
172
+ return createNoopSpinner(text);
173
+ }
174
+ return ora(text).start();
175
+ }
176
+
177
+ // src/core/password-manager.ts
178
+ function parseVaultPasswords() {
179
+ const passwordMap = /* @__PURE__ */ new Map();
180
+ const passwordsEnv = process.env.VAULT_PASSWORDS;
181
+ if (passwordsEnv) {
182
+ const pairs = passwordsEnv.trim().split(/\s+/);
183
+ for (const pair of pairs) {
184
+ const colonIndex = pair.indexOf(":");
185
+ if (colonIndex > 0) {
186
+ const vaultKey = pair.substring(0, colonIndex);
187
+ const password = pair.substring(colonIndex + 1);
188
+ passwordMap.set(vaultKey, password);
189
+ }
190
+ }
191
+ }
192
+ return passwordMap;
193
+ }
194
+ function getPasswordFromEnv(vaultId, vaultName) {
195
+ const vaultPasswords = parseVaultPasswords();
196
+ if (vaultName && vaultPasswords.has(vaultName)) {
197
+ return vaultPasswords.get(vaultName);
198
+ }
199
+ if (vaultPasswords.has(vaultId)) {
200
+ return vaultPasswords.get(vaultId);
201
+ }
202
+ if (process.env.VAULT_PASSWORD) {
203
+ return process.env.VAULT_PASSWORD;
204
+ }
205
+ return null;
206
+ }
207
+ async function promptForPassword(vaultName, vaultId) {
208
+ const displayName = vaultName || vaultId || "vault";
209
+ const { password } = await inquirer.prompt([
210
+ {
211
+ type: "password",
212
+ name: "password",
213
+ message: `Enter password for vault "${displayName}":`,
214
+ mask: "*"
215
+ }
216
+ ]);
217
+ return password;
218
+ }
219
+ async function getPassword(vaultId, vaultName) {
220
+ const envPassword = getPasswordFromEnv(vaultId, vaultName);
221
+ if (envPassword) {
222
+ return envPassword;
223
+ }
224
+ if (isSilent() || isJsonOutput()) {
225
+ throw new Error("Password required but not provided. Set VAULT_PASSWORD or VAULT_PASSWORDS environment variable.");
226
+ }
227
+ return promptForPassword(vaultName, vaultId);
228
+ }
229
+ function createPasswordCallback() {
230
+ return async (vaultId, vaultName) => {
231
+ return getPassword(vaultId, vaultName);
232
+ };
233
+ }
234
+
235
+ // src/adapters/cli-context.ts
236
+ var CLIContext = class extends BaseCommandContext {
237
+ get isInteractive() {
238
+ return false;
239
+ }
240
+ /**
241
+ * Get password for a vault
242
+ * In CLI mode, we check env vars first, then prompt
243
+ * No caching since each command runs independently
244
+ */
245
+ async getPassword(vaultId, vaultName) {
246
+ return getPassword(vaultId, vaultName);
247
+ }
248
+ };
249
+
250
+ // src/adapters/cli-runner.ts
251
+ function withExit(handler) {
252
+ return async (...args) => {
253
+ try {
254
+ await handler(...args);
255
+ process.exit(0);
256
+ } catch (err) {
257
+ const exitCode = err.exitCode ?? 1;
258
+ if (isJsonOutput()) {
259
+ outputJsonError(err.message, err.code ?? "GENERAL_ERROR");
260
+ process.exit(exitCode);
261
+ }
262
+ if (err.exitCode !== void 0) {
263
+ process.exit(err.exitCode);
264
+ }
265
+ printError(`
266
+ x ${err.message}`);
267
+ process.exit(1);
268
+ }
269
+ };
270
+ }
271
+
272
+ // src/commands/balance.ts
273
+ import { fiatCurrencies, fiatCurrencyNameRecord as fiatCurrencyNameRecord2 } from "@vultisig/sdk";
274
+
275
+ // src/ui.ts
276
+ import { fiatCurrencyNameRecord, Vultisig } from "@vultisig/sdk";
277
+ import chalk2 from "chalk";
278
+ import inquirer2 from "inquirer";
279
+ function displayBalance(chain, balance) {
280
+ printResult(chalk2.cyan(`
281
+ ${chain} Balance:`));
282
+ printResult(` Amount: ${balance.amount} ${balance.symbol}`);
283
+ if (balance.fiatValue && balance.fiatCurrency) {
284
+ printResult(` Value: ${balance.fiatValue.toFixed(2)} ${balance.fiatCurrency}`);
285
+ }
286
+ }
287
+ function displayBalancesTable(balances) {
288
+ printResult(chalk2.cyan("\nPortfolio Balances:\n"));
289
+ const tableData = Object.entries(balances).map(([chain, balance]) => ({
290
+ Chain: chain,
291
+ Amount: balance.amount,
292
+ Symbol: balance.symbol,
293
+ Value: balance.fiatValue && balance.fiatCurrency ? `${balance.fiatValue.toFixed(2)} ${balance.fiatCurrency}` : "N/A"
294
+ }));
295
+ printTable(tableData);
296
+ }
297
+ function displayPortfolio(portfolio, currency) {
298
+ const currencyName = fiatCurrencyNameRecord[currency];
299
+ printResult(chalk2.cyan("\n+----------------------------------------+"));
300
+ printResult(chalk2.cyan(`| Portfolio Total Value (${currencyName}) |`));
301
+ printResult(chalk2.cyan("+----------------------------------------+"));
302
+ const totalDisplay = portfolio.totalValue.amount.padEnd(20) + portfolio.totalValue.currency.toUpperCase().padStart(16);
303
+ printResult(chalk2.cyan("| ") + chalk2.bold.green(totalDisplay) + chalk2.cyan(" |"));
304
+ printResult(chalk2.cyan("+----------------------------------------+\n"));
305
+ printResult(chalk2.bold("Chain Breakdown:\n"));
306
+ const table = portfolio.chainBalances.map(({ chain, balance, value }) => ({
307
+ Chain: chain,
308
+ Amount: balance.amount,
309
+ Symbol: balance.symbol,
310
+ Value: value ? `${value.amount} ${value.currency.toUpperCase()}` : "N/A"
311
+ }));
312
+ printTable(table);
313
+ }
314
+ function displayAddresses(addresses) {
315
+ printResult(chalk2.cyan("\nVault Addresses:\n"));
316
+ const table = Object.entries(addresses).map(([chain, address]) => ({
317
+ Chain: chain,
318
+ Address: address
319
+ }));
320
+ printTable(table);
321
+ }
322
+ function displayVaultInfo(vault) {
323
+ printResult(chalk2.cyan("\n+----------------------------------------+"));
324
+ printResult(chalk2.cyan("| Vault Information |"));
325
+ printResult(chalk2.cyan("+----------------------------------------+\n"));
326
+ printResult(chalk2.bold("Basic Information:"));
327
+ printResult(` Name: ${chalk2.green(vault.name)}`);
328
+ printResult(` ID: ${vault.id}`);
329
+ printResult(` Type: ${chalk2.yellow(vault.type)}`);
330
+ printResult(` Created: ${new Date(vault.createdAt).toLocaleString()}`);
331
+ printResult(` Last Modified: ${new Date(vault.lastModified).toLocaleString()}`);
332
+ printResult(chalk2.bold("\nSecurity:"));
333
+ printResult(` Encrypted: ${vault.isEncrypted ? chalk2.green("Yes") : chalk2.gray("No")}`);
334
+ printResult(` Backed Up: ${vault.isBackedUp ? chalk2.green("Yes") : chalk2.yellow("No")}`);
335
+ printResult(chalk2.bold("\nMPC Configuration:"));
336
+ printResult(` Library Type: ${vault.libType}`);
337
+ printResult(` Threshold: ${chalk2.cyan(vault.threshold)} of ${chalk2.cyan(vault.totalSigners)}`);
338
+ printResult(` Local Party: ${vault.localPartyId}`);
339
+ printResult(` Total Signers: ${vault.totalSigners}`);
340
+ const modes = vault.availableSigningModes;
341
+ printResult(chalk2.bold("\nSigning Modes:"));
342
+ modes.forEach((mode) => {
343
+ printResult(` - ${mode}`);
344
+ });
345
+ const chains = vault.chains;
346
+ printResult(chalk2.bold("\nChains:"));
347
+ printResult(` Total: ${chains.length}`);
348
+ chains.forEach((chain) => {
349
+ printResult(` - ${chain}`);
350
+ });
351
+ printResult(chalk2.bold("\nPreferences:"));
352
+ printResult(` Currency: ${vault.currency.toUpperCase()}`);
353
+ printResult(chalk2.bold("\nPublic Keys:"));
354
+ printResult(` ECDSA: ${vault.publicKeys.ecdsa.substring(0, 20)}...`);
355
+ printResult(` EdDSA: ${vault.publicKeys.eddsa.substring(0, 20)}...`);
356
+ printResult(` Chain Code: ${vault.hexChainCode.substring(0, 20)}...
357
+ `);
358
+ }
359
+ function displayTransactionPreview(fromAddress, toAddress, amount, symbol, chain, memo, gas) {
360
+ if (gas) {
361
+ const bigIntReplacer2 = (_k, v) => typeof v === "bigint" ? v.toString() : v;
362
+ info(chalk2.blue(`
363
+ Estimated gas: ${JSON.stringify(gas, bigIntReplacer2, 2)}`));
364
+ }
365
+ printResult(chalk2.cyan("\nTransaction Preview:"));
366
+ printResult(` From: ${fromAddress}`);
367
+ printResult(` To: ${toAddress}`);
368
+ printResult(` Amount: ${amount} ${symbol}`);
369
+ printResult(` Chain: ${chain}`);
370
+ if (memo) {
371
+ printResult(` Memo: ${memo}`);
372
+ }
373
+ }
374
+ function displayTransactionResult(chain, txHash) {
375
+ const explorerUrl = Vultisig.getTxExplorerUrl(chain, txHash);
376
+ printResult(txHash);
377
+ printResult(explorerUrl);
378
+ }
379
+ function displayVaultsList(vaults, activeVault) {
380
+ printResult(chalk2.cyan("\nStored Vaults:\n"));
381
+ const table = vaults.map((vault) => ({
382
+ ID: vault.id,
383
+ Name: vault.name === activeVault?.name ? chalk2.green(`${vault.name} (active)`) : vault.name,
384
+ Type: vault.type,
385
+ Chains: vault.chains.length,
386
+ Created: new Date(vault.createdAt).toLocaleDateString()
387
+ }));
388
+ printTable(table);
389
+ }
390
+ async function confirmTransaction() {
391
+ const { confirmed } = await inquirer2.prompt([
392
+ {
393
+ type: "confirm",
394
+ name: "confirmed",
395
+ message: "Proceed with this transaction?",
396
+ default: false
397
+ }
398
+ ]);
399
+ return confirmed;
400
+ }
401
+ function setupVaultEvents(vault) {
402
+ vault.on("balanceUpdated", ({ chain, balance, tokenId }) => {
403
+ const asset = tokenId ? `${balance.symbol} token` : balance.symbol;
404
+ info(chalk2.blue(`i Balance updated for ${chain} (${asset}): ${balance.amount}`));
405
+ });
406
+ vault.on("transactionBroadcast", ({ chain, txHash }) => {
407
+ info(chalk2.green(`+ Transaction broadcast on ${chain}`));
408
+ info(chalk2.blue(` TX Hash: ${txHash}`));
409
+ });
410
+ vault.on("chainAdded", ({ chain }) => {
411
+ info(chalk2.green(`+ Chain added: ${chain}`));
412
+ });
413
+ vault.on("chainRemoved", ({ chain }) => {
414
+ warn(chalk2.yellow(`i Chain removed: ${chain}`));
415
+ });
416
+ vault.on("tokenAdded", ({ chain, token }) => {
417
+ info(chalk2.green(`+ Token added: ${token.symbol} on ${chain}`));
418
+ });
419
+ vault.on("valuesUpdated", ({ chain }) => {
420
+ if (chain === "all") {
421
+ info(chalk2.blue("i Portfolio values updated"));
422
+ } else {
423
+ info(chalk2.blue(`i Values updated for ${chain}`));
424
+ }
425
+ });
426
+ if (!isJsonOutput()) {
427
+ vault.on("error", (err) => {
428
+ printError(chalk2.red(`x Vault error: ${err.message}`));
429
+ });
430
+ }
431
+ }
432
+ function formatBigintAmount(amount, decimals) {
433
+ if (amount === 0n) return "0";
434
+ const divisor = BigInt(10 ** decimals);
435
+ const whole = amount / divisor;
436
+ const fraction = amount % divisor;
437
+ if (fraction === 0n) {
438
+ return whole.toString();
439
+ }
440
+ const fractionStr = fraction.toString().padStart(decimals, "0");
441
+ const trimmed = fractionStr.replace(/0+$/, "");
442
+ return `${whole}.${trimmed}`;
443
+ }
444
+ function displaySwapPreview(quote, fromAmount, fromSymbol, toSymbol, options) {
445
+ const estimatedOutputFormatted = formatBigintAmount(quote.estimatedOutput, options.toDecimals);
446
+ printResult(chalk2.cyan("\nSwap Preview:"));
447
+ printResult(` From: ${fromAmount} ${fromSymbol}`);
448
+ printResult(` To: ${estimatedOutputFormatted} ${toSymbol}`);
449
+ if (quote.estimatedOutputFiat !== void 0) {
450
+ printResult(` (~$${quote.estimatedOutputFiat.toFixed(2)})`);
451
+ }
452
+ printResult(` Provider: ${quote.provider}`);
453
+ if (quote.fees) {
454
+ const networkFeeFormatted = formatBigintAmount(quote.fees.network, options.feeDecimals);
455
+ const totalFeeFormatted = formatBigintAmount(quote.fees.total, options.feeDecimals);
456
+ printResult(chalk2.bold("\n Fees:"));
457
+ printResult(` Network: ${networkFeeFormatted} ${options.feeSymbol}`);
458
+ if (quote.feesFiat) {
459
+ printResult(` (~$${quote.feesFiat.network.toFixed(2)})`);
460
+ }
461
+ if (quote.fees.affiliate && quote.fees.affiliate > 0n) {
462
+ const affiliateFeeFormatted = formatBigintAmount(quote.fees.affiliate, options.feeDecimals);
463
+ printResult(` Affiliate: ${affiliateFeeFormatted} ${options.feeSymbol}`);
464
+ if (quote.feesFiat?.affiliate) {
465
+ printResult(` (~$${quote.feesFiat.affiliate.toFixed(2)})`);
466
+ }
467
+ }
468
+ printResult(` Total: ${totalFeeFormatted} ${options.feeSymbol}`);
469
+ if (quote.feesFiat) {
470
+ printResult(` (~$${quote.feesFiat.total.toFixed(2)})`);
471
+ }
472
+ }
473
+ if (quote.warnings && quote.warnings.length > 0) {
474
+ printResult(chalk2.yellow("\n Warnings:"));
475
+ quote.warnings.forEach((warning) => {
476
+ printResult(chalk2.yellow(` - ${warning}`));
477
+ });
478
+ }
479
+ if (quote.requiresApproval) {
480
+ printResult(chalk2.yellow("\n Token approval required before swap"));
481
+ if (quote.approvalInfo) {
482
+ printResult(` Spender: ${quote.approvalInfo.spender}`);
483
+ }
484
+ }
485
+ const expiresIn = Math.max(0, Math.floor((quote.expiresAt - Date.now()) / 1e3));
486
+ info(chalk2.gray(`
487
+ Quote expires in ${expiresIn}s`));
488
+ }
489
+ function displaySwapResult(fromChain, _toChain, txHash, quote, toDecimals) {
490
+ const explorerUrl = Vultisig.getTxExplorerUrl(fromChain, txHash);
491
+ const estimatedOutputFormatted = formatBigintAmount(quote.estimatedOutput, toDecimals);
492
+ printResult(txHash);
493
+ printResult(explorerUrl);
494
+ printResult(estimatedOutputFormatted);
495
+ }
496
+ function displaySwapChains(chains) {
497
+ printResult(chalk2.cyan("\nSupported Swap Chains:\n"));
498
+ const table = chains.map((chain) => ({
499
+ Chain: chain
500
+ }));
501
+ printTable(table);
502
+ printResult(`
503
+ Total: ${chains.length} chains`);
504
+ }
505
+ async function confirmSwap() {
506
+ const { confirmed } = await inquirer2.prompt([
507
+ {
508
+ type: "confirm",
509
+ name: "confirmed",
510
+ message: "Proceed with this swap?",
511
+ default: false
512
+ }
513
+ ]);
514
+ return confirmed;
515
+ }
516
+
517
+ // src/commands/balance.ts
518
+ async function executeBalance(ctx2, options = {}) {
519
+ const vault = await ctx2.ensureActiveVault();
520
+ const spinner = createSpinner("Loading balances...");
521
+ if (options.chain) {
522
+ const balance = await vault.balance(options.chain);
523
+ spinner.succeed("Balance loaded");
524
+ if (isJsonOutput()) {
525
+ outputJson({ chain: options.chain, balance });
526
+ return;
527
+ }
528
+ displayBalance(options.chain, balance);
529
+ } else {
530
+ const balances = await vault.balances(void 0, options.includeTokens);
531
+ spinner.succeed("Balances loaded");
532
+ if (isJsonOutput()) {
533
+ outputJson({ balances });
534
+ return;
535
+ }
536
+ displayBalancesTable(balances);
537
+ }
538
+ }
539
+ async function executePortfolio(ctx2, options = {}) {
540
+ const vault = await ctx2.ensureActiveVault();
541
+ const currency = options.currency || "usd";
542
+ if (!fiatCurrencies.includes(currency)) {
543
+ error(`x Invalid currency: ${currency}`);
544
+ warn(`Supported currencies: ${fiatCurrencies.join(", ")}`);
545
+ throw new Error("Invalid currency");
546
+ }
547
+ if (vault.currency !== currency) {
548
+ await vault.setCurrency(currency);
549
+ }
550
+ const currencyName = fiatCurrencyNameRecord2[currency];
551
+ const spinner = createSpinner(`Loading portfolio in ${currencyName}...`);
552
+ const totalValue = await vault.getTotalValue(currency);
553
+ const chains = vault.chains;
554
+ const chainBalances = await Promise.all(
555
+ chains.map(async (chain) => {
556
+ const balance = await vault.balance(chain);
557
+ try {
558
+ const value = await vault.getValue(chain, void 0, currency);
559
+ return { chain, balance, value };
560
+ } catch {
561
+ return { chain, balance };
562
+ }
563
+ })
564
+ );
565
+ const portfolio = { totalValue, chainBalances };
566
+ spinner.succeed("Portfolio loaded");
567
+ if (isJsonOutput()) {
568
+ outputJson({ portfolio, currency });
569
+ return;
570
+ }
571
+ displayPortfolio(portfolio, currency);
572
+ }
573
+
574
+ // src/commands/chains.ts
575
+ import chalk3 from "chalk";
576
+ async function executeChains(ctx2, options = {}) {
577
+ const vault = await ctx2.ensureActiveVault();
578
+ if (options.add) {
579
+ await vault.addChain(options.add);
580
+ success(`
581
+ + Added chain: ${options.add}`);
582
+ const address = await vault.address(options.add);
583
+ info(`Address: ${address}`);
584
+ } else if (options.remove) {
585
+ await vault.removeChain(options.remove);
586
+ success(`
587
+ + Removed chain: ${options.remove}`);
588
+ } else {
589
+ const chains = vault.chains;
590
+ if (isJsonOutput()) {
591
+ outputJson({ chains: [...chains] });
592
+ return;
593
+ }
594
+ printResult(chalk3.cyan("\nActive Chains:\n"));
595
+ chains.forEach((chain) => {
596
+ printResult(` - ${chain}`);
597
+ });
598
+ info(chalk3.gray("\nUse --add <chain> to add a chain or --remove <chain> to remove one"));
599
+ }
600
+ }
601
+ async function executeAddresses(ctx2) {
602
+ const vault = await ctx2.ensureActiveVault();
603
+ const spinner = createSpinner("Loading addresses...");
604
+ const addresses = await vault.addresses();
605
+ spinner.succeed("Addresses loaded");
606
+ if (isJsonOutput()) {
607
+ outputJson({ addresses });
608
+ return;
609
+ }
610
+ displayAddresses(addresses);
611
+ }
612
+
613
+ // src/commands/tokens.ts
614
+ import chalk4 from "chalk";
615
+ import inquirer3 from "inquirer";
616
+ async function executeTokens(ctx2, options) {
617
+ await ctx2.ensureActiveVault();
618
+ if (options.add) {
619
+ let symbol = options.symbol;
620
+ let name = options.name;
621
+ let decimals = options.decimals;
622
+ const prompts = [];
623
+ if (!symbol) {
624
+ prompts.push({
625
+ type: "input",
626
+ name: "symbol",
627
+ message: "Enter token symbol (e.g., USDT):",
628
+ validate: (input) => input.trim() !== "" || "Symbol is required"
629
+ });
630
+ }
631
+ if (!name) {
632
+ prompts.push({
633
+ type: "input",
634
+ name: "name",
635
+ message: "Enter token name (e.g., Tether USD):",
636
+ validate: (input) => input.trim() !== "" || "Name is required"
637
+ });
638
+ }
639
+ if (decimals === void 0) {
640
+ prompts.push({
641
+ type: "number",
642
+ name: "decimals",
643
+ message: "Enter token decimals:",
644
+ default: 18,
645
+ validate: (input) => input >= 0 || "Decimals must be non-negative"
646
+ });
647
+ }
648
+ if (prompts.length > 0) {
649
+ const answers = await inquirer3.prompt(prompts);
650
+ symbol = symbol || answers.symbol?.trim();
651
+ name = name || answers.name?.trim();
652
+ decimals = decimals ?? answers.decimals;
653
+ }
654
+ await addToken(ctx2, {
655
+ chain: options.chain,
656
+ contractAddress: options.add,
657
+ symbol,
658
+ name,
659
+ decimals
660
+ });
661
+ } else if (options.remove) {
662
+ await removeToken(ctx2, options.chain, options.remove);
663
+ } else {
664
+ await listTokens(ctx2, options.chain);
665
+ }
666
+ }
667
+ async function addToken(ctx2, options) {
668
+ const vault = await ctx2.ensureActiveVault();
669
+ await vault.addToken(options.chain, {
670
+ id: `${options.chain}-${options.contractAddress}`,
671
+ contractAddress: options.contractAddress,
672
+ symbol: options.symbol,
673
+ name: options.name,
674
+ decimals: options.decimals,
675
+ chainId: options.chain,
676
+ isNative: false
677
+ });
678
+ success(`
679
+ + Added token ${options.symbol} on ${options.chain}`);
680
+ }
681
+ async function removeToken(ctx2, chain, tokenId) {
682
+ const vault = await ctx2.ensureActiveVault();
683
+ await vault.removeToken(chain, tokenId);
684
+ success(`
685
+ + Removed token ${tokenId} from ${chain}`);
686
+ }
687
+ async function listTokens(ctx2, chain) {
688
+ const vault = await ctx2.ensureActiveVault();
689
+ const spinner = createSpinner(`Loading tokens for ${chain}...`);
690
+ const tokens = vault.getTokens(chain);
691
+ spinner.succeed(`Tokens loaded for ${chain}`);
692
+ if (isJsonOutput()) {
693
+ outputJson({ chain, tokens: tokens || [] });
694
+ return;
695
+ }
696
+ if (!tokens || tokens.length === 0) {
697
+ warn(`
698
+ No tokens configured for ${chain}`);
699
+ info(chalk4.gray(`
700
+ Use --add <contractAddress> to add a token`));
701
+ } else {
702
+ printResult(chalk4.cyan(`
703
+ Tokens for ${chain}:
704
+ `));
705
+ const table = tokens.map((token) => ({
706
+ Symbol: token.symbol,
707
+ Name: token.name,
708
+ Contract: token.contractAddress || "N/A",
709
+ Decimals: token.decimals,
710
+ Native: token.isNative ? "Yes" : "No"
711
+ }));
712
+ printTable(table);
713
+ info(chalk4.gray(`
714
+ Use --add <contractAddress> to add or --remove <tokenId> to remove`));
715
+ }
716
+ }
717
+
718
+ // src/commands/transaction.ts
719
+ import { Chain, Vultisig as Vultisig2 } from "@vultisig/sdk";
720
+ async function executeSend(ctx2, params) {
721
+ const vault = await ctx2.ensureActiveVault();
722
+ if (!Object.values(Chain).includes(params.chain)) {
723
+ throw new Error(`Invalid chain: ${params.chain}`);
724
+ }
725
+ if (isNaN(parseFloat(params.amount)) || parseFloat(params.amount) <= 0) {
726
+ throw new Error("Invalid amount");
727
+ }
728
+ return sendTransaction(vault, params);
729
+ }
730
+ async function sendTransaction(vault, params) {
731
+ const prepareSpinner = createSpinner("Preparing transaction...");
732
+ const address = await vault.address(params.chain);
733
+ const balance = await vault.balance(params.chain, params.tokenId);
734
+ const coin = {
735
+ chain: params.chain,
736
+ address,
737
+ decimals: balance.decimals,
738
+ ticker: balance.symbol,
739
+ id: params.tokenId
740
+ };
741
+ const amount = BigInt(Math.floor(parseFloat(params.amount) * Math.pow(10, balance.decimals)));
742
+ const payload = await vault.prepareSendTx({
743
+ coin,
744
+ receiver: params.to,
745
+ amount,
746
+ memo: params.memo
747
+ });
748
+ prepareSpinner.succeed("Transaction prepared");
749
+ let gas;
750
+ try {
751
+ gas = await vault.gas(params.chain);
752
+ } catch {
753
+ warn("\nGas estimation unavailable");
754
+ }
755
+ if (!isJsonOutput()) {
756
+ displayTransactionPreview(
757
+ payload.coin.address,
758
+ params.to,
759
+ params.amount,
760
+ payload.coin.ticker,
761
+ params.chain,
762
+ params.memo,
763
+ gas
764
+ );
765
+ }
766
+ if (!params.yes && !isJsonOutput()) {
767
+ const confirmed = await confirmTransaction();
768
+ if (!confirmed) {
769
+ warn("Transaction cancelled");
770
+ throw new Error("Transaction cancelled by user");
771
+ }
772
+ }
773
+ const signSpinner = createSpinner("Signing transaction...");
774
+ vault.on("signingProgress", ({ step }) => {
775
+ signSpinner.text = `${step.message} (${step.progress}%)`;
776
+ });
777
+ try {
778
+ const messageHashes = await vault.extractMessageHashes(payload);
779
+ const signature = await vault.sign({
780
+ transaction: payload,
781
+ chain: payload.coin.chain,
782
+ messageHashes
783
+ });
784
+ signSpinner.succeed("Transaction signed");
785
+ const broadcastSpinner = createSpinner("Broadcasting transaction...");
786
+ const txHash = await vault.broadcastTx({
787
+ chain: params.chain,
788
+ keysignPayload: payload,
789
+ signature
790
+ });
791
+ broadcastSpinner.succeed(`Transaction broadcast: ${txHash}`);
792
+ const result = {
793
+ txHash,
794
+ chain: params.chain,
795
+ explorerUrl: Vultisig2.getTxExplorerUrl(params.chain, txHash)
796
+ };
797
+ if (isJsonOutput()) {
798
+ outputJson(result);
799
+ } else {
800
+ displayTransactionResult(params.chain, txHash);
801
+ }
802
+ return result;
803
+ } finally {
804
+ vault.removeAllListeners("signingProgress");
805
+ }
806
+ }
807
+
808
+ // src/commands/vault-management.ts
809
+ import chalk5 from "chalk";
810
+ import { promises as fs } from "fs";
811
+ import inquirer4 from "inquirer";
812
+ async function executeCreate(ctx2, options = { type: "fast" }) {
813
+ const vaultType = options.type.toLowerCase();
814
+ if (vaultType !== "fast" && vaultType !== "secure") {
815
+ throw new Error('Invalid vault type. Must be "fast" or "secure"');
816
+ }
817
+ let name = options.name;
818
+ let password = options.password;
819
+ const prompts = [];
820
+ if (!name) {
821
+ prompts.push({
822
+ type: "input",
823
+ name: "name",
824
+ message: "Enter vault name:",
825
+ validate: (input) => input.trim() !== "" || "Name is required"
826
+ });
827
+ }
828
+ if (!password) {
829
+ prompts.push({
830
+ type: "password",
831
+ name: "password",
832
+ message: "Enter password:",
833
+ mask: "*",
834
+ validate: (input) => input.length >= 8 || "Password must be at least 8 characters"
835
+ });
836
+ prompts.push({
837
+ type: "password",
838
+ name: "confirmPassword",
839
+ message: "Confirm password:",
840
+ mask: "*",
841
+ validate: (input, ans) => input === ans.password || "Passwords do not match"
842
+ });
843
+ }
844
+ if (prompts.length > 0) {
845
+ const answers = await inquirer4.prompt(prompts);
846
+ name = name || answers.name;
847
+ password = password || answers.password;
848
+ }
849
+ if (vaultType === "fast") {
850
+ let email = options.email;
851
+ if (!email) {
852
+ const emailAnswer = await inquirer4.prompt([
853
+ {
854
+ type: "input",
855
+ name: "email",
856
+ message: "Enter email for verification:",
857
+ validate: (input) => /\S+@\S+\.\S+/.test(input) || "Invalid email format"
858
+ }
859
+ ]);
860
+ email = emailAnswer.email;
861
+ }
862
+ const spinner = createSpinner("Creating vault...");
863
+ const result = await ctx2.sdk.createFastVault({
864
+ name,
865
+ password,
866
+ email,
867
+ onProgress: (step) => {
868
+ spinner.text = `${step.message} (${step.progress}%)`;
869
+ }
870
+ });
871
+ setupVaultEvents(result.vault);
872
+ await ctx2.setActiveVault(result.vault);
873
+ spinner.succeed(`Vault created: ${name}`);
874
+ if (result.verificationRequired) {
875
+ let code = options.code;
876
+ if (!code) {
877
+ warn("\nA verification code has been sent to your email.");
878
+ info("Please check your inbox and enter the code.");
879
+ const codeAnswer = await inquirer4.prompt([
880
+ {
881
+ type: "input",
882
+ name: "code",
883
+ message: `Verification code sent to ${email}. Enter code:`,
884
+ validate: (input) => /^\d{4,6}$/.test(input) || "Code must be 4-6 digits"
885
+ }
886
+ ]);
887
+ code = codeAnswer.code;
888
+ }
889
+ const verifySpinner = createSpinner("Verifying email code...");
890
+ const verified = await ctx2.sdk.verifyVault(result.vaultId, code);
891
+ if (verified) {
892
+ verifySpinner.succeed("Email verified successfully!");
893
+ } else {
894
+ verifySpinner.fail("Invalid verification code");
895
+ error("\nx Verification failed. Please check the code and try again.");
896
+ warn("\nTo retry verification, use:");
897
+ info(` npm run wallet verify ${result.vaultId}`);
898
+ warn("\nTo resend the verification email:");
899
+ info(` npm run wallet verify ${result.vaultId} --resend`);
900
+ const err = new Error("Verification failed");
901
+ err.exitCode = 1;
902
+ throw err;
903
+ }
904
+ }
905
+ success("\n+ Vault created!");
906
+ info("\nYour vault is ready. Run the following commands:");
907
+ printResult(chalk5.cyan(" npm run wallet balance ") + "- View balances");
908
+ printResult(chalk5.cyan(" npm run wallet addresses ") + "- View addresses");
909
+ printResult(chalk5.cyan(" npm run wallet portfolio ") + "- View portfolio value");
910
+ return result.vault;
911
+ } else {
912
+ let threshold = options.threshold;
913
+ let totalShares = options.shares;
914
+ const securePrompts = [];
915
+ if (threshold === void 0) {
916
+ securePrompts.push({
917
+ type: "number",
918
+ name: "threshold",
919
+ message: "Signing threshold (m):",
920
+ default: 2,
921
+ validate: (input) => input > 0 || "Threshold must be greater than 0"
922
+ });
923
+ }
924
+ if (totalShares === void 0) {
925
+ securePrompts.push({
926
+ type: "number",
927
+ name: "totalShares",
928
+ message: "Total shares (n):",
929
+ default: 3,
930
+ validate: (input, ans) => {
931
+ const t = threshold ?? ans.threshold;
932
+ return input >= t || `Total shares must be >= threshold (${t})`;
933
+ }
934
+ });
935
+ }
936
+ if (securePrompts.length > 0) {
937
+ const secureAnswers = await inquirer4.prompt(securePrompts);
938
+ threshold = threshold ?? secureAnswers.threshold;
939
+ totalShares = totalShares ?? secureAnswers.totalShares;
940
+ }
941
+ const spinner = createSpinner("Creating secure vault...");
942
+ try {
943
+ const result = await ctx2.sdk.createSecureVault({
944
+ name,
945
+ password,
946
+ devices: totalShares,
947
+ threshold,
948
+ onProgress: (step) => {
949
+ spinner.text = `${step.message} (${step.progress}%)`;
950
+ }
951
+ });
952
+ setupVaultEvents(result.vault);
953
+ await ctx2.setActiveVault(result.vault);
954
+ spinner.succeed(`Secure vault created: ${name} (${threshold}-of-${totalShares})`);
955
+ warn(`
956
+ Important: Save your vault backup file (.vult) in a secure location.`);
957
+ warn(`This is a ${threshold}-of-${totalShares} vault. You'll need ${threshold} devices to sign transactions.`);
958
+ success("\n+ Vault created!");
959
+ return result.vault;
960
+ } catch (err) {
961
+ spinner.fail("Secure vault creation failed");
962
+ if (err.message?.includes("not implemented")) {
963
+ warn("\nSecure vault creation is not yet implemented in the SDK");
964
+ }
965
+ throw err;
966
+ }
967
+ }
968
+ }
969
+ async function executeImport(ctx2, file) {
970
+ const { password } = await inquirer4.prompt([
971
+ {
972
+ type: "password",
973
+ name: "password",
974
+ message: "Enter vault password (if encrypted):",
975
+ mask: "*"
976
+ }
977
+ ]);
978
+ const spinner = createSpinner("Importing vault...");
979
+ const vultContent = await fs.readFile(file, "utf-8");
980
+ const vault = await ctx2.sdk.importVault(vultContent, password || void 0);
981
+ setupVaultEvents(vault);
982
+ await ctx2.setActiveVault(vault);
983
+ spinner.succeed(`Vault imported: ${vault.name}`);
984
+ success("\n+ Vault imported successfully!");
985
+ info('\nRun "npm run wallet balance" to view balances');
986
+ return vault;
987
+ }
988
+ async function executeVerify(ctx2, vaultId, options = {}) {
989
+ if (options.resend) {
990
+ const spinner2 = createSpinner("Resending verification email...");
991
+ await ctx2.sdk.resendVaultVerification(vaultId);
992
+ spinner2.succeed("Verification email sent!");
993
+ info("Check your inbox for the new verification code.");
994
+ }
995
+ let code = options.code;
996
+ if (!code) {
997
+ const codeAnswer = await inquirer4.prompt([
998
+ {
999
+ type: "input",
1000
+ name: "code",
1001
+ message: "Enter verification code:",
1002
+ validate: (input) => /^\d{4,6}$/.test(input) || "Code must be 4-6 digits"
1003
+ }
1004
+ ]);
1005
+ code = codeAnswer.code;
1006
+ }
1007
+ const spinner = createSpinner("Verifying email code...");
1008
+ const verified = await ctx2.sdk.verifyVault(vaultId, code);
1009
+ if (verified) {
1010
+ spinner.succeed("Vault verified successfully!");
1011
+ return true;
1012
+ } else {
1013
+ spinner.fail("Invalid verification code");
1014
+ error("\nx Verification failed. Please check the code and try again.");
1015
+ warn("\nTip: Use --resend to get a new verification code:");
1016
+ info(` npm run wallet verify ${vaultId} --resend`);
1017
+ return false;
1018
+ }
1019
+ }
1020
+ async function executeExport(ctx2, options = {}) {
1021
+ const vault = await ctx2.ensureActiveVault();
1022
+ let encrypt = options.encrypt;
1023
+ let password = options.password;
1024
+ if (encrypt === void 0) {
1025
+ const encryptAnswer = await inquirer4.prompt([
1026
+ {
1027
+ type: "confirm",
1028
+ name: "encrypt",
1029
+ message: "Encrypt export with password?",
1030
+ default: true
1031
+ }
1032
+ ]);
1033
+ encrypt = encryptAnswer.encrypt;
1034
+ }
1035
+ if (encrypt && !password) {
1036
+ const passwordAnswer = await inquirer4.prompt([
1037
+ {
1038
+ type: "password",
1039
+ name: "password",
1040
+ message: "Enter password:",
1041
+ mask: "*"
1042
+ }
1043
+ ]);
1044
+ password = passwordAnswer.password;
1045
+ }
1046
+ const spinner = createSpinner("Exporting vault...");
1047
+ const { data: vultContent } = await vault.export(encrypt ? password : void 0);
1048
+ const fileName = options.outputPath || `${vault.name}-${vault.localPartyId}-vault.vult`;
1049
+ await fs.writeFile(fileName, vultContent, "utf-8");
1050
+ spinner.succeed(`Vault exported: ${fileName}`);
1051
+ success("\n+ Vault exported successfully!");
1052
+ info(`File: ${fileName}`);
1053
+ return fileName;
1054
+ }
1055
+ async function executeVaults(ctx2) {
1056
+ const spinner = createSpinner("Loading vaults...");
1057
+ const vaults = await ctx2.sdk.listVaults();
1058
+ spinner.succeed("Vaults loaded");
1059
+ if (isJsonOutput()) {
1060
+ const activeVault2 = ctx2.getActiveVault();
1061
+ outputJson({
1062
+ vaults: vaults.map((v) => ({
1063
+ id: v.id,
1064
+ name: v.name,
1065
+ type: v.type,
1066
+ chains: v.chains.length,
1067
+ createdAt: v.createdAt,
1068
+ isActive: activeVault2?.id === v.id
1069
+ }))
1070
+ });
1071
+ return vaults;
1072
+ }
1073
+ if (vaults.length === 0) {
1074
+ warn("\nNo vaults found. Create or import a vault first.");
1075
+ return [];
1076
+ }
1077
+ const activeVault = ctx2.getActiveVault();
1078
+ displayVaultsList(vaults, activeVault);
1079
+ info(chalk5.gray('\nUse "npm run wallet switch <id>" to switch active vault'));
1080
+ return vaults;
1081
+ }
1082
+ async function executeSwitch(ctx2, vaultId) {
1083
+ const spinner = createSpinner("Loading vault...");
1084
+ const vault = await ctx2.sdk.getVaultById(vaultId);
1085
+ if (!vault) {
1086
+ spinner.fail("Vault not found");
1087
+ throw new Error(`No vault found with ID: ${vaultId}`);
1088
+ }
1089
+ await ctx2.setActiveVault(vault);
1090
+ setupVaultEvents(vault);
1091
+ spinner.succeed("Vault switched");
1092
+ success(`
1093
+ + Switched to vault: ${vault.name}`);
1094
+ info(` Type: ${vault.type}`);
1095
+ info(` Chains: ${vault.chains.length}`);
1096
+ return vault;
1097
+ }
1098
+ async function executeRename(ctx2, newName) {
1099
+ const vault = await ctx2.ensureActiveVault();
1100
+ const oldName = vault.name;
1101
+ const spinner = createSpinner("Renaming vault...");
1102
+ await vault.rename(newName);
1103
+ spinner.succeed("Vault renamed");
1104
+ success(`
1105
+ + Vault renamed from "${oldName}" to "${newName}"`);
1106
+ }
1107
+ async function executeInfo(ctx2) {
1108
+ const vault = await ctx2.ensureActiveVault();
1109
+ if (isJsonOutput()) {
1110
+ outputJson({
1111
+ vault: {
1112
+ id: vault.id,
1113
+ name: vault.name,
1114
+ type: vault.type,
1115
+ createdAt: vault.createdAt,
1116
+ lastModified: vault.lastModified,
1117
+ isEncrypted: vault.isEncrypted,
1118
+ isBackedUp: vault.isBackedUp,
1119
+ libType: vault.libType,
1120
+ threshold: vault.threshold,
1121
+ totalSigners: vault.totalSigners,
1122
+ localPartyId: vault.localPartyId,
1123
+ availableSigningModes: [...vault.availableSigningModes],
1124
+ chains: [...vault.chains],
1125
+ currency: vault.currency,
1126
+ publicKeys: {
1127
+ ecdsa: vault.publicKeys.ecdsa,
1128
+ eddsa: vault.publicKeys.eddsa,
1129
+ chainCode: vault.hexChainCode
1130
+ }
1131
+ }
1132
+ });
1133
+ return;
1134
+ }
1135
+ displayVaultInfo(vault);
1136
+ }
1137
+
1138
+ // src/commands/swap.ts
1139
+ async function executeSwapChains(ctx2) {
1140
+ const vault = await ctx2.ensureActiveVault();
1141
+ const spinner = createSpinner("Loading supported swap chains...");
1142
+ const chains = await vault.getSupportedSwapChains();
1143
+ spinner.succeed("Swap chains loaded");
1144
+ if (isJsonOutput()) {
1145
+ outputJson({ swapChains: [...chains] });
1146
+ return chains;
1147
+ }
1148
+ displaySwapChains(chains);
1149
+ return chains;
1150
+ }
1151
+ async function executeSwapQuote(ctx2, options) {
1152
+ const vault = await ctx2.ensureActiveVault();
1153
+ if (isNaN(options.amount) || options.amount <= 0) {
1154
+ throw new Error("Invalid amount");
1155
+ }
1156
+ const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
1157
+ if (!isSupported) {
1158
+ throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
1159
+ }
1160
+ const spinner = createSpinner("Getting swap quote...");
1161
+ const quote = await vault.getSwapQuote({
1162
+ fromCoin: { chain: options.fromChain, token: options.fromToken },
1163
+ toCoin: { chain: options.toChain, token: options.toToken },
1164
+ amount: options.amount,
1165
+ fiatCurrency: "usd"
1166
+ // Request fiat conversion
1167
+ });
1168
+ spinner.succeed("Quote received");
1169
+ if (isJsonOutput()) {
1170
+ outputJson({
1171
+ fromChain: options.fromChain,
1172
+ toChain: options.toChain,
1173
+ amount: options.amount,
1174
+ quote
1175
+ });
1176
+ return quote;
1177
+ }
1178
+ const feeBalance = await vault.balance(options.fromChain);
1179
+ displaySwapPreview(quote, String(options.amount), quote.fromCoin.ticker, quote.toCoin.ticker, {
1180
+ fromDecimals: quote.fromCoin.decimals,
1181
+ toDecimals: quote.toCoin.decimals,
1182
+ feeDecimals: feeBalance.decimals,
1183
+ feeSymbol: feeBalance.symbol
1184
+ });
1185
+ info('\nTo execute this swap, use the "swap" command');
1186
+ return quote;
1187
+ }
1188
+ async function executeSwap(ctx2, options) {
1189
+ const vault = await ctx2.ensureActiveVault();
1190
+ if (isNaN(options.amount) || options.amount <= 0) {
1191
+ throw new Error("Invalid amount");
1192
+ }
1193
+ const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
1194
+ if (!isSupported) {
1195
+ throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
1196
+ }
1197
+ const quoteSpinner = createSpinner("Getting swap quote...");
1198
+ const quote = await vault.getSwapQuote({
1199
+ fromCoin: { chain: options.fromChain, token: options.fromToken },
1200
+ toCoin: { chain: options.toChain, token: options.toToken },
1201
+ amount: options.amount,
1202
+ fiatCurrency: "usd"
1203
+ // Request fiat conversion
1204
+ });
1205
+ quoteSpinner.succeed("Quote received");
1206
+ const feeBalance = await vault.balance(options.fromChain);
1207
+ if (!isJsonOutput()) {
1208
+ displaySwapPreview(quote, String(options.amount), quote.fromCoin.ticker, quote.toCoin.ticker, {
1209
+ fromDecimals: quote.fromCoin.decimals,
1210
+ toDecimals: quote.toCoin.decimals,
1211
+ feeDecimals: feeBalance.decimals,
1212
+ feeSymbol: feeBalance.symbol
1213
+ });
1214
+ }
1215
+ if (!options.yes && !isJsonOutput()) {
1216
+ const confirmed = await confirmSwap();
1217
+ if (!confirmed) {
1218
+ warn("Swap cancelled");
1219
+ throw new Error("Swap cancelled by user");
1220
+ }
1221
+ }
1222
+ const prepSpinner = createSpinner("Preparing swap transaction...");
1223
+ const { keysignPayload, approvalPayload } = await vault.prepareSwapTx({
1224
+ fromCoin: { chain: options.fromChain, token: options.fromToken },
1225
+ toCoin: { chain: options.toChain, token: options.toToken },
1226
+ amount: options.amount,
1227
+ swapQuote: quote,
1228
+ autoApprove: false
1229
+ });
1230
+ prepSpinner.succeed("Swap prepared");
1231
+ if (approvalPayload) {
1232
+ info("\nToken approval required before swap...");
1233
+ const approvalSpinner = createSpinner("Signing approval transaction...");
1234
+ vault.on("signingProgress", ({ step }) => {
1235
+ approvalSpinner.text = `Approval: ${step.message} (${step.progress}%)`;
1236
+ });
1237
+ try {
1238
+ const approvalHashes = await vault.extractMessageHashes(approvalPayload);
1239
+ const approvalSig = await vault.sign({
1240
+ transaction: approvalPayload,
1241
+ chain: options.fromChain,
1242
+ messageHashes: approvalHashes
1243
+ });
1244
+ approvalSpinner.succeed("Approval signed");
1245
+ const broadcastApprovalSpinner = createSpinner("Broadcasting approval...");
1246
+ const approvalTxHash = await vault.broadcastTx({
1247
+ chain: options.fromChain,
1248
+ keysignPayload: approvalPayload,
1249
+ signature: approvalSig
1250
+ });
1251
+ broadcastApprovalSpinner.succeed(`Approval broadcast: ${approvalTxHash}`);
1252
+ info("Waiting for approval to confirm...");
1253
+ await new Promise((resolve) => setTimeout(resolve, 5e3));
1254
+ } finally {
1255
+ vault.removeAllListeners("signingProgress");
1256
+ }
1257
+ }
1258
+ const signSpinner = createSpinner("Signing swap transaction...");
1259
+ vault.on("signingProgress", ({ step }) => {
1260
+ signSpinner.text = `${step.message} (${step.progress}%)`;
1261
+ });
1262
+ try {
1263
+ const messageHashes = await vault.extractMessageHashes(keysignPayload);
1264
+ const signature = await vault.sign({
1265
+ transaction: keysignPayload,
1266
+ chain: options.fromChain,
1267
+ messageHashes
1268
+ });
1269
+ signSpinner.succeed("Swap transaction signed");
1270
+ const broadcastSpinner = createSpinner("Broadcasting swap transaction...");
1271
+ const txHash = await vault.broadcastTx({
1272
+ chain: options.fromChain,
1273
+ keysignPayload,
1274
+ signature
1275
+ });
1276
+ broadcastSpinner.succeed(`Swap broadcast: ${txHash}`);
1277
+ if (isJsonOutput()) {
1278
+ outputJson({
1279
+ txHash,
1280
+ fromChain: options.fromChain,
1281
+ toChain: options.toChain,
1282
+ quote
1283
+ });
1284
+ } else {
1285
+ displaySwapResult(options.fromChain, options.toChain, txHash, quote, quote.toCoin.decimals);
1286
+ }
1287
+ return { txHash, quote };
1288
+ } finally {
1289
+ vault.removeAllListeners("signingProgress");
1290
+ }
1291
+ }
1292
+
1293
+ // src/commands/settings.ts
1294
+ import { Chain as Chain2, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
1295
+ import chalk6 from "chalk";
1296
+ import inquirer5 from "inquirer";
1297
+ async function executeCurrency(ctx2, newCurrency) {
1298
+ const vault = await ctx2.ensureActiveVault();
1299
+ if (!newCurrency) {
1300
+ const currentCurrency = vault.currency;
1301
+ const currencyName2 = fiatCurrencyNameRecord3[currentCurrency];
1302
+ printResult(chalk6.cyan("\nCurrent Currency Preference:"));
1303
+ printResult(` ${chalk6.green(currentCurrency.toUpperCase())} - ${currencyName2}`);
1304
+ info(chalk6.gray(`
1305
+ Supported currencies: ${fiatCurrencies2.join(", ")}`));
1306
+ info(chalk6.gray('Use "npm run wallet currency <code>" to change'));
1307
+ return currentCurrency;
1308
+ }
1309
+ const currency = newCurrency.toLowerCase();
1310
+ if (!fiatCurrencies2.includes(currency)) {
1311
+ error(`x Invalid currency: ${newCurrency}`);
1312
+ warn(`Supported currencies: ${fiatCurrencies2.join(", ")}`);
1313
+ throw new Error("Invalid currency");
1314
+ }
1315
+ const spinner = createSpinner("Updating currency preference...");
1316
+ await vault.setCurrency(currency);
1317
+ spinner.succeed("Currency updated");
1318
+ const currencyName = fiatCurrencyNameRecord3[currency];
1319
+ success(`
1320
+ + Currency preference set to ${currency.toUpperCase()} (${currencyName})`);
1321
+ return currency;
1322
+ }
1323
+ async function executeServer(ctx2) {
1324
+ const spinner = createSpinner("Checking server status...");
1325
+ try {
1326
+ const status = await ctx2.sdk.getServerStatus();
1327
+ spinner.succeed("Server status retrieved");
1328
+ if (isJsonOutput()) {
1329
+ outputJson({ server: status });
1330
+ return status;
1331
+ }
1332
+ printResult(chalk6.cyan("\nServer Status:\n"));
1333
+ printResult(chalk6.bold("Fast Vault Server:"));
1334
+ printResult(` Online: ${status.fastVault.online ? chalk6.green("Yes") : chalk6.red("No")}`);
1335
+ if (status.fastVault.latency) {
1336
+ printResult(` Latency: ${status.fastVault.latency}ms`);
1337
+ }
1338
+ printResult(chalk6.bold("\nMessage Relay:"));
1339
+ printResult(` Online: ${status.messageRelay.online ? chalk6.green("Yes") : chalk6.red("No")}`);
1340
+ if (status.messageRelay.latency) {
1341
+ printResult(` Latency: ${status.messageRelay.latency}ms`);
1342
+ }
1343
+ return status;
1344
+ } catch (err) {
1345
+ spinner.fail("Failed to check server status");
1346
+ error(`
1347
+ x ${err.message}`);
1348
+ throw err;
1349
+ }
1350
+ }
1351
+ async function executeAddressBook(ctx2, options = {}) {
1352
+ if (options.add) {
1353
+ let chain = options.chain;
1354
+ let address = options.address;
1355
+ let name = options.name;
1356
+ const prompts = [];
1357
+ if (!chain) {
1358
+ prompts.push({
1359
+ type: "list",
1360
+ name: "chain",
1361
+ message: "Select chain:",
1362
+ choices: Object.values(Chain2)
1363
+ });
1364
+ }
1365
+ if (!address) {
1366
+ prompts.push({
1367
+ type: "input",
1368
+ name: "address",
1369
+ message: "Enter address:",
1370
+ validate: (input) => input.trim() !== "" || "Address is required"
1371
+ });
1372
+ }
1373
+ if (!name) {
1374
+ prompts.push({
1375
+ type: "input",
1376
+ name: "name",
1377
+ message: "Enter name/label:",
1378
+ validate: (input) => input.trim() !== "" || "Name is required"
1379
+ });
1380
+ }
1381
+ if (prompts.length > 0) {
1382
+ const answers = await inquirer5.prompt(prompts);
1383
+ chain = chain || answers.chain;
1384
+ address = address || answers.address?.trim();
1385
+ name = name || answers.name?.trim();
1386
+ }
1387
+ const spinner2 = createSpinner("Adding address to address book...");
1388
+ await ctx2.sdk.addAddressBookEntry([
1389
+ {
1390
+ chain,
1391
+ address,
1392
+ name,
1393
+ source: "saved",
1394
+ dateAdded: Date.now()
1395
+ }
1396
+ ]);
1397
+ spinner2.succeed("Address added");
1398
+ success(`
1399
+ + Added ${name} (${chain}: ${address})`);
1400
+ return [];
1401
+ }
1402
+ if (options.remove) {
1403
+ const spinner2 = createSpinner("Removing address from address book...");
1404
+ await ctx2.sdk.removeAddressBookEntry([{ address: options.remove, chain: options.chain }]);
1405
+ spinner2.succeed("Address removed");
1406
+ success(`
1407
+ + Removed ${options.remove}`);
1408
+ return [];
1409
+ }
1410
+ const spinner = createSpinner("Loading address book...");
1411
+ const addressBook = await ctx2.sdk.getAddressBook(options.chain);
1412
+ spinner.succeed("Address book loaded");
1413
+ const allEntries = [...addressBook.saved, ...addressBook.vaults];
1414
+ if (isJsonOutput()) {
1415
+ outputJson({ addressBook: allEntries, chain: options.chain });
1416
+ return allEntries;
1417
+ }
1418
+ if (allEntries.length === 0) {
1419
+ warn(`
1420
+ No addresses in address book${options.chain ? ` for ${options.chain}` : ""}`);
1421
+ info(chalk6.gray("\nUse --add to add an address to the address book"));
1422
+ } else {
1423
+ printResult(chalk6.cyan(`
1424
+ Address Book${options.chain ? ` (${options.chain})` : ""}:
1425
+ `));
1426
+ const table = allEntries.map((entry) => ({
1427
+ Name: entry.name,
1428
+ Chain: entry.chain,
1429
+ Address: entry.address,
1430
+ Source: entry.source
1431
+ }));
1432
+ printTable(table);
1433
+ info(chalk6.gray("\nUse --add to add or --remove <address> to remove an address"));
1434
+ }
1435
+ return allEntries;
1436
+ }
1437
+
1438
+ // src/interactive/completer.ts
1439
+ import { Chain as Chain3 } from "@vultisig/sdk";
1440
+ import fs2 from "fs";
1441
+ import path from "path";
1442
+ var COMMANDS = [
1443
+ // Vault management
1444
+ "vaults",
1445
+ "vault",
1446
+ "import",
1447
+ "create",
1448
+ "info",
1449
+ "export",
1450
+ // Wallet operations
1451
+ "balance",
1452
+ "bal",
1453
+ "send",
1454
+ "portfolio",
1455
+ "addresses",
1456
+ "chains",
1457
+ "tokens",
1458
+ // Swap operations
1459
+ "swap-chains",
1460
+ "swap-quote",
1461
+ "swap",
1462
+ // Session commands (shell-only)
1463
+ "lock",
1464
+ "unlock",
1465
+ "status",
1466
+ // Settings
1467
+ "currency",
1468
+ "server",
1469
+ "address-book",
1470
+ // Help
1471
+ "help",
1472
+ "?",
1473
+ // REPL commands
1474
+ ".help",
1475
+ ".clear",
1476
+ ".exit"
1477
+ ];
1478
+ function createCompleter(ctx2) {
1479
+ return function completer(line) {
1480
+ try {
1481
+ const parts = line.split(/\s+/);
1482
+ const command = parts[0]?.toLowerCase();
1483
+ if ((command === "import" || command === "export") && parts.length > 1) {
1484
+ const partial = parts.slice(1).join(" ");
1485
+ return completeFilePath(partial, command === "import");
1486
+ }
1487
+ if (command === "vault" && parts.length > 1) {
1488
+ const partial = parts.slice(1).join(" ");
1489
+ return completeVaultName(ctx2, partial);
1490
+ }
1491
+ if (command === "chains" && parts.length >= 2) {
1492
+ const flag = parts[parts.length - 2]?.toLowerCase();
1493
+ if (flag === "--add" || flag === "--remove") {
1494
+ const partial = parts[parts.length - 1] || "";
1495
+ return completeChainName(partial);
1496
+ }
1497
+ if (parts[parts.length - 1]?.toLowerCase() === "--add" || parts[parts.length - 1]?.toLowerCase() === "--remove") {
1498
+ return completeChainName("");
1499
+ }
1500
+ }
1501
+ if (["balance", "bal", "tokens", "send", "swap", "swap-quote"].includes(command) && parts.length === 2) {
1502
+ const partial = parts[1] || "";
1503
+ return completeChainName(partial);
1504
+ }
1505
+ const hits = COMMANDS.filter((c) => c.startsWith(line));
1506
+ const show = hits.length ? hits : COMMANDS;
1507
+ return [show, line];
1508
+ } catch {
1509
+ return [[], line];
1510
+ }
1511
+ };
1512
+ }
1513
+ function completeFilePath(partial, filterVult) {
1514
+ try {
1515
+ const endsWithSeparator = partial.endsWith("/") || partial.endsWith(path.sep);
1516
+ let dir;
1517
+ let basename;
1518
+ if (endsWithSeparator) {
1519
+ dir = partial;
1520
+ basename = "";
1521
+ } else {
1522
+ dir = path.dirname(partial);
1523
+ basename = path.basename(partial);
1524
+ if (fs2.existsSync(partial) && fs2.statSync(partial).isDirectory()) {
1525
+ dir = partial;
1526
+ basename = "";
1527
+ }
1528
+ }
1529
+ const resolvedDir = path.resolve(dir);
1530
+ if (!fs2.existsSync(resolvedDir) || !fs2.statSync(resolvedDir).isDirectory()) {
1531
+ return [[], partial];
1532
+ }
1533
+ const files = fs2.readdirSync(resolvedDir);
1534
+ const matches = files.filter((file) => file.startsWith(basename)).map((file) => {
1535
+ const fullPath = path.join(dir, file);
1536
+ const stats = fs2.statSync(path.join(resolvedDir, file));
1537
+ if (stats.isDirectory()) {
1538
+ return fullPath + "/";
1539
+ }
1540
+ if (filterVult) {
1541
+ if (file.endsWith(".vult") || stats.isDirectory()) {
1542
+ return fullPath;
1543
+ }
1544
+ return null;
1545
+ }
1546
+ return fullPath;
1547
+ }).filter((item) => item !== null);
1548
+ return [matches, partial];
1549
+ } catch {
1550
+ return [[], partial];
1551
+ }
1552
+ }
1553
+ function completeVaultName(ctx2, partial) {
1554
+ const vaultNames = Array.from(ctx2.getVaults().values()).map((vault) => vault.name);
1555
+ const partialLower = partial.toLowerCase();
1556
+ const matches = vaultNames.filter((name) => name.toLowerCase().startsWith(partialLower));
1557
+ matches.sort();
1558
+ const show = matches.length > 0 ? matches : vaultNames.sort();
1559
+ return [show, partial];
1560
+ }
1561
+ function completeChainName(partial) {
1562
+ const allChains = Object.values(Chain3);
1563
+ const partialLower = partial.toLowerCase();
1564
+ const matches = allChains.filter((chain) => chain.toLowerCase().startsWith(partialLower));
1565
+ matches.sort();
1566
+ const show = matches.length > 0 ? matches : allChains.sort();
1567
+ return [show, partial];
1568
+ }
1569
+ function findChainByName(name) {
1570
+ const allChains = Object.values(Chain3);
1571
+ const nameLower = name.toLowerCase();
1572
+ const found = allChains.find((chain) => chain.toLowerCase() === nameLower);
1573
+ return found ? found : null;
1574
+ }
1575
+
1576
+ // src/interactive/event-buffer.ts
1577
+ import chalk7 from "chalk";
1578
+ var EventBuffer = class {
1579
+ eventBuffer = [];
1580
+ isCommandRunning = false;
1581
+ /**
1582
+ * Mark the start of a command execution.
1583
+ * Events will be buffered until endCommand() is called.
1584
+ */
1585
+ startCommand() {
1586
+ this.isCommandRunning = true;
1587
+ this.eventBuffer = [];
1588
+ }
1589
+ /**
1590
+ * Mark the end of a command execution.
1591
+ * Flushes any buffered events to the console.
1592
+ */
1593
+ endCommand() {
1594
+ this.isCommandRunning = false;
1595
+ this.flushBuffer();
1596
+ }
1597
+ /**
1598
+ * Handle an event - buffer if command is running, display immediately if idle.
1599
+ */
1600
+ handleEvent(message, type = "info") {
1601
+ if (this.isCommandRunning) {
1602
+ this.eventBuffer.push({
1603
+ timestamp: /* @__PURE__ */ new Date(),
1604
+ message,
1605
+ type
1606
+ });
1607
+ } else {
1608
+ this.displayEvent(message, type);
1609
+ }
1610
+ }
1611
+ /**
1612
+ * Display a single event to the console with appropriate formatting.
1613
+ */
1614
+ displayEvent(message, type) {
1615
+ switch (type) {
1616
+ case "success":
1617
+ console.log(chalk7.green(message));
1618
+ break;
1619
+ case "warning":
1620
+ console.log(chalk7.yellow(message));
1621
+ break;
1622
+ case "error":
1623
+ console.error(chalk7.red(message));
1624
+ break;
1625
+ case "info":
1626
+ default:
1627
+ console.log(chalk7.blue(message));
1628
+ break;
1629
+ }
1630
+ }
1631
+ /**
1632
+ * Flush all buffered events to the console.
1633
+ */
1634
+ flushBuffer() {
1635
+ if (this.eventBuffer.length === 0) {
1636
+ return;
1637
+ }
1638
+ console.log(chalk7.gray("\n--- Background Events ---"));
1639
+ this.eventBuffer.forEach((event) => {
1640
+ const timeStr = event.timestamp.toLocaleTimeString();
1641
+ const message = `[${timeStr}] ${event.message}`;
1642
+ this.displayEvent(message, event.type);
1643
+ });
1644
+ console.log(chalk7.gray("--- End Events ---\n"));
1645
+ }
1646
+ /**
1647
+ * Setup all vault event listeners
1648
+ */
1649
+ setupVaultListeners(vault) {
1650
+ vault.on("balanceUpdated", ({ chain, balance, tokenId }) => {
1651
+ const asset = tokenId ? `${balance.symbol} token` : balance.symbol;
1652
+ this.handleEvent(`i Balance updated for ${chain} (${asset}): ${balance.amount}`, "info");
1653
+ });
1654
+ vault.on("transactionSigned", () => {
1655
+ this.handleEvent(`+ Transaction signed successfully`, "success");
1656
+ });
1657
+ vault.on("transactionBroadcast", ({ chain, txHash }) => {
1658
+ this.handleEvent(`+ Transaction broadcast on ${chain}`, "success");
1659
+ this.handleEvent(` TX Hash: ${txHash}`, "info");
1660
+ });
1661
+ vault.on("signingProgress", ({ step }) => {
1662
+ this.handleEvent(`i Signing: ${step}`, "info");
1663
+ });
1664
+ vault.on("chainAdded", ({ chain }) => {
1665
+ this.handleEvent(`+ Chain added: ${chain}`, "success");
1666
+ });
1667
+ vault.on("chainRemoved", ({ chain }) => {
1668
+ this.handleEvent(`i Chain removed: ${chain}`, "warning");
1669
+ });
1670
+ vault.on("tokenAdded", ({ chain, token }) => {
1671
+ this.handleEvent(`+ Token added: ${token.symbol} on ${chain}`, "success");
1672
+ });
1673
+ vault.on("tokenRemoved", ({ chain, tokenId }) => {
1674
+ this.handleEvent(`i Token removed: ${tokenId} from ${chain}`, "warning");
1675
+ });
1676
+ vault.on("renamed", ({ oldName, newName }) => {
1677
+ this.handleEvent(`i Vault renamed: ${oldName} -> ${newName}`, "info");
1678
+ });
1679
+ vault.on("valuesUpdated", ({ chain }) => {
1680
+ if (chain === "all") {
1681
+ this.handleEvent("i Portfolio values updated", "info");
1682
+ } else {
1683
+ this.handleEvent(`i Values updated for ${chain}`, "info");
1684
+ }
1685
+ });
1686
+ vault.on("totalValueUpdated", ({ value }) => {
1687
+ this.handleEvent(`i Portfolio total: ${value.formatted}`, "info");
1688
+ });
1689
+ vault.on("saved", () => {
1690
+ this.handleEvent(`+ Vault saved`, "success");
1691
+ });
1692
+ vault.on("loaded", () => {
1693
+ this.handleEvent(`i Vault loaded`, "info");
1694
+ });
1695
+ vault.on("unlocked", () => {
1696
+ this.handleEvent(`+ Vault unlocked`, "success");
1697
+ });
1698
+ vault.on("locked", () => {
1699
+ this.handleEvent(`i Vault locked`, "info");
1700
+ });
1701
+ vault.on("swapQuoteReceived", ({ quote }) => {
1702
+ this.handleEvent(`i Swap quote received: ${quote.fromAmount} -> ${quote.toAmount}`, "info");
1703
+ });
1704
+ vault.on("swapApprovalRequired", ({ token, amount }) => {
1705
+ this.handleEvent(`! Approval required for ${token}: ${amount}`, "warning");
1706
+ });
1707
+ vault.on("swapApprovalGranted", ({ token }) => {
1708
+ this.handleEvent(`+ Approval granted for ${token}`, "success");
1709
+ });
1710
+ vault.on("swapPrepared", ({ provider, fromAmount, toAmountExpected }) => {
1711
+ this.handleEvent(`i Swap prepared via ${provider}: ${fromAmount} -> ${toAmountExpected}`, "info");
1712
+ });
1713
+ vault.on("error", (error2) => {
1714
+ this.handleEvent(`x Vault error: ${error2.message}`, "error");
1715
+ });
1716
+ }
1717
+ /**
1718
+ * Remove all event listeners from a vault.
1719
+ */
1720
+ cleanupVaultListeners(vault) {
1721
+ vault.removeAllListeners("balanceUpdated");
1722
+ vault.removeAllListeners("transactionSigned");
1723
+ vault.removeAllListeners("transactionBroadcast");
1724
+ vault.removeAllListeners("signingProgress");
1725
+ vault.removeAllListeners("chainAdded");
1726
+ vault.removeAllListeners("chainRemoved");
1727
+ vault.removeAllListeners("tokenAdded");
1728
+ vault.removeAllListeners("tokenRemoved");
1729
+ vault.removeAllListeners("renamed");
1730
+ vault.removeAllListeners("valuesUpdated");
1731
+ vault.removeAllListeners("totalValueUpdated");
1732
+ vault.removeAllListeners("saved");
1733
+ vault.removeAllListeners("loaded");
1734
+ vault.removeAllListeners("unlocked");
1735
+ vault.removeAllListeners("locked");
1736
+ vault.removeAllListeners("swapQuoteReceived");
1737
+ vault.removeAllListeners("swapApprovalRequired");
1738
+ vault.removeAllListeners("swapApprovalGranted");
1739
+ vault.removeAllListeners("swapPrepared");
1740
+ vault.removeAllListeners("error");
1741
+ }
1742
+ };
1743
+
1744
+ // src/interactive/session.ts
1745
+ import { fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
1746
+ import chalk9 from "chalk";
1747
+ import ora3 from "ora";
1748
+ import * as repl from "repl";
1749
+
1750
+ // src/interactive/shell-commands.ts
1751
+ import chalk8 from "chalk";
1752
+ import Table from "cli-table3";
1753
+ import inquirer6 from "inquirer";
1754
+ import ora2 from "ora";
1755
+ function formatTimeRemaining(ms) {
1756
+ if (!ms || ms <= 0) return "expired";
1757
+ const seconds = Math.floor(ms / 1e3);
1758
+ const minutes = Math.floor(seconds / 60);
1759
+ const remainingSeconds = seconds % 60;
1760
+ if (minutes > 0) {
1761
+ return `${minutes}m ${remainingSeconds}s`;
1762
+ }
1763
+ return `${remainingSeconds}s`;
1764
+ }
1765
+ async function executeLock(ctx2) {
1766
+ const vault = ctx2.getActiveVault();
1767
+ if (!vault) {
1768
+ console.log(chalk8.red("No active vault."));
1769
+ console.log(chalk8.yellow('Use "vault <name>" to switch to a vault first.'));
1770
+ return;
1771
+ }
1772
+ ctx2.lockVault(vault.id);
1773
+ console.log(chalk8.green("\n+ Vault locked"));
1774
+ console.log(chalk8.gray("Password cache cleared. You will need to enter the password again."));
1775
+ }
1776
+ async function executeUnlock(ctx2) {
1777
+ const vault = ctx2.getActiveVault();
1778
+ if (!vault) {
1779
+ console.log(chalk8.red("No active vault."));
1780
+ console.log(chalk8.yellow('Use "vault <name>" to switch to a vault first.'));
1781
+ return;
1782
+ }
1783
+ if (ctx2.isVaultUnlocked(vault.id)) {
1784
+ const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
1785
+ console.log(chalk8.yellow("\nVault is already unlocked."));
1786
+ console.log(chalk8.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
1787
+ return;
1788
+ }
1789
+ const { password } = await inquirer6.prompt([
1790
+ {
1791
+ type: "password",
1792
+ name: "password",
1793
+ message: "Enter vault password:",
1794
+ mask: "*"
1795
+ }
1796
+ ]);
1797
+ const spinner = ora2("Unlocking vault...").start();
1798
+ try {
1799
+ await vault.unlock(password);
1800
+ ctx2.cachePassword(vault.id, password);
1801
+ const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
1802
+ spinner.succeed("Vault unlocked");
1803
+ console.log(chalk8.green(`
1804
+ + Vault unlocked for ${formatTimeRemaining(timeRemaining)}`));
1805
+ } catch (err) {
1806
+ spinner.fail("Failed to unlock vault");
1807
+ console.error(chalk8.red(`
1808
+ x ${err.message}`));
1809
+ }
1810
+ }
1811
+ async function executeStatus(ctx2) {
1812
+ const vault = ctx2.getActiveVault();
1813
+ if (!vault) {
1814
+ console.log(chalk8.red("No active vault."));
1815
+ console.log(chalk8.yellow('Use "vault <name>" to switch to a vault first.'));
1816
+ return;
1817
+ }
1818
+ const isUnlocked = ctx2.isVaultUnlocked(vault.id);
1819
+ let timeRemaining;
1820
+ let timeRemainingFormatted;
1821
+ if (isUnlocked) {
1822
+ timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
1823
+ timeRemainingFormatted = formatTimeRemaining(timeRemaining);
1824
+ }
1825
+ const status = {
1826
+ name: vault.name,
1827
+ id: vault.id,
1828
+ type: vault.type,
1829
+ isUnlocked,
1830
+ timeRemaining,
1831
+ timeRemainingFormatted,
1832
+ createdAt: vault.createdAt,
1833
+ lastModified: vault.lastModified,
1834
+ threshold: vault.threshold,
1835
+ totalSigners: vault.totalSigners,
1836
+ libType: vault.libType,
1837
+ isEncrypted: vault.isEncrypted,
1838
+ isBackedUp: vault.isBackedUp,
1839
+ chains: vault.chains.length,
1840
+ currency: vault.currency,
1841
+ availableSigningModes: vault.availableSigningModes
1842
+ };
1843
+ displayStatus(status);
1844
+ }
1845
+ function displayStatus(status) {
1846
+ console.log(chalk8.cyan("\n+----------------------------------------+"));
1847
+ console.log(chalk8.cyan("| Vault Status |"));
1848
+ console.log(chalk8.cyan("+----------------------------------------+\n"));
1849
+ console.log(chalk8.bold("Vault:"));
1850
+ console.log(` Name: ${chalk8.green(status.name)}`);
1851
+ console.log(` ID: ${status.id}`);
1852
+ console.log(` Type: ${chalk8.yellow(status.type)}`);
1853
+ console.log(chalk8.bold("\nSecurity:"));
1854
+ if (status.isUnlocked) {
1855
+ console.log(` Status: ${chalk8.green("Unlocked")} ${chalk8.green("\u{1F513}")}`);
1856
+ console.log(` Expires: ${status.timeRemainingFormatted}`);
1857
+ } else {
1858
+ console.log(` Status: ${chalk8.yellow("Locked")} ${chalk8.yellow("\u{1F512}")}`);
1859
+ }
1860
+ console.log(` Encrypted: ${status.isEncrypted ? chalk8.green("Yes") : chalk8.gray("No")}`);
1861
+ console.log(` Backed Up: ${status.isBackedUp ? chalk8.green("Yes") : chalk8.yellow("No")}`);
1862
+ console.log(chalk8.bold("\nMPC Configuration:"));
1863
+ console.log(` Library: ${status.libType}`);
1864
+ console.log(` Threshold: ${chalk8.cyan(status.threshold)} of ${chalk8.cyan(status.totalSigners)}`);
1865
+ console.log(chalk8.bold("\nSigning Modes:"));
1866
+ status.availableSigningModes.forEach((mode) => {
1867
+ console.log(` - ${mode}`);
1868
+ });
1869
+ console.log(chalk8.bold("\nDetails:"));
1870
+ console.log(` Chains: ${status.chains}`);
1871
+ console.log(` Currency: ${status.currency.toUpperCase()}`);
1872
+ console.log(` Created: ${new Date(status.createdAt).toLocaleString()}`);
1873
+ console.log(` Modified: ${new Date(status.lastModified).toLocaleString()}
1874
+ `);
1875
+ }
1876
+ function showHelp() {
1877
+ const table = new Table({
1878
+ head: [chalk8.bold("Available Commands")],
1879
+ colWidths: [50],
1880
+ chars: {
1881
+ mid: "",
1882
+ "left-mid": "",
1883
+ "mid-mid": "",
1884
+ "right-mid": ""
1885
+ },
1886
+ style: {
1887
+ head: ["cyan"],
1888
+ border: ["cyan"]
1889
+ }
1890
+ });
1891
+ table.push(
1892
+ [chalk8.bold("Vault Management:")],
1893
+ [" vaults - List all vaults"],
1894
+ [" vault <name> - Switch to vault"],
1895
+ [" import <file> - Import vault from file"],
1896
+ [" create - Create new vault"],
1897
+ [" info - Show vault details"],
1898
+ [" export [path] - Export vault to file"],
1899
+ [""],
1900
+ [chalk8.bold("Wallet Operations:")],
1901
+ [" balance [chain] - Show balances"],
1902
+ [" send <chain> <to> <amount> - Send transaction"],
1903
+ [" portfolio [-c usd] - Show portfolio value"],
1904
+ [" addresses - Show all addresses"],
1905
+ [" chains [--add/--remove] - Manage chains"],
1906
+ [" tokens <chain> - Manage tokens"],
1907
+ [""],
1908
+ [chalk8.bold("Swap Operations:")],
1909
+ [" swap-chains - List swap-enabled chains"],
1910
+ [" swap-quote <from> <to> <amount> - Get quote"],
1911
+ [" swap <from> <to> <amount> - Execute swap"],
1912
+ [""],
1913
+ [chalk8.bold("Session Commands (shell only):")],
1914
+ [" lock - Lock vault"],
1915
+ [" unlock - Unlock vault"],
1916
+ [" status - Show vault status"],
1917
+ [""],
1918
+ [chalk8.bold("Settings:")],
1919
+ [" currency [code] - View/set currency"],
1920
+ [" server - Check server status"],
1921
+ [" address-book - Manage saved addresses"],
1922
+ [""],
1923
+ [chalk8.bold("Help & Navigation:")],
1924
+ [" help, ? - Show this help"],
1925
+ [" .clear - Clear screen"],
1926
+ [" .exit - Exit shell"]
1927
+ );
1928
+ console.log("\n" + table.toString() + "\n");
1929
+ }
1930
+
1931
+ // src/interactive/shell-context.ts
1932
+ import inquirer7 from "inquirer";
1933
+ var ShellContext = class extends BaseCommandContext {
1934
+ vaults = /* @__PURE__ */ new Map();
1935
+ constructor(sdk, options) {
1936
+ super(sdk, options);
1937
+ }
1938
+ get isInteractive() {
1939
+ return true;
1940
+ }
1941
+ /**
1942
+ * Get all loaded vaults
1943
+ */
1944
+ getVaults() {
1945
+ return this.vaults;
1946
+ }
1947
+ /**
1948
+ * Add a vault to the context
1949
+ */
1950
+ addVault(vault) {
1951
+ this.vaults.set(vault.id, vault);
1952
+ }
1953
+ /**
1954
+ * Get vault by ID
1955
+ */
1956
+ getVaultById(id) {
1957
+ return this.vaults.get(id);
1958
+ }
1959
+ /**
1960
+ * Find vault by name (case-insensitive)
1961
+ */
1962
+ findVaultByName(name) {
1963
+ const nameLower = name.toLowerCase();
1964
+ for (const vault of this.vaults.values()) {
1965
+ if (vault.name.toLowerCase() === nameLower) {
1966
+ return vault;
1967
+ }
1968
+ }
59
1969
  return null;
1970
+ }
1971
+ /**
1972
+ * Get password for a vault
1973
+ * In Shell mode, we check cache first, then env, then prompt
1974
+ * Passwords are cached for the session
1975
+ */
1976
+ async getPassword(vaultId, vaultName) {
1977
+ const cached = this.getCachedPassword(vaultId);
1978
+ if (cached) {
1979
+ return cached;
1980
+ }
1981
+ const envPassword = getPasswordFromEnv(vaultId, vaultName);
1982
+ if (envPassword) {
1983
+ this.cachePassword(vaultId, envPassword);
1984
+ return envPassword;
1985
+ }
1986
+ const displayName = vaultName || vaultId;
1987
+ const { password } = await inquirer7.prompt([
1988
+ {
1989
+ type: "password",
1990
+ name: "password",
1991
+ message: `Enter password for vault "${displayName}":`,
1992
+ mask: "*"
1993
+ }
1994
+ ]);
1995
+ this.cachePassword(vaultId, password);
1996
+ return password;
1997
+ }
1998
+ /**
1999
+ * Lock a vault (clear its cached password)
2000
+ */
2001
+ lockVault(vaultId) {
2002
+ this.clearPasswordCache(vaultId);
2003
+ const vault = this.vaults.get(vaultId);
2004
+ if (vault) {
2005
+ vault.lock();
2006
+ }
2007
+ }
2008
+ /**
2009
+ * Get unlock time remaining for a vault
2010
+ */
2011
+ getUnlockTimeRemaining(vaultId) {
2012
+ const entry = this.passwordCache.get(vaultId);
2013
+ if (!entry) return void 0;
2014
+ const remaining = entry.expiresAt - Date.now();
2015
+ return remaining > 0 ? remaining : void 0;
2016
+ }
2017
+ /**
2018
+ * Check if a vault is unlocked (has cached password)
2019
+ */
2020
+ isVaultUnlocked(vaultId) {
2021
+ return this.isPasswordCached(vaultId);
2022
+ }
2023
+ };
2024
+ function createShellContext(sdk, options) {
2025
+ return new ShellContext(sdk, {
2026
+ passwordTtlMs: options?.passwordTtlMs ?? DEFAULT_PASSWORD_CACHE_TTL
2027
+ });
60
2028
  }
61
- async function init(vaultOverride) {
62
- if (!ctx) {
63
- const sdk = new Vultisig({
64
- onPasswordRequired: createPasswordCallback(),
2029
+
2030
+ // src/interactive/session.ts
2031
+ function createReplSafeSpinner(text) {
2032
+ return ora3({
2033
+ text,
2034
+ hideCursor: false,
2035
+ stream: process.stdout,
2036
+ isEnabled: true,
2037
+ isSilent: false
2038
+ });
2039
+ }
2040
+ var ShellSession = class {
2041
+ ctx;
2042
+ eventBuffer;
2043
+ replServer;
2044
+ constructor(sdk, options) {
2045
+ this.ctx = createShellContext(sdk, options);
2046
+ this.eventBuffer = new EventBuffer();
2047
+ }
2048
+ /**
2049
+ * Start the interactive shell
2050
+ */
2051
+ async start() {
2052
+ console.clear();
2053
+ console.log(chalk9.cyan.bold("\n=============================================="));
2054
+ console.log(chalk9.cyan.bold(" Vultisig Interactive Shell"));
2055
+ console.log(chalk9.cyan.bold("==============================================\n"));
2056
+ await this.loadAllVaults();
2057
+ this.displayVaultList();
2058
+ console.log(chalk9.gray('Type "help" for available commands, ".exit" to quit\n'));
2059
+ this.replServer = repl.start({
2060
+ prompt: this.getPrompt(),
2061
+ eval: this.evalCommand.bind(this),
2062
+ ignoreUndefined: true,
2063
+ terminal: true,
2064
+ useColors: true,
2065
+ completer: createCompleter(this.ctx)
2066
+ });
2067
+ this.setupReplCommands();
2068
+ }
2069
+ /**
2070
+ * Custom eval function for command processing
2071
+ */
2072
+ async evalCommand(cmd, _context, _filename, callback) {
2073
+ const input = cmd.trim();
2074
+ if (!input) {
2075
+ callback(null);
2076
+ return;
2077
+ }
2078
+ const [command, ...args] = input.split(/\s+/);
2079
+ try {
2080
+ this.eventBuffer.startCommand();
2081
+ await this.executeCommand(command.toLowerCase(), args);
2082
+ this.eventBuffer.endCommand();
2083
+ this.replServer.setPrompt(this.getPrompt());
2084
+ callback(null);
2085
+ this.restoreTerminalState();
2086
+ } catch (error2) {
2087
+ this.eventBuffer.endCommand();
2088
+ console.error(chalk9.red(`
2089
+ x Error: ${error2.message}`));
2090
+ this.replServer.setPrompt(this.getPrompt());
2091
+ callback(null);
2092
+ this.restoreTerminalState();
2093
+ }
2094
+ }
2095
+ /**
2096
+ * Restore terminal state after commands that may have altered it.
2097
+ * Some libraries (like ora spinners) pause stdin and disable raw mode.
2098
+ */
2099
+ restoreTerminalState() {
2100
+ if (process.stdin.isPaused()) {
2101
+ process.stdin.resume();
2102
+ }
2103
+ if (process.stdin.isTTY && !process.stdin.isRaw) {
2104
+ process.stdin.setRawMode(true);
2105
+ }
2106
+ this.replServer.displayPrompt();
2107
+ }
2108
+ /**
2109
+ * Execute a command
2110
+ */
2111
+ async executeCommand(command, args) {
2112
+ switch (command) {
2113
+ // Vault management
2114
+ case "vaults":
2115
+ await executeVaults(this.ctx);
2116
+ break;
2117
+ case "vault":
2118
+ await this.switchVault(args);
2119
+ break;
2120
+ case "import":
2121
+ await this.importVault(args);
2122
+ break;
2123
+ case "create":
2124
+ await this.createVault();
2125
+ break;
2126
+ case "info":
2127
+ await executeInfo(this.ctx);
2128
+ break;
2129
+ case "export":
2130
+ await executeExport(this.ctx, { outputPath: args.join(" ") || void 0 });
2131
+ break;
2132
+ case "rename":
2133
+ if (args.length === 0) {
2134
+ console.log(chalk9.yellow("Usage: rename <newName>"));
2135
+ return;
2136
+ }
2137
+ await executeRename(this.ctx, args.join(" "));
2138
+ break;
2139
+ // Balance commands
2140
+ case "balance":
2141
+ case "bal":
2142
+ await this.runBalance(args);
2143
+ break;
2144
+ case "portfolio":
2145
+ await this.runPortfolio(args);
2146
+ break;
2147
+ // Transaction
2148
+ case "send":
2149
+ await this.runSend(args);
2150
+ break;
2151
+ // Chain management
2152
+ case "addresses":
2153
+ await executeAddresses(this.ctx);
2154
+ break;
2155
+ case "chains":
2156
+ await this.runChains(args);
2157
+ break;
2158
+ case "tokens":
2159
+ await this.runTokens(args);
2160
+ break;
2161
+ // Swap commands
2162
+ case "swap-chains":
2163
+ await executeSwapChains(this.ctx);
2164
+ break;
2165
+ case "swap-quote":
2166
+ await this.runSwapQuote(args);
2167
+ break;
2168
+ case "swap":
2169
+ await this.runSwap(args);
2170
+ break;
2171
+ // Shell-only commands
2172
+ case "lock":
2173
+ await executeLock(this.ctx);
2174
+ break;
2175
+ case "unlock":
2176
+ await executeUnlock(this.ctx);
2177
+ break;
2178
+ case "status":
2179
+ await executeStatus(this.ctx);
2180
+ break;
2181
+ // Settings
2182
+ case "currency":
2183
+ await executeCurrency(this.ctx, args[0]);
2184
+ break;
2185
+ case "server":
2186
+ await executeServer(this.ctx);
2187
+ break;
2188
+ case "address-book":
2189
+ await this.runAddressBook(args);
2190
+ break;
2191
+ // Help
2192
+ case "help":
2193
+ case "?":
2194
+ showHelp();
2195
+ break;
2196
+ default:
2197
+ if (command && !command.startsWith(".")) {
2198
+ console.log(chalk9.yellow(`Unknown command: ${command}`));
2199
+ console.log(chalk9.gray('Type "help" for available commands'));
2200
+ }
2201
+ break;
2202
+ }
2203
+ }
2204
+ // ===== Command Helpers =====
2205
+ async switchVault(args) {
2206
+ if (args.length === 0) {
2207
+ console.log(chalk9.yellow("Usage: vault <name>"));
2208
+ console.log(chalk9.gray('Run "vaults" to see available vaults'));
2209
+ return;
2210
+ }
2211
+ const vaultName = args.join(" ");
2212
+ const vault = this.ctx.findVaultByName(vaultName);
2213
+ if (!vault) {
2214
+ console.log(chalk9.red(`Vault not found: ${vaultName}`));
2215
+ console.log(chalk9.gray('Run "vaults" to see available vaults'));
2216
+ return;
2217
+ }
2218
+ await this.ctx.setActiveVault(vault);
2219
+ console.log(chalk9.green(`
2220
+ + Switched to: ${vault.name}`));
2221
+ const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
2222
+ const status = isUnlocked ? chalk9.green("Unlocked") : chalk9.yellow("Locked");
2223
+ console.log(`Status: ${status}`);
2224
+ }
2225
+ async importVault(args) {
2226
+ if (args.length === 0) {
2227
+ console.log(chalk9.yellow("Usage: import <file>"));
2228
+ return;
2229
+ }
2230
+ const filePath = args.join(" ");
2231
+ const vault = await executeImport(this.ctx, filePath);
2232
+ this.ctx.addVault(vault);
2233
+ this.eventBuffer.setupVaultListeners(vault);
2234
+ }
2235
+ async createVault() {
2236
+ const vault = await executeCreate(this.ctx, { type: "fast" });
2237
+ this.ctx.addVault(vault);
2238
+ this.eventBuffer.setupVaultListeners(vault);
2239
+ }
2240
+ async runBalance(args) {
2241
+ const chainStr = args[0];
2242
+ const includeTokens = args.includes("-t") || args.includes("--tokens");
2243
+ await executeBalance(this.ctx, {
2244
+ chain: chainStr ? findChainByName(chainStr) || chainStr : void 0,
2245
+ includeTokens
2246
+ });
2247
+ }
2248
+ async runPortfolio(args) {
2249
+ let currency = "usd";
2250
+ for (let i = 0; i < args.length; i++) {
2251
+ if ((args[i] === "-c" || args[i] === "--currency") && i + 1 < args.length) {
2252
+ currency = args[i + 1].toLowerCase();
2253
+ i++;
2254
+ }
2255
+ }
2256
+ if (!fiatCurrencies3.includes(currency)) {
2257
+ console.log(chalk9.red(`Invalid currency: ${currency}`));
2258
+ console.log(chalk9.yellow(`Supported currencies: ${fiatCurrencies3.join(", ")}`));
2259
+ return;
2260
+ }
2261
+ await executePortfolio(this.ctx, { currency });
2262
+ }
2263
+ async runSend(args) {
2264
+ if (args.length < 3) {
2265
+ console.log(chalk9.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
2266
+ return;
2267
+ }
2268
+ const [chainStr, to, amount, ...rest] = args;
2269
+ const chain = findChainByName(chainStr) || chainStr;
2270
+ let tokenId;
2271
+ let memo;
2272
+ for (let i = 0; i < rest.length; i++) {
2273
+ if (rest[i] === "--token" && i + 1 < rest.length) {
2274
+ tokenId = rest[i + 1];
2275
+ i++;
2276
+ } else if (rest[i] === "--memo" && i + 1 < rest.length) {
2277
+ memo = rest.slice(i + 1).join(" ");
2278
+ break;
2279
+ }
2280
+ }
2281
+ try {
2282
+ await executeSend(this.ctx, { chain, to, amount, tokenId, memo });
2283
+ } catch (err) {
2284
+ if (err.message === "Transaction cancelled by user") {
2285
+ console.log(chalk9.yellow("\nTransaction cancelled"));
2286
+ return;
2287
+ }
2288
+ throw err;
2289
+ }
2290
+ }
2291
+ async runChains(args) {
2292
+ let addChain;
2293
+ let removeChain;
2294
+ for (let i = 0; i < args.length; i++) {
2295
+ if (args[i] === "--add" && i + 1 < args.length) {
2296
+ const chain = findChainByName(args[i + 1]);
2297
+ if (!chain) {
2298
+ console.log(chalk9.red(`Unknown chain: ${args[i + 1]}`));
2299
+ console.log(chalk9.gray("Use tab completion to see available chains"));
2300
+ return;
2301
+ }
2302
+ addChain = chain;
2303
+ i++;
2304
+ } else if (args[i] === "--remove" && i + 1 < args.length) {
2305
+ const chain = findChainByName(args[i + 1]);
2306
+ if (!chain) {
2307
+ console.log(chalk9.red(`Unknown chain: ${args[i + 1]}`));
2308
+ console.log(chalk9.gray("Use tab completion to see available chains"));
2309
+ return;
2310
+ }
2311
+ removeChain = chain;
2312
+ i++;
2313
+ }
2314
+ }
2315
+ await executeChains(this.ctx, { add: addChain, remove: removeChain });
2316
+ }
2317
+ async runTokens(args) {
2318
+ if (args.length === 0) {
2319
+ console.log(chalk9.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
2320
+ return;
2321
+ }
2322
+ const chainStr = args[0];
2323
+ const chain = findChainByName(chainStr) || chainStr;
2324
+ let add;
2325
+ let remove;
2326
+ for (let i = 1; i < args.length; i++) {
2327
+ if (args[i] === "--add" && i + 1 < args.length) {
2328
+ add = args[i + 1];
2329
+ i++;
2330
+ } else if (args[i] === "--remove" && i + 1 < args.length) {
2331
+ remove = args[i + 1];
2332
+ i++;
2333
+ }
2334
+ }
2335
+ await executeTokens(this.ctx, { chain, add, remove });
2336
+ }
2337
+ async runSwapQuote(args) {
2338
+ if (args.length < 3) {
2339
+ console.log(
2340
+ chalk9.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
2341
+ );
2342
+ return;
2343
+ }
2344
+ const [fromChainStr, toChainStr, amountStr, ...rest] = args;
2345
+ const fromChain = findChainByName(fromChainStr) || fromChainStr;
2346
+ const toChain = findChainByName(toChainStr) || toChainStr;
2347
+ const amount = parseFloat(amountStr);
2348
+ let fromToken;
2349
+ let toToken;
2350
+ for (let i = 0; i < rest.length; i++) {
2351
+ if (rest[i] === "--from-token" && i + 1 < rest.length) {
2352
+ fromToken = rest[i + 1];
2353
+ i++;
2354
+ } else if (rest[i] === "--to-token" && i + 1 < rest.length) {
2355
+ toToken = rest[i + 1];
2356
+ i++;
2357
+ }
2358
+ }
2359
+ await executeSwapQuote(this.ctx, { fromChain, toChain, amount, fromToken, toToken });
2360
+ }
2361
+ async runSwap(args) {
2362
+ if (args.length < 3) {
2363
+ console.log(
2364
+ chalk9.yellow(
2365
+ "Usage: swap <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>] [--slippage <pct>]"
2366
+ )
2367
+ );
2368
+ return;
2369
+ }
2370
+ const [fromChainStr, toChainStr, amountStr, ...rest] = args;
2371
+ const fromChain = findChainByName(fromChainStr) || fromChainStr;
2372
+ const toChain = findChainByName(toChainStr) || toChainStr;
2373
+ const amount = parseFloat(amountStr);
2374
+ let fromToken;
2375
+ let toToken;
2376
+ let slippage;
2377
+ for (let i = 0; i < rest.length; i++) {
2378
+ if (rest[i] === "--from-token" && i + 1 < rest.length) {
2379
+ fromToken = rest[i + 1];
2380
+ i++;
2381
+ } else if (rest[i] === "--to-token" && i + 1 < rest.length) {
2382
+ toToken = rest[i + 1];
2383
+ i++;
2384
+ } else if (rest[i] === "--slippage" && i + 1 < rest.length) {
2385
+ slippage = parseFloat(rest[i + 1]);
2386
+ i++;
2387
+ }
2388
+ }
2389
+ try {
2390
+ await executeSwap(this.ctx, { fromChain, toChain, amount, fromToken, toToken, slippage });
2391
+ } catch (err) {
2392
+ if (err.message === "Swap cancelled by user") {
2393
+ console.log(chalk9.yellow("\nSwap cancelled"));
2394
+ return;
2395
+ }
2396
+ throw err;
2397
+ }
2398
+ }
2399
+ async runAddressBook(args) {
2400
+ let add = false;
2401
+ let remove;
2402
+ let chain;
2403
+ for (let i = 0; i < args.length; i++) {
2404
+ if (args[i] === "--add") {
2405
+ add = true;
2406
+ } else if (args[i] === "--remove" && i + 1 < args.length) {
2407
+ remove = args[i + 1];
2408
+ i++;
2409
+ } else if (args[i] === "--chain" && i + 1 < args.length) {
2410
+ chain = findChainByName(args[i + 1]) || args[i + 1];
2411
+ i++;
2412
+ }
2413
+ }
2414
+ await executeAddressBook(this.ctx, { add, remove, chain });
2415
+ }
2416
+ // ===== Setup =====
2417
+ setupReplCommands() {
2418
+ this.replServer.defineCommand("help", {
2419
+ help: "Show available commands",
2420
+ action: () => {
2421
+ showHelp();
2422
+ this.replServer.displayPrompt();
2423
+ }
2424
+ });
2425
+ this.replServer.defineCommand("clear", {
2426
+ help: "Clear the screen",
2427
+ action: () => {
2428
+ console.clear();
2429
+ this.displayVaultList();
2430
+ this.replServer.displayPrompt();
2431
+ }
2432
+ });
2433
+ const originalExit = this.replServer.commands.exit;
2434
+ this.replServer.defineCommand("exit", {
2435
+ help: originalExit.help,
2436
+ action: () => {
2437
+ console.log(chalk9.yellow("\nGoodbye!"));
2438
+ this.ctx.dispose();
2439
+ originalExit.action.call(this.replServer);
2440
+ }
2441
+ });
2442
+ }
2443
+ async loadAllVaults() {
2444
+ const spinner = createReplSafeSpinner("Loading vaults...").start();
2445
+ try {
2446
+ const activeVault = await this.ctx.sdk.getActiveVault();
2447
+ if (activeVault) {
2448
+ this.ctx.addVault(activeVault);
2449
+ await this.ctx.setActiveVault(activeVault);
2450
+ this.eventBuffer.setupVaultListeners(activeVault);
2451
+ }
2452
+ const vaultList = await this.ctx.sdk.listVaults();
2453
+ if (vaultList && vaultList.length > 0) {
2454
+ vaultList.forEach((vault) => {
2455
+ if (!this.ctx.getVaultById(vault.id)) {
2456
+ this.ctx.addVault(vault);
2457
+ this.eventBuffer.setupVaultListeners(vault);
2458
+ }
65
2459
  });
66
- await sdk.initialize();
67
- ctx = new CLIContext(sdk);
68
- // Determine which vault to use (precedence: flag > env var > stored active)
69
- const vaultSelector = vaultOverride || process.env.VULTISIG_VAULT;
70
- let vault = null;
71
- if (vaultSelector) {
72
- vault = await findVaultByNameOrId(sdk, vaultSelector);
73
- if (!vault) {
74
- throw new Error(`Vault not found: "${vaultSelector}"`);
75
- }
2460
+ if (!this.ctx.getActiveVault() && this.ctx.getVaults().size > 0) {
2461
+ const firstVault = this.ctx.getVaults().values().next().value;
2462
+ await this.ctx.setActiveVault(firstVault);
76
2463
  }
77
- else {
78
- vault = await sdk.getActiveVault();
2464
+ spinner.succeed(`Loaded ${this.ctx.getVaults().size} vault(s)`);
2465
+ } else if (this.ctx.getVaults().size > 0) {
2466
+ spinner.succeed(`Loaded ${this.ctx.getVaults().size} vault(s)`);
2467
+ } else {
2468
+ spinner.succeed("No vaults found");
2469
+ }
2470
+ } catch (error2) {
2471
+ if (this.ctx.getVaults().size > 0) {
2472
+ spinner.succeed(`Loaded ${this.ctx.getVaults().size} vault(s)`);
2473
+ } else {
2474
+ spinner.fail("Failed to load vaults");
2475
+ throw error2;
2476
+ }
2477
+ }
2478
+ }
2479
+ getPrompt() {
2480
+ const vault = this.ctx.getActiveVault();
2481
+ if (!vault) return chalk9.cyan("wallet> ");
2482
+ const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
2483
+ const status = isUnlocked ? chalk9.green("\u{1F513}") : chalk9.yellow("\u{1F512}");
2484
+ return chalk9.cyan(`wallet[${vault.name}]${status}> `);
2485
+ }
2486
+ displayVaultList() {
2487
+ const vaults = Array.from(this.ctx.getVaults().values());
2488
+ const activeVault = this.ctx.getActiveVault();
2489
+ if (vaults.length === 0) {
2490
+ console.log(chalk9.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
2491
+ return;
2492
+ }
2493
+ console.log(chalk9.cyan("Loaded Vaults:\n"));
2494
+ vaults.forEach((vault) => {
2495
+ const isActive = vault.id === activeVault?.id;
2496
+ const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
2497
+ const activeMarker = isActive ? chalk9.green(" (active)") : "";
2498
+ const lockIcon = isUnlocked ? chalk9.green("\u{1F513}") : chalk9.yellow("\u{1F512}");
2499
+ console.log(` ${lockIcon} ${vault.name}${activeMarker} - ${vault.type}`);
2500
+ });
2501
+ console.log();
2502
+ }
2503
+ };
2504
+
2505
+ // src/lib/errors.ts
2506
+ import chalk10 from "chalk";
2507
+
2508
+ // src/lib/version.ts
2509
+ import chalk11 from "chalk";
2510
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
2511
+ import { homedir } from "os";
2512
+ import { join } from "path";
2513
+ var cachedVersion = null;
2514
+ function getVersion() {
2515
+ if (cachedVersion) return cachedVersion;
2516
+ try {
2517
+ const packagePath = new URL("../../package.json", import.meta.url);
2518
+ const pkg = JSON.parse(readFileSync(packagePath, "utf-8"));
2519
+ cachedVersion = pkg.version;
2520
+ return cachedVersion;
2521
+ } catch {
2522
+ cachedVersion = "0.1.0-beta.1";
2523
+ return cachedVersion;
2524
+ }
2525
+ }
2526
+ var CACHE_DIR = join(homedir(), ".vultisig", "cache");
2527
+ var VERSION_CACHE_FILE = join(CACHE_DIR, "version-check.json");
2528
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
2529
+ function readVersionCache() {
2530
+ try {
2531
+ if (!existsSync(VERSION_CACHE_FILE)) return null;
2532
+ const data = readFileSync(VERSION_CACHE_FILE, "utf-8");
2533
+ return JSON.parse(data);
2534
+ } catch {
2535
+ return null;
2536
+ }
2537
+ }
2538
+ function writeVersionCache(cache) {
2539
+ try {
2540
+ if (!existsSync(CACHE_DIR)) {
2541
+ mkdirSync(CACHE_DIR, { recursive: true });
2542
+ }
2543
+ writeFileSync(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
2544
+ } catch {
2545
+ }
2546
+ }
2547
+ async function fetchLatestVersion() {
2548
+ try {
2549
+ const controller = new AbortController();
2550
+ const timeout = setTimeout(() => controller.abort(), 5e3);
2551
+ const response = await fetch("https://registry.npmjs.org/@vultisig/cli/latest", {
2552
+ signal: controller.signal,
2553
+ headers: {
2554
+ Accept: "application/json"
2555
+ }
2556
+ });
2557
+ clearTimeout(timeout);
2558
+ if (!response.ok) return null;
2559
+ const data = await response.json();
2560
+ return data.version ?? null;
2561
+ } catch {
2562
+ return null;
2563
+ }
2564
+ }
2565
+ function isNewerVersion(v1, v2) {
2566
+ const parse = (v) => {
2567
+ const clean = v.replace(/^v/, "").replace(/-.*$/, "");
2568
+ return clean.split(".").map((n) => parseInt(n, 10) || 0);
2569
+ };
2570
+ const p1 = parse(v1);
2571
+ const p2 = parse(v2);
2572
+ for (let i = 0; i < 3; i++) {
2573
+ const n1 = p1[i] ?? 0;
2574
+ const n2 = p2[i] ?? 0;
2575
+ if (n2 > n1) return true;
2576
+ if (n2 < n1) return false;
2577
+ }
2578
+ if (v1.includes("-") && !v2.includes("-")) return true;
2579
+ return false;
2580
+ }
2581
+ async function checkForUpdates() {
2582
+ if (process.env.VULTISIG_NO_UPDATE_CHECK === "1") {
2583
+ return null;
2584
+ }
2585
+ const currentVersion = getVersion();
2586
+ const cache = readVersionCache();
2587
+ if (cache && Date.now() - cache.lastCheck < CACHE_TTL_MS) {
2588
+ return {
2589
+ currentVersion,
2590
+ latestVersion: cache.latestVersion,
2591
+ updateAvailable: cache.latestVersion ? isNewerVersion(currentVersion, cache.latestVersion) : false
2592
+ };
2593
+ }
2594
+ const latestVersion = await fetchLatestVersion();
2595
+ writeVersionCache({
2596
+ lastCheck: Date.now(),
2597
+ latestVersion
2598
+ });
2599
+ return {
2600
+ currentVersion,
2601
+ latestVersion,
2602
+ updateAvailable: latestVersion ? isNewerVersion(currentVersion, latestVersion) : false
2603
+ };
2604
+ }
2605
+ function formatVersionShort() {
2606
+ return `vultisig/${getVersion()}`;
2607
+ }
2608
+ function formatVersionDetailed() {
2609
+ const lines = [];
2610
+ lines.push(chalk11.bold(`Vultisig CLI v${getVersion()}`));
2611
+ lines.push("");
2612
+ lines.push(` Node.js: ${process.version}`);
2613
+ lines.push(` Platform: ${process.platform}-${process.arch}`);
2614
+ lines.push(` Config: ~/.vultisig/`);
2615
+ return lines.join("\n");
2616
+ }
2617
+ function detectInstallMethod() {
2618
+ const execPath = process.execPath;
2619
+ if (execPath.includes("homebrew") || execPath.includes("Cellar")) {
2620
+ return "homebrew";
2621
+ }
2622
+ if (process.env.npm_execpath?.includes("yarn")) {
2623
+ return "yarn";
2624
+ }
2625
+ if (process.env.npm_config_user_agent?.includes("npm")) {
2626
+ return "npm";
2627
+ }
2628
+ if (execPath.includes("node_modules")) {
2629
+ return "npm";
2630
+ }
2631
+ return "unknown";
2632
+ }
2633
+ function getUpdateCommand() {
2634
+ const method = detectInstallMethod();
2635
+ switch (method) {
2636
+ case "npm":
2637
+ return "npm update -g @vultisig/cli";
2638
+ case "yarn":
2639
+ return "yarn global upgrade @vultisig/cli";
2640
+ case "homebrew":
2641
+ return "brew upgrade vultisig";
2642
+ default:
2643
+ return "npm update -g @vultisig/cli";
2644
+ }
2645
+ }
2646
+
2647
+ // src/lib/completion.ts
2648
+ import { homedir as homedir2 } from "os";
2649
+ import { join as join2 } from "path";
2650
+ import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
2651
+ var tabtab = null;
2652
+ async function getTabtab() {
2653
+ if (!tabtab) {
2654
+ try {
2655
+ tabtab = await import("tabtab");
2656
+ if (tabtab.default) {
2657
+ tabtab = tabtab.default;
2658
+ }
2659
+ } catch {
2660
+ return null;
2661
+ }
2662
+ }
2663
+ return tabtab;
2664
+ }
2665
+ var COMMANDS2 = [
2666
+ "create",
2667
+ "import",
2668
+ "verify",
2669
+ "balance",
2670
+ "send",
2671
+ "portfolio",
2672
+ "currency",
2673
+ "server",
2674
+ "export",
2675
+ "addresses",
2676
+ "address-book",
2677
+ "chains",
2678
+ "vaults",
2679
+ "switch",
2680
+ "rename",
2681
+ "info",
2682
+ "tokens",
2683
+ "swap-chains",
2684
+ "swap-quote",
2685
+ "swap",
2686
+ "completion",
2687
+ "version",
2688
+ "update"
2689
+ ];
2690
+ var CHAINS = [
2691
+ "Ethereum",
2692
+ "Bitcoin",
2693
+ "Solana",
2694
+ "Polygon",
2695
+ "Arbitrum",
2696
+ "Optimism",
2697
+ "Avalanche",
2698
+ "BSC",
2699
+ "Base",
2700
+ "Cosmos",
2701
+ "THORChain",
2702
+ "Maya",
2703
+ "Dydx",
2704
+ "Kujira",
2705
+ "Sui",
2706
+ "Polkadot",
2707
+ "Ripple",
2708
+ "Dogecoin",
2709
+ "Litecoin",
2710
+ "Dash",
2711
+ "Zcash"
2712
+ ];
2713
+ function getVaultNames() {
2714
+ try {
2715
+ const vaultDir = join2(homedir2(), ".vultisig", "vaults");
2716
+ if (!existsSync2(vaultDir)) return [];
2717
+ const { readdirSync } = __require("fs");
2718
+ const files = readdirSync(vaultDir);
2719
+ const names = [];
2720
+ for (const file of files) {
2721
+ if (file.startsWith("vault:") && file.endsWith(".json")) {
2722
+ try {
2723
+ const content = readFileSync2(join2(vaultDir, file), "utf-8");
2724
+ const vault = JSON.parse(content);
2725
+ if (vault.name) names.push(vault.name);
2726
+ if (vault.id) names.push(vault.id);
2727
+ } catch {
79
2728
  }
80
- if (vault) {
81
- await ctx.setActiveVault(vault);
82
- setupVaultEvents(vault);
2729
+ }
2730
+ }
2731
+ return names;
2732
+ } catch {
2733
+ return [];
2734
+ }
2735
+ }
2736
+ async function handleCompletion() {
2737
+ const tt = await getTabtab();
2738
+ if (!tt) return false;
2739
+ const env = tt.parseEnv(process.env);
2740
+ if (!env.complete) return false;
2741
+ const { line, lastPartial } = env;
2742
+ const parts = line.split(/\s+/).filter(Boolean);
2743
+ const cmd = parts[1];
2744
+ let completions = [];
2745
+ if (!cmd || parts.length === 1 || parts.length === 2 && lastPartial) {
2746
+ completions = COMMANDS2.filter((c) => c.startsWith(lastPartial || ""));
2747
+ } else {
2748
+ switch (cmd) {
2749
+ case "balance":
2750
+ case "tokens":
2751
+ case "send":
2752
+ if (parts.length === 2 || parts.length === 3 && lastPartial) {
2753
+ completions = CHAINS.filter((c) => c.toLowerCase().startsWith((lastPartial || "").toLowerCase()));
2754
+ }
2755
+ break;
2756
+ case "switch":
2757
+ case "verify":
2758
+ if (parts.length === 2 || parts.length === 3 && lastPartial) {
2759
+ completions = getVaultNames().filter((n) => n.toLowerCase().startsWith((lastPartial || "").toLowerCase()));
2760
+ }
2761
+ break;
2762
+ case "import":
2763
+ case "export":
2764
+ break;
2765
+ case "chains":
2766
+ if (lastPartial?.startsWith("-")) {
2767
+ completions = ["--add", "--remove"].filter((o) => o.startsWith(lastPartial));
2768
+ } else if (parts.includes("--add") || parts.includes("--remove")) {
2769
+ completions = CHAINS.filter((c) => c.toLowerCase().startsWith((lastPartial || "").toLowerCase()));
2770
+ }
2771
+ break;
2772
+ case "swap":
2773
+ case "swap-quote":
2774
+ if (parts.length <= 3 || parts.length === 4 && lastPartial) {
2775
+ completions = CHAINS.filter((c) => c.toLowerCase().startsWith((lastPartial || "").toLowerCase()));
83
2776
  }
2777
+ break;
2778
+ case "completion":
2779
+ if (parts.length === 2 || parts.length === 3 && lastPartial) {
2780
+ completions = ["install", "uninstall", "bash", "zsh", "fish"].filter(
2781
+ (s) => s.startsWith((lastPartial || "").toLowerCase())
2782
+ );
2783
+ }
2784
+ break;
84
2785
  }
85
- return ctx;
86
- }
87
- // ============================================================================
88
- // Commands
89
- // ============================================================================
90
- // Command: Create new vault
91
- program
92
- .command('create')
93
- .description('Create a new vault')
94
- .option('--type <type>', 'Vault type: fast or secure', 'fast')
95
- .option('--name <name>', 'Vault name')
96
- .option('--password <password>', 'Vault password')
97
- .option('--email <email>', 'Email for verification (fast vault)')
98
- .option('--code <code>', 'Verification code (if already received)')
99
- .option('--threshold <m>', 'Signing threshold (secure vault)')
100
- .option('--shares <n>', 'Total shares (secure vault)')
101
- .action(withExit(async (options) => {
102
- const context = await init(program.opts().vault);
103
- await executeCreate(context, {
2786
+ }
2787
+ if (lastPartial?.startsWith("-")) {
2788
+ const flags = ["-h", "--help", "-v", "--version", "-i", "--interactive", "--debug"];
2789
+ completions = [...completions, ...flags.filter((f) => f.startsWith(lastPartial))];
2790
+ }
2791
+ if (completions.length > 0) {
2792
+ tt.log(completions);
2793
+ }
2794
+ return true;
2795
+ }
2796
+ function setupCompletionCommand(program2) {
2797
+ program2.command("completion [shell]").description("Generate shell completion scripts").option("--install", "Install completion for current shell").option("--uninstall", "Remove completion scripts").action(async (shell, options) => {
2798
+ const tt = await getTabtab();
2799
+ if (!tt) {
2800
+ console.error("Shell completion is not available. Install tabtab: npm install -g tabtab");
2801
+ process.exit(1);
2802
+ }
2803
+ if (options.install) {
2804
+ try {
2805
+ await tt.install({
2806
+ name: "vultisig",
2807
+ completer: "vultisig"
2808
+ });
2809
+ await tt.install({
2810
+ name: "vsig",
2811
+ completer: "vsig"
2812
+ });
2813
+ console.log("Shell completion installed successfully for vultisig and vsig!");
2814
+ console.log("Restart your shell or run: source ~/.bashrc (or ~/.zshrc)");
2815
+ } catch (err) {
2816
+ console.error(`Failed to install completion: ${err.message}`);
2817
+ process.exit(1);
2818
+ }
2819
+ return;
2820
+ }
2821
+ if (options.uninstall) {
2822
+ try {
2823
+ await tt.uninstall({
2824
+ name: "vultisig"
2825
+ });
2826
+ await tt.uninstall({
2827
+ name: "vsig"
2828
+ });
2829
+ console.log("Shell completion uninstalled successfully!");
2830
+ } catch (err) {
2831
+ console.error(`Failed to uninstall completion: ${err.message}`);
2832
+ process.exit(1);
2833
+ }
2834
+ return;
2835
+ }
2836
+ if (shell) {
2837
+ const scripts = {
2838
+ bash: getBashCompletionScript(),
2839
+ zsh: getZshCompletionScript(),
2840
+ fish: getFishCompletionScript()
2841
+ };
2842
+ const script = scripts[shell.toLowerCase()];
2843
+ if (script) {
2844
+ console.log(script);
2845
+ } else {
2846
+ console.error(`Unknown shell: ${shell}. Supported: bash, zsh, fish`);
2847
+ process.exit(1);
2848
+ }
2849
+ return;
2850
+ }
2851
+ console.log("Usage: vultisig completion [shell] [options]");
2852
+ console.log("");
2853
+ console.log("Generate shell completion scripts");
2854
+ console.log("");
2855
+ console.log("Arguments:");
2856
+ console.log(" shell Shell type: bash, zsh, fish");
2857
+ console.log("");
2858
+ console.log("Options:");
2859
+ console.log(" --install Install completion for current shell");
2860
+ console.log(" --uninstall Remove completion scripts");
2861
+ console.log("");
2862
+ console.log("Examples:");
2863
+ console.log(" vultisig completion --install");
2864
+ console.log(" vultisig completion bash >> ~/.bashrc");
2865
+ console.log(" vultisig completion zsh >> ~/.zshrc");
2866
+ });
2867
+ }
2868
+ function getBashCompletionScript() {
2869
+ return `
2870
+ # vultisig bash completion
2871
+ _vultisig_completions() {
2872
+ local cur="\${COMP_WORDS[COMP_CWORD]}"
2873
+ local cmd="\${COMP_WORDS[1]}"
2874
+
2875
+ if [ "\${COMP_CWORD}" -eq 1 ]; then
2876
+ COMPREPLY=($(compgen -W "${COMMANDS2.join(" ")}" -- "\${cur}"))
2877
+ return
2878
+ fi
2879
+
2880
+ case "\${cmd}" in
2881
+ balance|tokens|send)
2882
+ COMPREPLY=($(compgen -W "${CHAINS.join(" ")}" -- "\${cur}"))
2883
+ ;;
2884
+ chains)
2885
+ COMPREPLY=($(compgen -W "--add --remove ${CHAINS.join(" ")}" -- "\${cur}"))
2886
+ ;;
2887
+ swap|swap-quote)
2888
+ COMPREPLY=($(compgen -W "${CHAINS.join(" ")}" -- "\${cur}"))
2889
+ ;;
2890
+ completion)
2891
+ COMPREPLY=($(compgen -W "install uninstall bash zsh fish" -- "\${cur}"))
2892
+ ;;
2893
+ import|export)
2894
+ COMPREPLY=($(compgen -f -- "\${cur}"))
2895
+ ;;
2896
+ *)
2897
+ COMPREPLY=($(compgen -W "-h --help" -- "\${cur}"))
2898
+ ;;
2899
+ esac
2900
+ }
2901
+
2902
+ complete -F _vultisig_completions vultisig
2903
+ complete -F _vultisig_completions vsig
2904
+ `.trim();
2905
+ }
2906
+ function getZshCompletionScript() {
2907
+ return `
2908
+ #compdef vultisig vsig
2909
+
2910
+ _vultisig() {
2911
+ local -a commands chains
2912
+ commands=(${COMMANDS2.map((c) => `'${c}:${c} command'`).join(" ")})
2913
+ chains=(${CHAINS.join(" ")})
2914
+
2915
+ _arguments -C \\
2916
+ '1: :->command' \\
2917
+ '*: :->args'
2918
+
2919
+ case "$state" in
2920
+ command)
2921
+ _describe 'command' commands
2922
+ ;;
2923
+ args)
2924
+ case "$words[2]" in
2925
+ balance|tokens|send|swap|swap-quote)
2926
+ _describe 'chain' chains
2927
+ ;;
2928
+ completion)
2929
+ _describe 'shell' '(install uninstall bash zsh fish)'
2930
+ ;;
2931
+ import|export)
2932
+ _files
2933
+ ;;
2934
+ esac
2935
+ ;;
2936
+ esac
2937
+ }
2938
+
2939
+ _vultisig
2940
+ `.trim();
2941
+ }
2942
+ function getFishCompletionScript() {
2943
+ const commands = ["vultisig", "vsig"];
2944
+ const commandCompletions = commands.flatMap((cmd) => COMMANDS2.map((c) => `complete -c ${cmd} -n "__fish_use_subcommand" -a "${c}"`)).join("\n");
2945
+ const chainCompletions = commands.flatMap(
2946
+ (cmd) => CHAINS.map(
2947
+ (c) => `complete -c ${cmd} -n "__fish_seen_subcommand_from balance tokens send swap swap-quote" -a "${c}"`
2948
+ )
2949
+ ).join("\n");
2950
+ return `
2951
+ # vultisig/vsig fish completion
2952
+ ${commandCompletions}
2953
+ ${chainCompletions}
2954
+ complete -c vultisig -n "__fish_seen_subcommand_from completion" -a "install uninstall bash zsh fish"
2955
+ complete -c vsig -n "__fish_seen_subcommand_from completion" -a "install uninstall bash zsh fish"
2956
+ complete -c vultisig -n "__fish_seen_subcommand_from import export" -a "(__fish_complete_path)"
2957
+ complete -c vsig -n "__fish_seen_subcommand_from import export" -a "(__fish_complete_path)"
2958
+ `.trim();
2959
+ }
2960
+
2961
+ // src/index.ts
2962
+ (async () => {
2963
+ const handled = await handleCompletion();
2964
+ if (handled) process.exit(0);
2965
+ })();
2966
+ var ctx;
2967
+ program.name("vultisig").description("Vultisig CLI - Secure multi-party crypto wallet").version(formatVersionShort(), "-v, --version", "Show version").option("--debug", "Enable debug output").option("--silent", "Suppress informational output, show only results").option("-o, --output <format>", "Output format: table, json (default: table)", "table").option("-i, --interactive", "Start interactive shell mode").option("--vault <nameOrId>", "Specify vault by name or ID").hook("preAction", (thisCommand) => {
2968
+ const opts = thisCommand.opts();
2969
+ initOutputMode({ silent: opts.silent, output: opts.output });
2970
+ });
2971
+ async function findVaultByNameOrId(sdk, nameOrId) {
2972
+ const vaults = await sdk.listVaults();
2973
+ const byId = vaults.find((v) => v.id === nameOrId);
2974
+ if (byId) return byId;
2975
+ const byName = vaults.find((v) => v.name.toLowerCase() === nameOrId.toLowerCase());
2976
+ if (byName) return byName;
2977
+ const byPartialId = vaults.find((v) => v.id.startsWith(nameOrId));
2978
+ if (byPartialId) return byPartialId;
2979
+ return null;
2980
+ }
2981
+ async function init(vaultOverride) {
2982
+ if (!ctx) {
2983
+ const sdk = new Vultisig3({
2984
+ onPasswordRequired: createPasswordCallback()
2985
+ });
2986
+ await sdk.initialize();
2987
+ ctx = new CLIContext(sdk);
2988
+ const vaultSelector = vaultOverride || process.env.VULTISIG_VAULT;
2989
+ let vault = null;
2990
+ if (vaultSelector) {
2991
+ vault = await findVaultByNameOrId(sdk, vaultSelector);
2992
+ if (!vault) {
2993
+ throw new Error(`Vault not found: "${vaultSelector}"`);
2994
+ }
2995
+ } else {
2996
+ vault = await sdk.getActiveVault();
2997
+ }
2998
+ if (vault) {
2999
+ await ctx.setActiveVault(vault);
3000
+ setupVaultEvents(vault);
3001
+ }
3002
+ }
3003
+ return ctx;
3004
+ }
3005
+ program.command("create").description("Create a new vault").option("--type <type>", "Vault type: fast or secure", "fast").option("--name <name>", "Vault name").option("--password <password>", "Vault password").option("--email <email>", "Email for verification (fast vault)").option("--code <code>", "Verification code (if already received)").option("--threshold <m>", "Signing threshold (secure vault)").option("--shares <n>", "Total shares (secure vault)").action(
3006
+ withExit(
3007
+ async (options) => {
3008
+ const context = await init(program.opts().vault);
3009
+ await executeCreate(context, {
104
3010
  type: options.type,
105
3011
  name: options.name,
106
3012
  password: options.password,
107
3013
  email: options.email,
108
3014
  code: options.code,
109
- threshold: options.threshold ? parseInt(options.threshold, 10) : undefined,
110
- shares: options.shares ? parseInt(options.shares, 10) : undefined,
111
- });
112
- }));
113
- // Command: Import vault from file
114
- program
115
- .command('import <file>')
116
- .description('Import vault from .vult file')
117
- .action(withExit(async (file) => {
3015
+ threshold: options.threshold ? parseInt(options.threshold, 10) : void 0,
3016
+ shares: options.shares ? parseInt(options.shares, 10) : void 0
3017
+ });
3018
+ }
3019
+ )
3020
+ );
3021
+ program.command("import <file>").description("Import vault from .vult file").action(
3022
+ withExit(async (file) => {
118
3023
  const context = await init(program.opts().vault);
119
3024
  await executeImport(context, file);
120
- }));
121
- // Command: Verify vault with email code
122
- program
123
- .command('verify <vaultId>')
124
- .description('Verify vault with email verification code')
125
- .option('-r, --resend', 'Resend verification email')
126
- .option('--code <code>', 'Verification code')
127
- .action(withExit(async (vaultId, options) => {
3025
+ })
3026
+ );
3027
+ program.command("verify <vaultId>").description("Verify vault with email verification code").option("-r, --resend", "Resend verification email").option("--code <code>", "Verification code").action(
3028
+ withExit(async (vaultId, options) => {
128
3029
  const context = await init(program.opts().vault);
129
3030
  const verified = await executeVerify(context, vaultId, options);
130
3031
  if (!verified) {
131
- const err = new Error('Verification failed');
132
- err.exitCode = 1;
133
- throw err;
3032
+ const err = new Error("Verification failed");
3033
+ err.exitCode = 1;
3034
+ throw err;
134
3035
  }
135
- }));
136
- // Command: Show balances
137
- program
138
- .command('balance [chain]')
139
- .description('Show balance for a chain or all chains')
140
- .option('-t, --tokens', 'Include token balances')
141
- .action(withExit(async (chainStr, options) => {
3036
+ })
3037
+ );
3038
+ program.command("balance [chain]").description("Show balance for a chain or all chains").option("-t, --tokens", "Include token balances").action(
3039
+ withExit(async (chainStr, options) => {
142
3040
  const context = await init(program.opts().vault);
143
3041
  await executeBalance(context, {
144
- chain: chainStr ? findChainByName(chainStr) || chainStr : undefined,
145
- includeTokens: options.tokens,
146
- });
147
- }));
148
- // Command: Send transaction
149
- program
150
- .command('send <chain> <to> <amount>')
151
- .description('Send tokens to an address')
152
- .option('--token <tokenId>', 'Token to send (default: native)')
153
- .option('--memo <memo>', 'Transaction memo')
154
- .option('-y, --yes', 'Skip confirmation prompt')
155
- .action(withExit(async (chainStr, to, amount, options) => {
156
- const context = await init(program.opts().vault);
157
- try {
3042
+ chain: chainStr ? findChainByName(chainStr) || chainStr : void 0,
3043
+ includeTokens: options.tokens
3044
+ });
3045
+ })
3046
+ );
3047
+ program.command("send <chain> <to> <amount>").description("Send tokens to an address").option("--token <tokenId>", "Token to send (default: native)").option("--memo <memo>", "Transaction memo").option("-y, --yes", "Skip confirmation prompt").action(
3048
+ withExit(
3049
+ async (chainStr, to, amount, options) => {
3050
+ const context = await init(program.opts().vault);
3051
+ try {
158
3052
  await executeSend(context, {
159
- chain: findChainByName(chainStr) || chainStr,
160
- to,
161
- amount,
162
- tokenId: options.token,
163
- memo: options.memo,
164
- yes: options.yes,
3053
+ chain: findChainByName(chainStr) || chainStr,
3054
+ to,
3055
+ amount,
3056
+ tokenId: options.token,
3057
+ memo: options.memo,
3058
+ yes: options.yes
165
3059
  });
166
- }
167
- catch (err) {
168
- if (err.message === 'Transaction cancelled by user') {
169
- warn('\nx Transaction cancelled');
170
- return;
3060
+ } catch (err) {
3061
+ if (err.message === "Transaction cancelled by user") {
3062
+ warn("\nx Transaction cancelled");
3063
+ return;
171
3064
  }
172
3065
  throw err;
3066
+ }
173
3067
  }
174
- }));
175
- // Command: Show portfolio value
176
- program
177
- .command('portfolio')
178
- .description('Show total portfolio value')
179
- .option('-c, --currency <currency>', 'Fiat currency (usd, eur, gbp, etc.)', 'usd')
180
- .action(withExit(async (options) => {
3068
+ )
3069
+ );
3070
+ program.command("portfolio").description("Show total portfolio value").option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").action(
3071
+ withExit(async (options) => {
181
3072
  const context = await init(program.opts().vault);
182
3073
  await executePortfolio(context, { currency: options.currency.toLowerCase() });
183
- }));
184
- // Command: Manage currency
185
- program
186
- .command('currency [newCurrency]')
187
- .description('View or set the vault currency preference')
188
- .action(withExit(async (newCurrency) => {
3074
+ })
3075
+ );
3076
+ program.command("currency [newCurrency]").description("View or set the vault currency preference").action(
3077
+ withExit(async (newCurrency) => {
189
3078
  const context = await init(program.opts().vault);
190
3079
  await executeCurrency(context, newCurrency);
191
- }));
192
- // Command: Server status
193
- program
194
- .command('server')
195
- .description('Check server connectivity and status')
196
- .action(withExit(async () => {
3080
+ })
3081
+ );
3082
+ program.command("server").description("Check server connectivity and status").action(
3083
+ withExit(async () => {
197
3084
  const context = await init(program.opts().vault);
198
3085
  await executeServer(context);
199
- }));
200
- // Command: Export vault
201
- program
202
- .command('export [path]')
203
- .description('Export vault to file')
204
- .option('--encrypt', 'Encrypt the export with a password')
205
- .option('--no-encrypt', 'Export without encryption')
206
- .option('--password <password>', 'Password for encryption')
207
- .action(withExit(async (path, options) => {
3086
+ })
3087
+ );
3088
+ program.command("export [path]").description("Export vault to file").option("--encrypt", "Encrypt the export with a password").option("--no-encrypt", "Export without encryption").option("--password <password>", "Password for encryption").action(
3089
+ withExit(async (path2, options) => {
208
3090
  const context = await init(program.opts().vault);
209
3091
  await executeExport(context, {
210
- outputPath: path,
211
- encrypt: options.encrypt,
212
- password: options.password,
3092
+ outputPath: path2,
3093
+ encrypt: options.encrypt,
3094
+ password: options.password
213
3095
  });
214
- }));
215
- // Command: Show addresses
216
- program
217
- .command('addresses')
218
- .description('Show all vault addresses')
219
- .action(withExit(async () => {
3096
+ })
3097
+ );
3098
+ program.command("addresses").description("Show all vault addresses").action(
3099
+ withExit(async () => {
220
3100
  const context = await init(program.opts().vault);
221
3101
  await executeAddresses(context);
222
- }));
223
- // Command: Manage address book
224
- program
225
- .command('address-book')
226
- .description('Manage address book entries')
227
- .option('--add', 'Add a new address book entry')
228
- .option('--remove <address>', 'Remove an address from the address book')
229
- .option('--chain <chain>', 'Chain for the address (for --add or --remove)')
230
- .option('--address <address>', 'Address to add (for --add)')
231
- .option('--name <name>', 'Name/label for the address (for --add)')
232
- .action(withExit(async (options) => {
3102
+ })
3103
+ );
3104
+ program.command("address-book").description("Manage address book entries").option("--add", "Add a new address book entry").option("--remove <address>", "Remove an address from the address book").option("--chain <chain>", "Chain for the address (for --add or --remove)").option("--address <address>", "Address to add (for --add)").option("--name <name>", "Name/label for the address (for --add)").action(
3105
+ withExit(async (options) => {
233
3106
  const context = await init(program.opts().vault);
234
3107
  await executeAddressBook(context, {
235
- add: options.add,
236
- remove: options.remove,
237
- chain: options.chain ? findChainByName(options.chain) || options.chain : undefined,
238
- address: options.address,
239
- name: options.name,
3108
+ add: options.add,
3109
+ remove: options.remove,
3110
+ chain: options.chain ? findChainByName(options.chain) || options.chain : void 0,
3111
+ address: options.address,
3112
+ name: options.name
240
3113
  });
241
- }));
242
- // Command: Manage chains
243
- program
244
- .command('chains')
245
- .description('List and manage chains')
246
- .option('--add <chain>', 'Add a chain')
247
- .option('--remove <chain>', 'Remove a chain')
248
- .action(withExit(async (options) => {
3114
+ })
3115
+ );
3116
+ program.command("chains").description("List and manage chains").option("--add <chain>", "Add a chain").option("--remove <chain>", "Remove a chain").action(
3117
+ withExit(async (options) => {
249
3118
  const context = await init(program.opts().vault);
250
3119
  await executeChains(context, {
251
- add: options.add ? findChainByName(options.add) || options.add : undefined,
252
- remove: options.remove ? findChainByName(options.remove) || options.remove : undefined,
253
- });
254
- }));
255
- // Command: List all vaults
256
- program
257
- .command('vaults')
258
- .description('List all stored vaults')
259
- .action(withExit(async () => {
3120
+ add: options.add ? findChainByName(options.add) || options.add : void 0,
3121
+ remove: options.remove ? findChainByName(options.remove) || options.remove : void 0
3122
+ });
3123
+ })
3124
+ );
3125
+ program.command("vaults").description("List all stored vaults").action(
3126
+ withExit(async () => {
260
3127
  const context = await init(program.opts().vault);
261
3128
  await executeVaults(context);
262
- }));
263
- // Command: Switch active vault
264
- program
265
- .command('switch <vaultId>')
266
- .description('Switch to a different vault')
267
- .action(withExit(async (vaultId) => {
3129
+ })
3130
+ );
3131
+ program.command("switch <vaultId>").description("Switch to a different vault").action(
3132
+ withExit(async (vaultId) => {
268
3133
  const context = await init(program.opts().vault);
269
3134
  await executeSwitch(context, vaultId);
270
- }));
271
- // Command: Rename vault
272
- program
273
- .command('rename <newName>')
274
- .description('Rename the active vault')
275
- .action(withExit(async (newName) => {
3135
+ })
3136
+ );
3137
+ program.command("rename <newName>").description("Rename the active vault").action(
3138
+ withExit(async (newName) => {
276
3139
  const context = await init(program.opts().vault);
277
3140
  await executeRename(context, newName);
278
- }));
279
- // Command: Show vault info
280
- program
281
- .command('info')
282
- .description('Show detailed vault information')
283
- .action(withExit(async () => {
3141
+ })
3142
+ );
3143
+ program.command("info").description("Show detailed vault information").action(
3144
+ withExit(async () => {
284
3145
  const context = await init(program.opts().vault);
285
3146
  await executeInfo(context);
286
- }));
287
- // Command: Manage tokens
288
- program
289
- .command('tokens <chain>')
290
- .description('List and manage tokens for a chain')
291
- .option('--add <contractAddress>', 'Add a token by contract address')
292
- .option('--remove <tokenId>', 'Remove a token by ID')
293
- .option('--symbol <symbol>', 'Token symbol (for --add)')
294
- .option('--name <name>', 'Token name (for --add)')
295
- .option('--decimals <decimals>', 'Token decimals (for --add)', '18')
296
- .action(withExit(async (chainStr, options) => {
297
- const context = await init(program.opts().vault);
298
- await executeTokens(context, {
3147
+ })
3148
+ );
3149
+ 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(
3150
+ withExit(
3151
+ async (chainStr, options) => {
3152
+ const context = await init(program.opts().vault);
3153
+ await executeTokens(context, {
299
3154
  chain: findChainByName(chainStr) || chainStr,
300
3155
  add: options.add,
301
3156
  remove: options.remove,
302
3157
  symbol: options.symbol,
303
3158
  name: options.name,
304
- decimals: options.decimals ? parseInt(options.decimals, 10) : undefined,
305
- });
306
- }));
307
- // ============================================================================
308
- // Swap Commands
309
- // ============================================================================
310
- // Command: List supported swap chains
311
- program
312
- .command('swap-chains')
313
- .description('List chains that support swaps')
314
- .action(withExit(async () => {
3159
+ decimals: options.decimals ? parseInt(options.decimals, 10) : void 0
3160
+ });
3161
+ }
3162
+ )
3163
+ );
3164
+ program.command("swap-chains").description("List chains that support swaps").action(
3165
+ withExit(async () => {
315
3166
  const context = await init(program.opts().vault);
316
3167
  await executeSwapChains(context);
317
- }));
318
- // Command: Get swap quote
319
- program
320
- .command('swap-quote <fromChain> <toChain> <amount>')
321
- .description('Get a swap quote without executing')
322
- .option('--from-token <address>', 'Token address to swap from (default: native)')
323
- .option('--to-token <address>', 'Token address to swap to (default: native)')
324
- .action(withExit(async (fromChainStr, toChainStr, amountStr, options) => {
325
- const context = await init(program.opts().vault);
326
- await executeSwapQuote(context, {
3168
+ })
3169
+ );
3170
+ program.command("swap-quote <fromChain> <toChain> <amount>").description("Get a swap quote without executing").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").action(
3171
+ withExit(
3172
+ async (fromChainStr, toChainStr, amountStr, options) => {
3173
+ const context = await init(program.opts().vault);
3174
+ await executeSwapQuote(context, {
327
3175
  fromChain: findChainByName(fromChainStr) || fromChainStr,
328
3176
  toChain: findChainByName(toChainStr) || toChainStr,
329
3177
  amount: parseFloat(amountStr),
330
3178
  fromToken: options.fromToken,
331
- toToken: options.toToken,
332
- });
333
- }));
334
- // Command: Execute swap
335
- program
336
- .command('swap <fromChain> <toChain> <amount>')
337
- .description('Swap tokens between chains')
338
- .option('--from-token <address>', 'Token address to swap from (default: native)')
339
- .option('--to-token <address>', 'Token address to swap to (default: native)')
340
- .option('--slippage <percent>', 'Slippage tolerance in percent', '1')
341
- .option('-y, --yes', 'Skip confirmation prompt')
342
- .action(withExit(async (fromChainStr, toChainStr, amountStr, options) => {
343
- const context = await init(program.opts().vault);
344
- try {
3179
+ toToken: options.toToken
3180
+ });
3181
+ }
3182
+ )
3183
+ );
3184
+ program.command("swap <fromChain> <toChain> <amount>").description("Swap tokens between chains").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").option("--slippage <percent>", "Slippage tolerance in percent", "1").option("-y, --yes", "Skip confirmation prompt").action(
3185
+ withExit(
3186
+ async (fromChainStr, toChainStr, amountStr, options) => {
3187
+ const context = await init(program.opts().vault);
3188
+ try {
345
3189
  await executeSwap(context, {
346
- fromChain: findChainByName(fromChainStr) || fromChainStr,
347
- toChain: findChainByName(toChainStr) || toChainStr,
348
- amount: parseFloat(amountStr),
349
- fromToken: options.fromToken,
350
- toToken: options.toToken,
351
- slippage: options.slippage ? parseFloat(options.slippage) : undefined,
352
- yes: options.yes,
3190
+ fromChain: findChainByName(fromChainStr) || fromChainStr,
3191
+ toChain: findChainByName(toChainStr) || toChainStr,
3192
+ amount: parseFloat(amountStr),
3193
+ fromToken: options.fromToken,
3194
+ toToken: options.toToken,
3195
+ slippage: options.slippage ? parseFloat(options.slippage) : void 0,
3196
+ yes: options.yes
353
3197
  });
354
- }
355
- catch (err) {
356
- if (err.message === 'Swap cancelled by user') {
357
- warn('\nx Swap cancelled');
358
- return;
3198
+ } catch (err) {
3199
+ if (err.message === "Swap cancelled by user") {
3200
+ warn("\nx Swap cancelled");
3201
+ return;
359
3202
  }
360
3203
  throw err;
3204
+ }
361
3205
  }
362
- }));
363
- // ============================================================================
364
- // CLI Management Commands
365
- // ============================================================================
366
- // Command: Show detailed version
367
- program
368
- .command('version')
369
- .description('Show detailed version information')
370
- .action(withExit(async () => {
3206
+ )
3207
+ );
3208
+ program.command("version").description("Show detailed version information").action(
3209
+ withExit(async () => {
371
3210
  printResult(formatVersionDetailed());
372
- // Check for updates
373
3211
  const result = await checkForUpdates();
374
3212
  if (result?.updateAvailable && result.latestVersion) {
375
- info('');
376
- info(chalk.yellow(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
377
- info(chalk.gray(`Run "${getUpdateCommand()}" to update`));
378
- }
379
- }));
380
- // Command: Check for updates
381
- program
382
- .command('update')
383
- .description('Check for updates and show update command')
384
- .option('--check', 'Just check for updates, do not update')
385
- .action(withExit(async (options) => {
386
- info('Checking for updates...');
3213
+ info("");
3214
+ info(chalk12.yellow(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
3215
+ info(chalk12.gray(`Run "${getUpdateCommand()}" to update`));
3216
+ }
3217
+ })
3218
+ );
3219
+ program.command("update").description("Check for updates and show update command").option("--check", "Just check for updates, do not update").action(
3220
+ withExit(async (options) => {
3221
+ info("Checking for updates...");
387
3222
  const result = await checkForUpdates();
388
3223
  if (!result) {
389
- printResult(chalk.gray('Update checking is disabled'));
390
- return;
3224
+ printResult(chalk12.gray("Update checking is disabled"));
3225
+ return;
391
3226
  }
392
3227
  if (result.updateAvailable && result.latestVersion) {
393
- printResult('');
394
- printResult(chalk.green(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
395
- printResult('');
396
- if (options.check) {
397
- printResult(`Run "${getUpdateCommand()}" to update`);
398
- }
399
- else {
400
- const updateCmd = getUpdateCommand();
401
- printResult(`To update, run:`);
402
- printResult(chalk.cyan(` ${updateCmd}`));
403
- }
404
- }
405
- else {
406
- printResult(chalk.green(`You're on the latest version (${result.currentVersion})`));
3228
+ printResult("");
3229
+ printResult(chalk12.green(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
3230
+ printResult("");
3231
+ if (options.check) {
3232
+ printResult(`Run "${getUpdateCommand()}" to update`);
3233
+ } else {
3234
+ const updateCmd = getUpdateCommand();
3235
+ printResult(`To update, run:`);
3236
+ printResult(chalk12.cyan(` ${updateCmd}`));
3237
+ }
3238
+ } else {
3239
+ printResult(chalk12.green(`You're on the latest version (${result.currentVersion})`));
407
3240
  }
408
- }));
409
- // Setup completion command
3241
+ })
3242
+ );
410
3243
  setupCompletionCommand(program);
411
- // ============================================================================
412
- // Interactive Mode
413
- // ============================================================================
414
3244
  async function startInteractiveMode() {
415
- const sdk = new Vultisig({
416
- onPasswordRequired: createPasswordCallback(),
417
- });
418
- await sdk.initialize();
419
- const session = new ShellSession(sdk);
420
- await session.start();
421
- }
422
- // ============================================================================
423
- // Cleanup & Entry Point
424
- // ============================================================================
425
- process.on('SIGINT', () => {
426
- warn('\n\nShutting down...');
427
- ctx?.dispose();
428
- process.exit(0);
429
- });
430
- // Check for interactive mode before parsing commands
431
- if (process.argv.includes('-i') || process.argv.includes('--interactive')) {
432
- startInteractiveMode().catch(err => {
433
- error(`Failed to start interactive mode: ${err.message}`);
434
- process.exit(1);
435
- });
3245
+ const sdk = new Vultisig3({
3246
+ onPasswordRequired: createPasswordCallback()
3247
+ });
3248
+ await sdk.initialize();
3249
+ const session = new ShellSession(sdk);
3250
+ await session.start();
436
3251
  }
437
- else {
438
- program.parse();
3252
+ process.on("SIGINT", () => {
3253
+ warn("\n\nShutting down...");
3254
+ ctx?.dispose();
3255
+ process.exit(0);
3256
+ });
3257
+ if (process.argv.includes("-i") || process.argv.includes("--interactive")) {
3258
+ startInteractiveMode().catch((err) => {
3259
+ error(`Failed to start interactive mode: ${err.message}`);
3260
+ process.exit(1);
3261
+ });
3262
+ } else {
3263
+ program.parse();
439
3264
  }
440
- //# sourceMappingURL=index.js.map