@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.
- package/README.md +6 -0
- package/dist/index.js +3185 -361
- package/package.json +4 -3
- package/dist/adapters/cli-context.js +0 -25
- package/dist/adapters/cli-context.js.map +0 -1
- package/dist/adapters/cli-runner.js +0 -47
- package/dist/adapters/cli-runner.js.map +0 -1
- package/dist/adapters/index.js +0 -6
- package/dist/adapters/index.js.map +0 -1
- package/dist/commands/balance.js +0 -65
- package/dist/commands/balance.js.map +0 -1
- package/dist/commands/chains.js +0 -46
- package/dist/commands/chains.js.map +0 -1
- package/dist/commands/index.js +0 -12
- package/dist/commands/index.js.map +0 -1
- package/dist/commands/settings.js +0 -151
- package/dist/commands/settings.js.map +0 -1
- package/dist/commands/swap.js +0 -180
- package/dist/commands/swap.js.map +0 -1
- package/dist/commands/tokens.js +0 -116
- package/dist/commands/tokens.js.map +0 -1
- package/dist/commands/transaction.js +0 -99
- package/dist/commands/transaction.js.map +0 -1
- package/dist/commands/vault-management.js +0 -360
- package/dist/commands/vault-management.js.map +0 -1
- package/dist/core/command-context.js +0 -81
- package/dist/core/command-context.js.map +0 -1
- package/dist/core/index.js +0 -7
- package/dist/core/index.js.map +0 -1
- package/dist/core/password-manager.js +0 -92
- package/dist/core/password-manager.js.map +0 -1
- package/dist/core/types.js +0 -2
- package/dist/core/types.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/interactive/completer.js +0 -170
- package/dist/interactive/completer.js.map +0 -1
- package/dist/interactive/event-buffer.js +0 -186
- package/dist/interactive/event-buffer.js.map +0 -1
- package/dist/interactive/index.js +0 -9
- package/dist/interactive/index.js.map +0 -1
- package/dist/interactive/session.js +0 -525
- package/dist/interactive/session.js.map +0 -1
- package/dist/interactive/shell-commands.js +0 -167
- package/dist/interactive/shell-commands.js.map +0 -1
- package/dist/interactive/shell-context.js +0 -112
- package/dist/interactive/shell-context.js.map +0 -1
- package/dist/lib/completion.js +0 -375
- package/dist/lib/completion.js.map +0 -1
- package/dist/lib/config.js +0 -172
- package/dist/lib/config.js.map +0 -1
- package/dist/lib/errors.js +0 -163
- package/dist/lib/errors.js.map +0 -1
- package/dist/lib/index.js +0 -9
- package/dist/lib/index.js.map +0 -1
- package/dist/lib/output.js +0 -155
- package/dist/lib/output.js.map +0 -1
- package/dist/lib/version.js +0 -210
- package/dist/lib/version.js.map +0 -1
- package/dist/ui.js +0 -286
- package/dist/ui.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,440 +1,3264 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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) :
|
|
110
|
-
shares: options.shares ? parseInt(options.shares, 10) :
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
122
|
-
program
|
|
123
|
-
|
|
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
|
-
|
|
132
|
-
|
|
133
|
-
|
|
3032
|
+
const err = new Error("Verification failed");
|
|
3033
|
+
err.exitCode = 1;
|
|
3034
|
+
throw err;
|
|
134
3035
|
}
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
program
|
|
138
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
});
|
|
147
|
-
})
|
|
148
|
-
|
|
149
|
-
program
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
176
|
-
program
|
|
177
|
-
|
|
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
|
-
|
|
185
|
-
program
|
|
186
|
-
|
|
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
|
-
|
|
193
|
-
program
|
|
194
|
-
|
|
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
|
-
|
|
201
|
-
program
|
|
202
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
3092
|
+
outputPath: path2,
|
|
3093
|
+
encrypt: options.encrypt,
|
|
3094
|
+
password: options.password
|
|
213
3095
|
});
|
|
214
|
-
})
|
|
215
|
-
|
|
216
|
-
program
|
|
217
|
-
|
|
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
|
-
|
|
224
|
-
program
|
|
225
|
-
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
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
|
-
|
|
243
|
-
program
|
|
244
|
-
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
});
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
program
|
|
257
|
-
|
|
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
|
-
|
|
264
|
-
program
|
|
265
|
-
|
|
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
|
-
|
|
272
|
-
program
|
|
273
|
-
|
|
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
|
-
|
|
280
|
-
program
|
|
281
|
-
|
|
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
|
-
|
|
288
|
-
program
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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) :
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
-
|
|
319
|
-
program
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
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
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
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
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
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
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
})
|
|
380
|
-
|
|
381
|
-
program
|
|
382
|
-
|
|
383
|
-
|
|
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
|
-
|
|
390
|
-
|
|
3224
|
+
printResult(chalk12.gray("Update checking is disabled"));
|
|
3225
|
+
return;
|
|
391
3226
|
}
|
|
392
3227
|
if (result.updateAvailable && result.latestVersion) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
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
|
-
|
|
3241
|
+
})
|
|
3242
|
+
);
|
|
410
3243
|
setupCompletionCommand(program);
|
|
411
|
-
// ============================================================================
|
|
412
|
-
// Interactive Mode
|
|
413
|
-
// ============================================================================
|
|
414
3244
|
async function startInteractiveMode() {
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
438
|
-
|
|
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
|