@vultisig/cli 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/README.md +34 -6
- package/dist/index.js +1527 -649
- package/package.json +8 -4
package/dist/index.js
CHANGED
|
@@ -1049,14 +1049,14 @@ var require_main = __commonJS({
|
|
|
1049
1049
|
cb = opts;
|
|
1050
1050
|
opts = {};
|
|
1051
1051
|
}
|
|
1052
|
-
var
|
|
1053
|
-
|
|
1054
|
-
|
|
1052
|
+
var qrcode4 = new QRCode(-1, this.error);
|
|
1053
|
+
qrcode4.addData(input);
|
|
1054
|
+
qrcode4.make();
|
|
1055
1055
|
var output = "";
|
|
1056
1056
|
if (opts && opts.small) {
|
|
1057
1057
|
var BLACK = true, WHITE = false;
|
|
1058
|
-
var moduleCount =
|
|
1059
|
-
var moduleData =
|
|
1058
|
+
var moduleCount = qrcode4.getModuleCount();
|
|
1059
|
+
var moduleData = qrcode4.modules.slice();
|
|
1060
1060
|
var oddRow = moduleCount % 2 === 1;
|
|
1061
1061
|
if (oddRow) {
|
|
1062
1062
|
moduleData.push(fill(moduleCount, WHITE));
|
|
@@ -1089,9 +1089,9 @@ var require_main = __commonJS({
|
|
|
1089
1089
|
output += borderBottom;
|
|
1090
1090
|
}
|
|
1091
1091
|
} else {
|
|
1092
|
-
var border = repeat(white).times(
|
|
1092
|
+
var border = repeat(white).times(qrcode4.getModuleCount() + 3);
|
|
1093
1093
|
output += border + "\n";
|
|
1094
|
-
|
|
1094
|
+
qrcode4.modules.forEach(function(row2) {
|
|
1095
1095
|
output += white;
|
|
1096
1096
|
output += row2.map(toCell).join("");
|
|
1097
1097
|
output += white + "\n";
|
|
@@ -1452,9 +1452,10 @@ var init_formatUnits = __esm({
|
|
|
1452
1452
|
// src/index.ts
|
|
1453
1453
|
import "dotenv/config";
|
|
1454
1454
|
import { promises as fs4 } from "node:fs";
|
|
1455
|
+
import { descriptions } from "@vultisig/client-shared";
|
|
1455
1456
|
import { parseKeygenQR, Vultisig as Vultisig6 } from "@vultisig/sdk";
|
|
1456
1457
|
import chalk15 from "chalk";
|
|
1457
|
-
import { program } from "commander";
|
|
1458
|
+
import { InvalidArgumentError, program } from "commander";
|
|
1458
1459
|
import inquirer8 from "inquirer";
|
|
1459
1460
|
|
|
1460
1461
|
// src/core/command-context.ts
|
|
@@ -1536,12 +1537,32 @@ import chalk from "chalk";
|
|
|
1536
1537
|
import ora from "ora";
|
|
1537
1538
|
var silentMode = false;
|
|
1538
1539
|
var outputFormat = "table";
|
|
1540
|
+
var nonInteractive = false;
|
|
1541
|
+
var quietMode = false;
|
|
1542
|
+
var fieldFilter;
|
|
1539
1543
|
function setSilentMode(silent) {
|
|
1540
1544
|
silentMode = silent;
|
|
1541
1545
|
}
|
|
1542
1546
|
function isSilent() {
|
|
1543
1547
|
return silentMode;
|
|
1544
1548
|
}
|
|
1549
|
+
function setNonInteractive(value) {
|
|
1550
|
+
nonInteractive = value;
|
|
1551
|
+
}
|
|
1552
|
+
function isNonInteractive() {
|
|
1553
|
+
return nonInteractive;
|
|
1554
|
+
}
|
|
1555
|
+
function requireInteractive(hint) {
|
|
1556
|
+
if (nonInteractive) {
|
|
1557
|
+
throw new Error(`Interactive prompt required but --non-interactive is set. ${hint}`);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
function setQuiet(value) {
|
|
1561
|
+
quietMode = value;
|
|
1562
|
+
}
|
|
1563
|
+
function setFields(fields) {
|
|
1564
|
+
fieldFilter = fields;
|
|
1565
|
+
}
|
|
1545
1566
|
function initOutputMode(options) {
|
|
1546
1567
|
outputFormat = options.output ?? "table";
|
|
1547
1568
|
silentMode = options.silent ?? process.env.VULTISIG_SILENT === "1";
|
|
@@ -1552,14 +1573,72 @@ function initOutputMode(options) {
|
|
|
1552
1573
|
function isJsonOutput() {
|
|
1553
1574
|
return outputFormat === "json";
|
|
1554
1575
|
}
|
|
1576
|
+
function stripEmpty(data) {
|
|
1577
|
+
if (Array.isArray(data)) return data.map(stripEmpty);
|
|
1578
|
+
if (data !== null && typeof data === "object") {
|
|
1579
|
+
return Object.fromEntries(
|
|
1580
|
+
Object.entries(data).filter(([, v]) => v != null && v !== "").map(([k, v]) => [k, stripEmpty(v)])
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
return data;
|
|
1584
|
+
}
|
|
1585
|
+
function filterFields(data, fields) {
|
|
1586
|
+
if (!fields.length) return data;
|
|
1587
|
+
if (Array.isArray(data)) return data.map((item) => filterFields(item, fields));
|
|
1588
|
+
if (data !== null && typeof data === "object") {
|
|
1589
|
+
const entries = Object.entries(data);
|
|
1590
|
+
const matched = entries.filter(([k]) => fields.includes(k));
|
|
1591
|
+
if (matched.length > 0) return Object.fromEntries(matched);
|
|
1592
|
+
const recursed = entries.map(([k, v]) => [k, filterFields(v, fields)]);
|
|
1593
|
+
return Object.fromEntries(recursed);
|
|
1594
|
+
}
|
|
1595
|
+
return data;
|
|
1596
|
+
}
|
|
1597
|
+
function collectKeys(data) {
|
|
1598
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1599
|
+
if (Array.isArray(data)) {
|
|
1600
|
+
for (const item of data) {
|
|
1601
|
+
if (item !== null && typeof item === "object") {
|
|
1602
|
+
for (const k of Object.keys(item)) keys.add(k);
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
} else if (data !== null && typeof data === "object") {
|
|
1606
|
+
for (const [k, v] of Object.entries(data)) {
|
|
1607
|
+
keys.add(k);
|
|
1608
|
+
if (v !== null && typeof v === "object") {
|
|
1609
|
+
for (const nested of collectKeys(v)) keys.add(nested);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
return keys;
|
|
1614
|
+
}
|
|
1615
|
+
function warnInvalidFields(data, fields) {
|
|
1616
|
+
const available = collectKeys(data);
|
|
1617
|
+
if (available.size === 0) return;
|
|
1618
|
+
const invalid = fields.filter((f) => !available.has(f));
|
|
1619
|
+
if (invalid.length > 0) {
|
|
1620
|
+
process.stderr.write(`Warning: unknown field(s): ${invalid.join(", ")}. Available: ${[...available].join(", ")}
|
|
1621
|
+
`);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
function applyOutputTransforms(data) {
|
|
1625
|
+
let out = quietMode ? stripEmpty(data) : data;
|
|
1626
|
+
if (fieldFilter?.length) {
|
|
1627
|
+
warnInvalidFields(out, fieldFilter);
|
|
1628
|
+
out = filterFields(out, fieldFilter);
|
|
1629
|
+
}
|
|
1630
|
+
return out;
|
|
1631
|
+
}
|
|
1555
1632
|
function bigIntReplacer(_key, value) {
|
|
1556
1633
|
return typeof value === "bigint" ? value.toString() : value;
|
|
1557
1634
|
}
|
|
1558
1635
|
function outputJson(data) {
|
|
1559
|
-
|
|
1636
|
+
const transformed = applyOutputTransforms(data);
|
|
1637
|
+
console.log(JSON.stringify({ success: true, v: 1, data: transformed }, bigIntReplacer, 2));
|
|
1560
1638
|
}
|
|
1561
|
-
function
|
|
1562
|
-
|
|
1639
|
+
function outputErrorJson(errJson) {
|
|
1640
|
+
const transformed = applyOutputTransforms(errJson);
|
|
1641
|
+
console.log(JSON.stringify(transformed, bigIntReplacer, 2));
|
|
1563
1642
|
}
|
|
1564
1643
|
function info(message) {
|
|
1565
1644
|
if (!silentMode) {
|
|
@@ -1661,11 +1740,24 @@ function createSpinner(text) {
|
|
|
1661
1740
|
activeSpinners.add(spinner2);
|
|
1662
1741
|
return spinner2;
|
|
1663
1742
|
}
|
|
1664
|
-
const spinner = wrapOraSpinner(ora(text).start());
|
|
1743
|
+
const spinner = wrapOraSpinner(ora({ text, stream: process.stderr }).start());
|
|
1665
1744
|
activeSpinners.add(spinner);
|
|
1666
1745
|
return spinner;
|
|
1667
1746
|
}
|
|
1668
1747
|
|
|
1748
|
+
// src/core/credential-store.ts
|
|
1749
|
+
import {
|
|
1750
|
+
_resetAll,
|
|
1751
|
+
clearCredentials,
|
|
1752
|
+
getDecryptionPassword,
|
|
1753
|
+
getServerPassword,
|
|
1754
|
+
getStoredServerPassword,
|
|
1755
|
+
isUsingFileFallback,
|
|
1756
|
+
setDecryptionPassword,
|
|
1757
|
+
setFilePassphrase,
|
|
1758
|
+
setServerPassword
|
|
1759
|
+
} from "@vultisig/client-shared";
|
|
1760
|
+
|
|
1669
1761
|
// src/core/password-manager.ts
|
|
1670
1762
|
var passwordCache = /* @__PURE__ */ new Map();
|
|
1671
1763
|
function cachePassword(vaultIdOrName, password) {
|
|
@@ -1700,12 +1792,15 @@ function getPasswordFromEnv(vaultId, vaultName) {
|
|
|
1700
1792
|
if (vaultPasswords.has(vaultId)) {
|
|
1701
1793
|
return vaultPasswords.get(vaultId);
|
|
1702
1794
|
}
|
|
1703
|
-
if (process.env.VAULT_PASSWORD) {
|
|
1704
|
-
return process.env.VAULT_PASSWORD;
|
|
1795
|
+
if (process.env.VAULT_PASSWORD || process.env.VULTISIG_PASSWORD) {
|
|
1796
|
+
return process.env.VAULT_PASSWORD || process.env.VULTISIG_PASSWORD;
|
|
1705
1797
|
}
|
|
1706
1798
|
return null;
|
|
1707
1799
|
}
|
|
1708
1800
|
async function promptForPassword(vaultName, vaultId) {
|
|
1801
|
+
requireInteractive(
|
|
1802
|
+
'Use --password flag, VAULT_PASSWORD env var, or "vsig auth setup" to store credentials in keyring.'
|
|
1803
|
+
);
|
|
1709
1804
|
const displayName = vaultName || vaultId || "vault";
|
|
1710
1805
|
const { password } = await inquirer.prompt([
|
|
1711
1806
|
{
|
|
@@ -1722,14 +1817,25 @@ async function getPassword(vaultId, vaultName) {
|
|
|
1722
1817
|
if (cachedPassword) {
|
|
1723
1818
|
return cachedPassword;
|
|
1724
1819
|
}
|
|
1820
|
+
try {
|
|
1821
|
+
const keyringPassword = await getServerPassword(vaultId);
|
|
1822
|
+
if (keyringPassword) {
|
|
1823
|
+
cachePassword(vaultId, keyringPassword);
|
|
1824
|
+
if (vaultName) cachePassword(vaultName, keyringPassword);
|
|
1825
|
+
return keyringPassword;
|
|
1826
|
+
}
|
|
1827
|
+
} catch {
|
|
1828
|
+
}
|
|
1725
1829
|
const envPassword = getPasswordFromEnv(vaultId, vaultName);
|
|
1726
1830
|
if (envPassword) {
|
|
1727
1831
|
cachePassword(vaultId, envPassword);
|
|
1728
1832
|
if (vaultName) cachePassword(vaultName, envPassword);
|
|
1729
1833
|
return envPassword;
|
|
1730
1834
|
}
|
|
1731
|
-
if (isSilent() || isJsonOutput()) {
|
|
1732
|
-
throw new Error(
|
|
1835
|
+
if (isSilent() || isJsonOutput() || isNonInteractive()) {
|
|
1836
|
+
throw new Error(
|
|
1837
|
+
"Password required but not provided. Set VAULT_PASSWORD or VAULT_PASSWORDS environment variable, or use --password flag."
|
|
1838
|
+
);
|
|
1733
1839
|
}
|
|
1734
1840
|
const password = await promptForPassword(vaultName, vaultId);
|
|
1735
1841
|
cachePassword(vaultId, password);
|
|
@@ -1745,12 +1851,8 @@ async function ensureVaultUnlocked(vault, password) {
|
|
|
1745
1851
|
if (!vault.isEncrypted || vault.isUnlocked()) {
|
|
1746
1852
|
return;
|
|
1747
1853
|
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
return;
|
|
1751
|
-
}
|
|
1752
|
-
const inputPassword = await promptForPassword(vault.name, vault.id);
|
|
1753
|
-
await vault.unlock(inputPassword);
|
|
1854
|
+
const resolvedPassword = password || await getPassword(vault.id, vault.name);
|
|
1855
|
+
await vault.unlock(resolvedPassword);
|
|
1754
1856
|
}
|
|
1755
1857
|
|
|
1756
1858
|
// src/adapters/cli-context.ts
|
|
@@ -1768,6 +1870,229 @@ var CLIContext = class extends BaseCommandContext {
|
|
|
1768
1870
|
}
|
|
1769
1871
|
};
|
|
1770
1872
|
|
|
1873
|
+
// src/core/errors.ts
|
|
1874
|
+
import { VaultError, VaultErrorCode, VaultImportError, VaultImportErrorCode } from "@vultisig/sdk";
|
|
1875
|
+
var EXIT_CODE_DESCRIPTIONS = {
|
|
1876
|
+
[0 /* SUCCESS */]: "Success",
|
|
1877
|
+
[1 /* USAGE */]: "Usage error (bad arguments, unknown command)",
|
|
1878
|
+
[2 /* AUTH_REQUIRED */]: "Authentication required",
|
|
1879
|
+
[3 /* NETWORK */]: "Network error (retryable)",
|
|
1880
|
+
[4 /* INVALID_INPUT */]: "Invalid input (bad chain, address, amount)",
|
|
1881
|
+
[5 /* RESOURCE_NOT_FOUND */]: "Resource not found (token, route)",
|
|
1882
|
+
[6 /* EXTERNAL_SERVICE */]: "External service error (retryable)",
|
|
1883
|
+
[7 /* UNKNOWN */]: "Unknown/unexpected error"
|
|
1884
|
+
};
|
|
1885
|
+
var VsigError = class extends Error {
|
|
1886
|
+
hint;
|
|
1887
|
+
suggestions;
|
|
1888
|
+
context;
|
|
1889
|
+
retryable = false;
|
|
1890
|
+
constructor(message, hint, suggestions, context) {
|
|
1891
|
+
super(message);
|
|
1892
|
+
this.name = this.constructor.name;
|
|
1893
|
+
this.hint = hint;
|
|
1894
|
+
this.suggestions = suggestions;
|
|
1895
|
+
this.context = context;
|
|
1896
|
+
}
|
|
1897
|
+
};
|
|
1898
|
+
var UsageError = class extends VsigError {
|
|
1899
|
+
exitCode = 1 /* USAGE */;
|
|
1900
|
+
code = "USAGE_ERROR";
|
|
1901
|
+
constructor(message, hint, suggestions) {
|
|
1902
|
+
super(message, hint, suggestions);
|
|
1903
|
+
}
|
|
1904
|
+
};
|
|
1905
|
+
var AuthRequiredError = class extends VsigError {
|
|
1906
|
+
exitCode = 2 /* AUTH_REQUIRED */;
|
|
1907
|
+
code = "AUTH_REQUIRED";
|
|
1908
|
+
constructor(message) {
|
|
1909
|
+
super(message ?? "Authentication required. Set up vault credentials.", "Ensure your vault is unlocked", [
|
|
1910
|
+
"vsig vaults",
|
|
1911
|
+
"vsig create"
|
|
1912
|
+
]);
|
|
1913
|
+
}
|
|
1914
|
+
};
|
|
1915
|
+
var NetworkError = class extends VsigError {
|
|
1916
|
+
exitCode = 3 /* NETWORK */;
|
|
1917
|
+
code = "NETWORK_ERROR";
|
|
1918
|
+
retryable = true;
|
|
1919
|
+
constructor(message, hint, suggestions) {
|
|
1920
|
+
super(message, hint, suggestions);
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1923
|
+
var InvalidChainError = class extends VsigError {
|
|
1924
|
+
exitCode = 4 /* INVALID_INPUT */;
|
|
1925
|
+
code = "INVALID_CHAIN";
|
|
1926
|
+
constructor(message, hint, suggestions, context) {
|
|
1927
|
+
super(message, hint, suggestions, context);
|
|
1928
|
+
}
|
|
1929
|
+
};
|
|
1930
|
+
var InvalidAddressError = class extends VsigError {
|
|
1931
|
+
exitCode = 4 /* INVALID_INPUT */;
|
|
1932
|
+
code = "INVALID_ADDRESS";
|
|
1933
|
+
constructor(message, hint, suggestions, context) {
|
|
1934
|
+
super(message, hint, suggestions, context);
|
|
1935
|
+
}
|
|
1936
|
+
};
|
|
1937
|
+
var InvalidInputError = class extends VsigError {
|
|
1938
|
+
exitCode = 4 /* INVALID_INPUT */;
|
|
1939
|
+
code = "INVALID_INPUT";
|
|
1940
|
+
constructor(message, hint, suggestions, context) {
|
|
1941
|
+
super(message, hint, suggestions, context);
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
var InsufficientBalanceError = class extends VsigError {
|
|
1945
|
+
exitCode = 4 /* INVALID_INPUT */;
|
|
1946
|
+
code = "INSUFFICIENT_BALANCE";
|
|
1947
|
+
constructor(message, hint, suggestions, context) {
|
|
1948
|
+
super(message, hint, suggestions, context);
|
|
1949
|
+
}
|
|
1950
|
+
};
|
|
1951
|
+
var NoRouteError = class extends VsigError {
|
|
1952
|
+
exitCode = 5 /* RESOURCE_NOT_FOUND */;
|
|
1953
|
+
code = "NO_ROUTE";
|
|
1954
|
+
constructor(message, hint, suggestions, context) {
|
|
1955
|
+
super(message, hint, suggestions, context);
|
|
1956
|
+
}
|
|
1957
|
+
};
|
|
1958
|
+
var TokenNotFoundError = class extends VsigError {
|
|
1959
|
+
exitCode = 5 /* RESOURCE_NOT_FOUND */;
|
|
1960
|
+
code = "TOKEN_NOT_FOUND";
|
|
1961
|
+
constructor(message, hint, suggestions, context) {
|
|
1962
|
+
super(message, hint, suggestions, context);
|
|
1963
|
+
}
|
|
1964
|
+
};
|
|
1965
|
+
var ExternalServiceError = class extends VsigError {
|
|
1966
|
+
exitCode = 6 /* EXTERNAL_SERVICE */;
|
|
1967
|
+
code = "EXTERNAL_SERVICE";
|
|
1968
|
+
retryable = true;
|
|
1969
|
+
constructor(message, hint, suggestions) {
|
|
1970
|
+
super(message, hint, suggestions);
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
var PricingUnavailableError = class extends VsigError {
|
|
1974
|
+
exitCode = 6 /* EXTERNAL_SERVICE */;
|
|
1975
|
+
code = "PRICING_UNAVAILABLE";
|
|
1976
|
+
retryable = true;
|
|
1977
|
+
constructor(message, hint, suggestions) {
|
|
1978
|
+
super(message, hint, suggestions);
|
|
1979
|
+
}
|
|
1980
|
+
};
|
|
1981
|
+
var UnknownError = class extends VsigError {
|
|
1982
|
+
exitCode = 7 /* UNKNOWN */;
|
|
1983
|
+
code = "UNKNOWN_ERROR";
|
|
1984
|
+
constructor(message) {
|
|
1985
|
+
super(message);
|
|
1986
|
+
}
|
|
1987
|
+
};
|
|
1988
|
+
function classifyError(err) {
|
|
1989
|
+
if (err instanceof VsigError) return err;
|
|
1990
|
+
if (err instanceof VaultError) {
|
|
1991
|
+
if (err.code === VaultErrorCode.BalanceFetchFailed && err.originalError) {
|
|
1992
|
+
const inner = classifyError(err.originalError);
|
|
1993
|
+
if (!(inner instanceof UnknownError)) return inner;
|
|
1994
|
+
}
|
|
1995
|
+
switch (err.code) {
|
|
1996
|
+
case VaultErrorCode.UnsupportedChain:
|
|
1997
|
+
case VaultErrorCode.ChainNotSupported:
|
|
1998
|
+
return new InvalidChainError(err.message);
|
|
1999
|
+
case VaultErrorCode.NetworkError:
|
|
2000
|
+
case VaultErrorCode.BalanceFetchFailed:
|
|
2001
|
+
case VaultErrorCode.Timeout:
|
|
2002
|
+
return new NetworkError(err.message);
|
|
2003
|
+
case VaultErrorCode.InvalidAmount:
|
|
2004
|
+
return new InvalidInputError(err.message);
|
|
2005
|
+
case VaultErrorCode.InvalidConfig: {
|
|
2006
|
+
const lowerMsg = err.message.toLowerCase();
|
|
2007
|
+
if (lowerMsg.includes("unknown chain") || lowerMsg.includes("unsupported chain") || lowerMsg.includes("chain not supported")) {
|
|
2008
|
+
const chainMatch = err.message.match(/chain[:\s]*"([^"]+)"/i);
|
|
2009
|
+
return new InvalidChainError(
|
|
2010
|
+
err.message,
|
|
2011
|
+
void 0,
|
|
2012
|
+
void 0,
|
|
2013
|
+
chainMatch ? { chain: chainMatch[1] } : void 0
|
|
2014
|
+
);
|
|
2015
|
+
}
|
|
2016
|
+
return new UsageError(err.message);
|
|
2017
|
+
}
|
|
2018
|
+
case VaultErrorCode.UnsupportedToken:
|
|
2019
|
+
return new TokenNotFoundError(err.message);
|
|
2020
|
+
case VaultErrorCode.BroadcastFailed:
|
|
2021
|
+
return new ExternalServiceError(err.message, "Broadcast failed \u2014 the node may be temporarily unavailable", [
|
|
2022
|
+
"Retry the transaction"
|
|
2023
|
+
]);
|
|
2024
|
+
case VaultErrorCode.GasEstimationFailed:
|
|
2025
|
+
return new InvalidInputError(err.message, "Gas estimation failed \u2014 check balance and transaction params");
|
|
2026
|
+
case VaultErrorCode.SigningFailed:
|
|
2027
|
+
return new UnknownError(err.message);
|
|
2028
|
+
default:
|
|
2029
|
+
return new UnknownError(err.message);
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
if (err instanceof VaultImportError) {
|
|
2033
|
+
switch (err.code) {
|
|
2034
|
+
case VaultImportErrorCode.PASSWORD_REQUIRED:
|
|
2035
|
+
case VaultImportErrorCode.INVALID_PASSWORD:
|
|
2036
|
+
return new AuthRequiredError(err.message);
|
|
2037
|
+
default:
|
|
2038
|
+
return new UsageError(err.message);
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
const msg = err.message.toLowerCase();
|
|
2042
|
+
if (msg.includes("unsupported chain") || msg.includes("invalid chain") || msg.includes("unknown chain")) {
|
|
2043
|
+
const chainMatch = err.message.match(/chain[:\s]*"([^"]+)"/i) || err.message.match(/chain[:\s]+(\S+)/i);
|
|
2044
|
+
return new InvalidChainError(err.message, void 0, void 0, chainMatch ? { chain: chainMatch[1] } : void 0);
|
|
2045
|
+
}
|
|
2046
|
+
if (msg.includes("invalid address") || msg.includes("bad address") || msg.includes("malformed address")) {
|
|
2047
|
+
const addrMatch = err.message.match(/(0x[a-fA-F0-9]+|bc1[a-z0-9]+|[13][a-km-zA-HJ-NP-Z1-9]+)/i);
|
|
2048
|
+
return new InvalidAddressError(err.message, void 0, void 0, addrMatch ? { address: addrMatch[1] } : void 0);
|
|
2049
|
+
}
|
|
2050
|
+
if (msg.includes("insufficient") && msg.includes("balance")) {
|
|
2051
|
+
return new InsufficientBalanceError(err.message);
|
|
2052
|
+
}
|
|
2053
|
+
if (msg.includes("no route") || msg.includes("no swap") || msg.includes("no provider")) {
|
|
2054
|
+
return new NoRouteError(err.message);
|
|
2055
|
+
}
|
|
2056
|
+
if (msg.includes("token not found") || msg.includes("unknown token")) {
|
|
2057
|
+
return new TokenNotFoundError(err.message);
|
|
2058
|
+
}
|
|
2059
|
+
if (msg.includes("pricing") || msg.includes("price unavailable") || msg.includes("price service")) {
|
|
2060
|
+
return new PricingUnavailableError(err.message);
|
|
2061
|
+
}
|
|
2062
|
+
if (msg.includes("econnrefused") || msg.includes("etimedout") || msg.includes("enotfound") || msg.includes("econnreset") || msg.includes("fetch failed") || msg.includes("network") || msg.includes("socket hang up") || msg.includes("dns")) {
|
|
2063
|
+
return new NetworkError(err.message);
|
|
2064
|
+
}
|
|
2065
|
+
return new UnknownError(err.message);
|
|
2066
|
+
}
|
|
2067
|
+
function toErrorJson(err) {
|
|
2068
|
+
if (err instanceof VsigError) {
|
|
2069
|
+
const json = {
|
|
2070
|
+
success: false,
|
|
2071
|
+
v: 1,
|
|
2072
|
+
error: {
|
|
2073
|
+
code: err.code,
|
|
2074
|
+
exitCode: err.exitCode,
|
|
2075
|
+
message: err.message,
|
|
2076
|
+
hint: err.hint,
|
|
2077
|
+
retryable: err.retryable
|
|
2078
|
+
}
|
|
2079
|
+
};
|
|
2080
|
+
if (err.suggestions?.length) json.error.suggestions = err.suggestions;
|
|
2081
|
+
if (err.context) json.error.context = err.context;
|
|
2082
|
+
return json;
|
|
2083
|
+
}
|
|
2084
|
+
return {
|
|
2085
|
+
success: false,
|
|
2086
|
+
v: 1,
|
|
2087
|
+
error: {
|
|
2088
|
+
code: "UNKNOWN_ERROR",
|
|
2089
|
+
exitCode: 7 /* UNKNOWN */,
|
|
2090
|
+
message: err.message,
|
|
2091
|
+
retryable: false
|
|
2092
|
+
}
|
|
2093
|
+
};
|
|
2094
|
+
}
|
|
2095
|
+
|
|
1771
2096
|
// src/adapters/cli-runner.ts
|
|
1772
2097
|
function withExit(handler) {
|
|
1773
2098
|
return async (...args) => {
|
|
@@ -1775,17 +2100,18 @@ function withExit(handler) {
|
|
|
1775
2100
|
await handler(...args);
|
|
1776
2101
|
process.exit(0);
|
|
1777
2102
|
} catch (err) {
|
|
1778
|
-
const
|
|
2103
|
+
const classified = err instanceof VsigError ? err : err instanceof Error ? classifyError(err) : classifyError(new Error(String(err)));
|
|
1779
2104
|
if (isJsonOutput()) {
|
|
1780
|
-
|
|
1781
|
-
process.exit(exitCode);
|
|
1782
|
-
}
|
|
1783
|
-
if (err.exitCode !== void 0) {
|
|
1784
|
-
process.exit(err.exitCode);
|
|
2105
|
+
outputErrorJson(toErrorJson(classified));
|
|
2106
|
+
process.exit(classified.exitCode);
|
|
1785
2107
|
}
|
|
1786
2108
|
printError(`
|
|
1787
|
-
x ${
|
|
1788
|
-
|
|
2109
|
+
x ${classified.message}`);
|
|
2110
|
+
if (classified.hint) printError(` hint: ${classified.hint}`);
|
|
2111
|
+
if (classified.suggestions?.length) {
|
|
2112
|
+
for (const s of classified.suggestions) printError(` - ${s}`);
|
|
2113
|
+
}
|
|
2114
|
+
process.exit(classified.exitCode);
|
|
1789
2115
|
}
|
|
1790
2116
|
};
|
|
1791
2117
|
}
|
|
@@ -1914,6 +2240,7 @@ function displayVaultsList(vaults, activeVault) {
|
|
|
1914
2240
|
printTable(table);
|
|
1915
2241
|
}
|
|
1916
2242
|
async function confirmTransaction() {
|
|
2243
|
+
requireInteractive("Use --yes to skip confirmation, or --password to provide password non-interactively.");
|
|
1917
2244
|
const { confirmed } = await inquirer2.prompt([
|
|
1918
2245
|
{
|
|
1919
2246
|
type: "confirm",
|
|
@@ -2033,6 +2360,7 @@ function displaySwapChains(chains) {
|
|
|
2033
2360
|
Total: ${chains.length} chains`);
|
|
2034
2361
|
}
|
|
2035
2362
|
async function confirmSwap() {
|
|
2363
|
+
requireInteractive("Use --yes to skip confirmation, or --password to provide password non-interactively.");
|
|
2036
2364
|
const { confirmed } = await inquirer2.prompt([
|
|
2037
2365
|
{
|
|
2038
2366
|
type: "confirm",
|
|
@@ -2121,13 +2449,25 @@ async function executeChains(ctx2, options = {}) {
|
|
|
2121
2449
|
return;
|
|
2122
2450
|
}
|
|
2123
2451
|
if (options.add) {
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2452
|
+
const alreadyActive = vault.chains.includes(options.add);
|
|
2453
|
+
if (!alreadyActive) {
|
|
2454
|
+
await vault.addChain(options.add);
|
|
2455
|
+
}
|
|
2127
2456
|
const address = await vault.address(options.add);
|
|
2457
|
+
if (isJsonOutput()) {
|
|
2458
|
+
outputJson({ chain: options.add, alreadyActive, address, chains: [...vault.chains] });
|
|
2459
|
+
return;
|
|
2460
|
+
}
|
|
2461
|
+
success(alreadyActive ? `
|
|
2462
|
+
Chain already active: ${options.add}` : `
|
|
2463
|
+
+ Added chain: ${options.add}`);
|
|
2128
2464
|
info(`Address: ${address}`);
|
|
2129
2465
|
} else if (options.remove) {
|
|
2130
2466
|
await vault.removeChain(options.remove);
|
|
2467
|
+
if (isJsonOutput()) {
|
|
2468
|
+
outputJson({ chain: options.remove, removed: true, chains: [...vault.chains] });
|
|
2469
|
+
return;
|
|
2470
|
+
}
|
|
2131
2471
|
success(`
|
|
2132
2472
|
+ Removed chain: ${options.remove}`);
|
|
2133
2473
|
} else {
|
|
@@ -2162,6 +2502,10 @@ import chalk4 from "chalk";
|
|
|
2162
2502
|
import inquirer3 from "inquirer";
|
|
2163
2503
|
async function executeTokens(ctx2, options) {
|
|
2164
2504
|
await ctx2.ensureActiveVault();
|
|
2505
|
+
if (options.discover) {
|
|
2506
|
+
await discoverTokens(ctx2, options.chain);
|
|
2507
|
+
return;
|
|
2508
|
+
}
|
|
2165
2509
|
if (options.add) {
|
|
2166
2510
|
let symbol = options.symbol;
|
|
2167
2511
|
let name = options.name;
|
|
@@ -2193,6 +2537,7 @@ async function executeTokens(ctx2, options) {
|
|
|
2193
2537
|
});
|
|
2194
2538
|
}
|
|
2195
2539
|
if (prompts.length > 0) {
|
|
2540
|
+
requireInteractive("Provide --symbol, --name, and --decimals flags for non-interactive token addition.");
|
|
2196
2541
|
const answers = await inquirer3.prompt(prompts);
|
|
2197
2542
|
symbol = symbol || answers.symbol?.trim();
|
|
2198
2543
|
name = name || answers.name?.trim();
|
|
@@ -2231,6 +2576,57 @@ async function removeToken(ctx2, chain, tokenId) {
|
|
|
2231
2576
|
success(`
|
|
2232
2577
|
+ Removed token ${tokenId} from ${chain}`);
|
|
2233
2578
|
}
|
|
2579
|
+
async function discoverTokens(ctx2, chain) {
|
|
2580
|
+
const vault = await ctx2.ensureActiveVault();
|
|
2581
|
+
const spinner = createSpinner(`Discovering tokens on ${chain}...`);
|
|
2582
|
+
let discovered;
|
|
2583
|
+
try {
|
|
2584
|
+
discovered = await vault.discoverTokens(chain);
|
|
2585
|
+
} catch (err) {
|
|
2586
|
+
spinner.fail(`Token discovery failed for ${chain}`);
|
|
2587
|
+
throw err;
|
|
2588
|
+
}
|
|
2589
|
+
if (discovered.length === 0) {
|
|
2590
|
+
spinner.succeed(`No new tokens found on ${chain}`);
|
|
2591
|
+
if (isJsonOutput()) {
|
|
2592
|
+
outputJson({ chain, discovered: [], count: 0 });
|
|
2593
|
+
}
|
|
2594
|
+
return;
|
|
2595
|
+
}
|
|
2596
|
+
const existingTokens = vault.getTokens(chain);
|
|
2597
|
+
const existingAddresses = new Set(existingTokens.map((t) => t.contractAddress ?? t.id));
|
|
2598
|
+
const newTokens = discovered.filter((d) => d.contractAddress && !existingAddresses.has(d.contractAddress));
|
|
2599
|
+
for (const d of newTokens) {
|
|
2600
|
+
await vault.addToken(chain, {
|
|
2601
|
+
id: d.contractAddress,
|
|
2602
|
+
symbol: d.ticker,
|
|
2603
|
+
name: d.ticker,
|
|
2604
|
+
decimals: d.decimals,
|
|
2605
|
+
contractAddress: d.contractAddress,
|
|
2606
|
+
chainId: chain,
|
|
2607
|
+
isNative: false
|
|
2608
|
+
});
|
|
2609
|
+
}
|
|
2610
|
+
const allTokens = vault.getTokens(chain);
|
|
2611
|
+
spinner.succeed(`Discovered ${newTokens.length} new token(s) on ${chain}`);
|
|
2612
|
+
if (isJsonOutput()) {
|
|
2613
|
+
outputJson({
|
|
2614
|
+
chain,
|
|
2615
|
+
discovered: newTokens.map((d) => ({
|
|
2616
|
+
symbol: d.ticker,
|
|
2617
|
+
contractAddress: d.contractAddress,
|
|
2618
|
+
decimals: d.decimals
|
|
2619
|
+
})),
|
|
2620
|
+
count: newTokens.length
|
|
2621
|
+
});
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
for (const d of newTokens) {
|
|
2625
|
+
printResult(` ${d.ticker} (${d.contractAddress})`);
|
|
2626
|
+
}
|
|
2627
|
+
info(chalk4.gray(`
|
|
2628
|
+
${allTokens.length} total token(s) tracked on ${chain}`));
|
|
2629
|
+
}
|
|
2234
2630
|
async function listTokens(ctx2, chain) {
|
|
2235
2631
|
const vault = await ctx2.ensureActiveVault();
|
|
2236
2632
|
const spinner = createSpinner(`Loading tokens for ${chain}...`);
|
|
@@ -2263,8 +2659,15 @@ Use --add <contractAddress> to add or --remove <tokenId> to remove`));
|
|
|
2263
2659
|
}
|
|
2264
2660
|
|
|
2265
2661
|
// src/commands/transaction.ts
|
|
2266
|
-
var import_qrcode_terminal = __toESM(require_main(), 1);
|
|
2267
2662
|
import { Chain, Vultisig as Vultisig2 } from "@vultisig/sdk";
|
|
2663
|
+
|
|
2664
|
+
// src/core/config-store.ts
|
|
2665
|
+
import { getConfigPath, loadConfig, saveConfig } from "@vultisig/client-shared";
|
|
2666
|
+
|
|
2667
|
+
// src/core/vault-discovery.ts
|
|
2668
|
+
import { discoverVaultFiles, SEARCH_DIRS } from "@vultisig/client-shared";
|
|
2669
|
+
|
|
2670
|
+
// src/commands/transaction.ts
|
|
2268
2671
|
async function executeSend(ctx2, params) {
|
|
2269
2672
|
const vault = await ctx2.ensureActiveVault();
|
|
2270
2673
|
if (!Object.values(Chain).includes(params.chain)) {
|
|
@@ -2278,59 +2681,59 @@ async function executeSend(ctx2, params) {
|
|
|
2278
2681
|
}
|
|
2279
2682
|
async function sendTransaction(vault, params) {
|
|
2280
2683
|
const prepareSpinner = createSpinner("Preparing transaction...");
|
|
2281
|
-
const
|
|
2282
|
-
const balance = await vault.balance(params.chain, params.tokenId);
|
|
2283
|
-
const coin = {
|
|
2684
|
+
const dryResult = await vault.send({
|
|
2284
2685
|
chain: params.chain,
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
const isMax = params.amount === "max";
|
|
2291
|
-
let amount;
|
|
2292
|
-
let displayAmount;
|
|
2293
|
-
if (isMax) {
|
|
2294
|
-
const maxInfo = await vault.getMaxSendAmount({ coin, receiver: params.to, memo: params.memo });
|
|
2295
|
-
amount = maxInfo.maxSendable;
|
|
2296
|
-
if (amount === 0n) {
|
|
2297
|
-
throw new Error("Insufficient balance to cover network fees");
|
|
2298
|
-
}
|
|
2299
|
-
displayAmount = formatBigintAmount(amount, balance.decimals);
|
|
2300
|
-
} else {
|
|
2301
|
-
const [whole, frac = ""] = params.amount.split(".");
|
|
2302
|
-
if (frac.length > balance.decimals) {
|
|
2303
|
-
throw new Error(`Amount has more than ${balance.decimals} decimal places`);
|
|
2304
|
-
}
|
|
2305
|
-
const paddedFrac = frac.padEnd(balance.decimals, "0");
|
|
2306
|
-
amount = BigInt(whole || "0") * 10n ** BigInt(balance.decimals) + BigInt(paddedFrac || "0");
|
|
2307
|
-
displayAmount = params.amount;
|
|
2308
|
-
}
|
|
2309
|
-
const payload = await vault.prepareSendTx({
|
|
2310
|
-
coin,
|
|
2311
|
-
receiver: params.to,
|
|
2312
|
-
amount,
|
|
2313
|
-
memo: params.memo
|
|
2686
|
+
to: params.to,
|
|
2687
|
+
amount: params.amount,
|
|
2688
|
+
symbol: params.tokenId,
|
|
2689
|
+
memo: params.memo,
|
|
2690
|
+
dryRun: true
|
|
2314
2691
|
});
|
|
2315
2692
|
prepareSpinner.succeed("Transaction prepared");
|
|
2693
|
+
if (!dryResult.dryRun) throw new Error("unreachable");
|
|
2694
|
+
if (params.dryRun) {
|
|
2695
|
+
const balance2 = await vault.balance(params.chain, params.tokenId);
|
|
2696
|
+
const hasInsufficientBalance = parseFloat(dryResult.total) > parseFloat(balance2.formattedAmount);
|
|
2697
|
+
const result = {
|
|
2698
|
+
dryRun: true,
|
|
2699
|
+
chain: params.chain,
|
|
2700
|
+
to: params.to,
|
|
2701
|
+
amount: params.amount,
|
|
2702
|
+
symbol: balance2.symbol,
|
|
2703
|
+
balance: balance2.formattedAmount
|
|
2704
|
+
};
|
|
2705
|
+
if (hasInsufficientBalance) {
|
|
2706
|
+
result.warning = `Insufficient balance: you have ${balance2.formattedAmount} ${balance2.symbol}`;
|
|
2707
|
+
}
|
|
2708
|
+
if (isJsonOutput()) {
|
|
2709
|
+
outputJson(result);
|
|
2710
|
+
} else {
|
|
2711
|
+
info(`
|
|
2712
|
+
Dry-run preview:`);
|
|
2713
|
+
info(` Chain: ${result.chain}`);
|
|
2714
|
+
info(` To: ${result.to}`);
|
|
2715
|
+
info(` Amount: ${result.amount} ${result.symbol}`);
|
|
2716
|
+
info(` Fee: ${dryResult.fee} ${result.symbol}`);
|
|
2717
|
+
info(` Balance: ${result.balance} ${result.symbol}`);
|
|
2718
|
+
if (result.warning) warn(` Warning: ${result.warning}`);
|
|
2719
|
+
}
|
|
2720
|
+
return result;
|
|
2721
|
+
}
|
|
2316
2722
|
let gas;
|
|
2317
2723
|
try {
|
|
2318
2724
|
gas = await vault.gas(params.chain);
|
|
2319
2725
|
} catch {
|
|
2320
2726
|
warn("\nGas estimation unavailable");
|
|
2321
2727
|
}
|
|
2728
|
+
const balance = await vault.balance(params.chain, params.tokenId);
|
|
2322
2729
|
if (!isJsonOutput()) {
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
params.to,
|
|
2326
|
-
displayAmount,
|
|
2327
|
-
payload.coin.ticker,
|
|
2328
|
-
params.chain,
|
|
2329
|
-
params.memo,
|
|
2330
|
-
gas
|
|
2331
|
-
);
|
|
2730
|
+
const address = await vault.address(params.chain);
|
|
2731
|
+
displayTransactionPreview(address, params.to, dryResult.total, balance.symbol, params.chain, params.memo, gas);
|
|
2332
2732
|
}
|
|
2333
|
-
if (!params.yes
|
|
2733
|
+
if (!params.yes) {
|
|
2734
|
+
if (isNonInteractive()) {
|
|
2735
|
+
throw new Error("Transaction requires confirmation. Use --yes to skip, or --dry-run to preview.");
|
|
2736
|
+
}
|
|
2334
2737
|
const confirmed = await confirmTransaction();
|
|
2335
2738
|
if (!confirmed) {
|
|
2336
2739
|
warn("Transaction cancelled");
|
|
@@ -2338,90 +2741,55 @@ async function sendTransaction(vault, params) {
|
|
|
2338
2741
|
}
|
|
2339
2742
|
}
|
|
2340
2743
|
await ensureVaultUnlocked(vault, params.password);
|
|
2341
|
-
const
|
|
2342
|
-
|
|
2744
|
+
const signSpinner = createSpinner(
|
|
2745
|
+
vault.type === "secure" ? "Preparing secure signing session..." : "Signing transaction..."
|
|
2746
|
+
);
|
|
2343
2747
|
vault.on("signingProgress", ({ step }) => {
|
|
2344
2748
|
signSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2345
2749
|
});
|
|
2346
|
-
if (isSecureVault) {
|
|
2347
|
-
vault.on("qrCodeReady", ({ qrPayload }) => {
|
|
2348
|
-
if (isJsonOutput()) {
|
|
2349
|
-
printResult(qrPayload);
|
|
2350
|
-
} else if (isSilent()) {
|
|
2351
|
-
printResult(`QR Payload: ${qrPayload}`);
|
|
2352
|
-
} else {
|
|
2353
|
-
signSpinner.stop();
|
|
2354
|
-
info("\nScan this QR code with your Vultisig mobile app to sign:");
|
|
2355
|
-
import_qrcode_terminal.default.generate(qrPayload, { small: true });
|
|
2356
|
-
info(`
|
|
2357
|
-
Or use this URL: ${qrPayload}
|
|
2358
|
-
`);
|
|
2359
|
-
signSpinner.start("Waiting for devices to join signing session...");
|
|
2360
|
-
}
|
|
2361
|
-
});
|
|
2362
|
-
vault.on(
|
|
2363
|
-
"deviceJoined",
|
|
2364
|
-
({ deviceId, totalJoined, required }) => {
|
|
2365
|
-
if (!isSilent()) {
|
|
2366
|
-
signSpinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
|
|
2367
|
-
} else if (!isJsonOutput()) {
|
|
2368
|
-
printResult(`Device joined: ${totalJoined}/${required}`);
|
|
2369
|
-
}
|
|
2370
|
-
}
|
|
2371
|
-
);
|
|
2372
|
-
}
|
|
2373
2750
|
try {
|
|
2374
|
-
const
|
|
2375
|
-
const signature = await vault.sign(
|
|
2376
|
-
{
|
|
2377
|
-
transaction: payload,
|
|
2378
|
-
chain: payload.coin.chain,
|
|
2379
|
-
messageHashes
|
|
2380
|
-
},
|
|
2381
|
-
{ signal: params.signal }
|
|
2382
|
-
);
|
|
2383
|
-
signSpinner.succeed("Transaction signed");
|
|
2384
|
-
const broadcastSpinner = createSpinner("Broadcasting transaction...");
|
|
2385
|
-
const txHash = await vault.broadcastTx({
|
|
2751
|
+
const result = await vault.send({
|
|
2386
2752
|
chain: params.chain,
|
|
2387
|
-
|
|
2388
|
-
|
|
2753
|
+
to: params.to,
|
|
2754
|
+
amount: params.amount,
|
|
2755
|
+
symbol: params.tokenId,
|
|
2756
|
+
memo: params.memo
|
|
2389
2757
|
});
|
|
2390
|
-
|
|
2391
|
-
const
|
|
2392
|
-
|
|
2758
|
+
if (result.dryRun) throw new Error("unreachable");
|
|
2759
|
+
const broadcast = result;
|
|
2760
|
+
signSpinner.succeed(`Transaction broadcast: ${broadcast.txHash}`);
|
|
2761
|
+
const txResult = {
|
|
2762
|
+
txHash: broadcast.txHash,
|
|
2393
2763
|
chain: params.chain,
|
|
2394
|
-
explorerUrl: Vultisig2.getTxExplorerUrl(params.chain, txHash)
|
|
2764
|
+
explorerUrl: Vultisig2.getTxExplorerUrl(params.chain, broadcast.txHash)
|
|
2395
2765
|
};
|
|
2396
2766
|
if (isJsonOutput()) {
|
|
2397
|
-
outputJson(
|
|
2767
|
+
outputJson(txResult);
|
|
2398
2768
|
} else {
|
|
2399
|
-
displayTransactionResult(params.chain, txHash);
|
|
2769
|
+
displayTransactionResult(params.chain, broadcast.txHash);
|
|
2400
2770
|
}
|
|
2401
|
-
return
|
|
2771
|
+
return txResult;
|
|
2402
2772
|
} finally {
|
|
2403
2773
|
vault.removeAllListeners("signingProgress");
|
|
2404
|
-
if (isSecureVault) {
|
|
2405
|
-
vault.removeAllListeners("qrCodeReady");
|
|
2406
|
-
vault.removeAllListeners("deviceJoined");
|
|
2407
|
-
}
|
|
2408
2774
|
}
|
|
2409
2775
|
}
|
|
2410
2776
|
|
|
2411
2777
|
// src/commands/execute.ts
|
|
2412
|
-
var
|
|
2778
|
+
var import_qrcode_terminal = __toESM(require_main(), 1);
|
|
2413
2779
|
import { Vultisig as Vultisig3 } from "@vultisig/sdk";
|
|
2414
2780
|
var COSMOS_CHAIN_CONFIG = {
|
|
2415
2781
|
THORChain: {
|
|
2416
2782
|
chainId: "thorchain-1",
|
|
2417
2783
|
prefix: "thor",
|
|
2418
2784
|
denom: "rune",
|
|
2785
|
+
decimals: 8,
|
|
2419
2786
|
gasLimit: "500000"
|
|
2420
2787
|
},
|
|
2421
2788
|
MayaChain: {
|
|
2422
2789
|
chainId: "mayachain-mainnet-v1",
|
|
2423
2790
|
prefix: "maya",
|
|
2424
2791
|
denom: "cacao",
|
|
2792
|
+
decimals: 10,
|
|
2425
2793
|
gasLimit: "500000"
|
|
2426
2794
|
}
|
|
2427
2795
|
};
|
|
@@ -2456,8 +2824,42 @@ async function executeContractTransaction(vault, params, chainConfig, msg, funds
|
|
|
2456
2824
|
const prepareSpinner = createSpinner("Preparing contract execution...");
|
|
2457
2825
|
const address = await vault.address(params.chain);
|
|
2458
2826
|
prepareSpinner.succeed("Transaction prepared");
|
|
2827
|
+
if (params.dryRun) {
|
|
2828
|
+
if (isJsonOutput()) {
|
|
2829
|
+
outputJson({
|
|
2830
|
+
dryRun: true,
|
|
2831
|
+
chain: params.chain,
|
|
2832
|
+
contract: params.contract,
|
|
2833
|
+
msg,
|
|
2834
|
+
funds,
|
|
2835
|
+
address,
|
|
2836
|
+
memo: params.memo
|
|
2837
|
+
});
|
|
2838
|
+
} else {
|
|
2839
|
+
info("\nContract Execution Preview (dry-run)");
|
|
2840
|
+
info("\u2501".repeat(50));
|
|
2841
|
+
info(`Chain: ${params.chain}`);
|
|
2842
|
+
info(`From: ${address}`);
|
|
2843
|
+
info(`Contract: ${params.contract}`);
|
|
2844
|
+
info(
|
|
2845
|
+
`Message: ${JSON.stringify(msg, null, 2).substring(0, 200)}${JSON.stringify(msg).length > 200 ? "..." : ""}`
|
|
2846
|
+
);
|
|
2847
|
+
if (funds.length > 0) {
|
|
2848
|
+
info(`Funds: ${funds.map((f) => `${f.amount} ${f.denom}`).join(", ")}`);
|
|
2849
|
+
}
|
|
2850
|
+
if (params.memo) {
|
|
2851
|
+
info(`Memo: ${params.memo}`);
|
|
2852
|
+
}
|
|
2853
|
+
info("\u2501".repeat(50));
|
|
2854
|
+
}
|
|
2855
|
+
return {
|
|
2856
|
+
txHash: "",
|
|
2857
|
+
chain: params.chain,
|
|
2858
|
+
explorerUrl: ""
|
|
2859
|
+
};
|
|
2860
|
+
}
|
|
2459
2861
|
if (!isJsonOutput()) {
|
|
2460
|
-
info("\
|
|
2862
|
+
info("\nContract Execution Preview");
|
|
2461
2863
|
info("\u2501".repeat(50));
|
|
2462
2864
|
info(`Chain: ${params.chain}`);
|
|
2463
2865
|
info(`From: ${address}`);
|
|
@@ -2473,7 +2875,10 @@ async function executeContractTransaction(vault, params, chainConfig, msg, funds
|
|
|
2473
2875
|
}
|
|
2474
2876
|
info("\u2501".repeat(50));
|
|
2475
2877
|
}
|
|
2476
|
-
if (!params.yes
|
|
2878
|
+
if (!params.yes) {
|
|
2879
|
+
if (isNonInteractive()) {
|
|
2880
|
+
throw new Error("Transaction requires confirmation. Use --yes to skip, or --dry-run to preview.");
|
|
2881
|
+
}
|
|
2477
2882
|
const confirmed = await confirmTransaction();
|
|
2478
2883
|
if (!confirmed) {
|
|
2479
2884
|
warn("Transaction cancelled");
|
|
@@ -2495,7 +2900,7 @@ async function executeContractTransaction(vault, params, chainConfig, msg, funds
|
|
|
2495
2900
|
} else {
|
|
2496
2901
|
signSpinner.stop();
|
|
2497
2902
|
info("\nScan this QR code with your Vultisig mobile app to sign:");
|
|
2498
|
-
|
|
2903
|
+
import_qrcode_terminal.default.generate(qrPayload, { small: true });
|
|
2499
2904
|
info(`
|
|
2500
2905
|
Or use this URL: ${qrPayload}
|
|
2501
2906
|
`);
|
|
@@ -2518,8 +2923,7 @@ Or use this URL: ${qrPayload}
|
|
|
2518
2923
|
const coin = {
|
|
2519
2924
|
chain: cosmosChain,
|
|
2520
2925
|
address,
|
|
2521
|
-
decimals:
|
|
2522
|
-
// THORChain uses 8 decimals
|
|
2926
|
+
decimals: chainConfig.decimals,
|
|
2523
2927
|
ticker: chainConfig.denom.toUpperCase()
|
|
2524
2928
|
};
|
|
2525
2929
|
const executeContractMsg = {
|
|
@@ -2580,7 +2984,7 @@ Or use this URL: ${qrPayload}
|
|
|
2580
2984
|
}
|
|
2581
2985
|
|
|
2582
2986
|
// src/commands/sign.ts
|
|
2583
|
-
var
|
|
2987
|
+
var import_qrcode_terminal2 = __toESM(require_main(), 1);
|
|
2584
2988
|
import { Chain as Chain3 } from "@vultisig/sdk";
|
|
2585
2989
|
async function executeSignBytes(ctx2, params) {
|
|
2586
2990
|
const vault = await ctx2.ensureActiveVault();
|
|
@@ -2606,7 +3010,7 @@ async function signBytes(vault, params) {
|
|
|
2606
3010
|
} else {
|
|
2607
3011
|
signSpinner.stop();
|
|
2608
3012
|
info("\nScan this QR code with your Vultisig mobile app to sign:");
|
|
2609
|
-
|
|
3013
|
+
import_qrcode_terminal2.default.generate(qrPayload, { small: true });
|
|
2610
3014
|
info(`
|
|
2611
3015
|
Or use this URL: ${qrPayload}
|
|
2612
3016
|
`);
|
|
@@ -2756,7 +3160,7 @@ function sleep(ms) {
|
|
|
2756
3160
|
}
|
|
2757
3161
|
|
|
2758
3162
|
// src/commands/vault-management.ts
|
|
2759
|
-
var
|
|
3163
|
+
var import_qrcode_terminal3 = __toESM(require_main(), 1);
|
|
2760
3164
|
import { FastVault } from "@vultisig/sdk";
|
|
2761
3165
|
import chalk5 from "chalk";
|
|
2762
3166
|
import { promises as fs } from "fs";
|
|
@@ -2904,7 +3308,7 @@ async function executeCreateSecure(ctx2, options) {
|
|
|
2904
3308
|
} else {
|
|
2905
3309
|
spinner.stop();
|
|
2906
3310
|
info("\nScan this QR code with your Vultisig mobile app:");
|
|
2907
|
-
|
|
3311
|
+
import_qrcode_terminal3.default.generate(qrPayload, { small: true });
|
|
2908
3312
|
info(`
|
|
2909
3313
|
Or use this URL: ${qrPayload}
|
|
2910
3314
|
`);
|
|
@@ -3427,7 +3831,7 @@ async function executeCreateFromSeedphraseSecure(ctx2, options) {
|
|
|
3427
3831
|
} else {
|
|
3428
3832
|
importSpinner.stop();
|
|
3429
3833
|
info("\nScan this QR code with your Vultisig mobile app:");
|
|
3430
|
-
|
|
3834
|
+
import_qrcode_terminal3.default.generate(qrPayload, { small: true });
|
|
3431
3835
|
info(`
|
|
3432
3836
|
Or use this URL: ${qrPayload}
|
|
3433
3837
|
`);
|
|
@@ -3616,35 +4020,24 @@ async function executeSwapQuote(ctx2, options) {
|
|
|
3616
4020
|
if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
|
|
3617
4021
|
throw new Error("Invalid amount");
|
|
3618
4022
|
}
|
|
3619
|
-
const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
|
|
3620
|
-
if (!isSupported) {
|
|
3621
|
-
throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
|
|
3622
|
-
}
|
|
3623
|
-
let resolvedAmount;
|
|
3624
|
-
if (isMax) {
|
|
3625
|
-
const bal = await vault.balance(options.fromChain, options.fromToken);
|
|
3626
|
-
resolvedAmount = parseFloat(bal.formattedAmount);
|
|
3627
|
-
if (resolvedAmount <= 0) {
|
|
3628
|
-
throw new Error("Zero balance \u2014 nothing to swap");
|
|
3629
|
-
}
|
|
3630
|
-
} else {
|
|
3631
|
-
resolvedAmount = options.amount;
|
|
3632
|
-
}
|
|
3633
4023
|
const spinner = createSpinner("Getting swap quote...");
|
|
3634
|
-
const
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
4024
|
+
const result = await vault.swap({
|
|
4025
|
+
fromChain: options.fromChain,
|
|
4026
|
+
fromSymbol: options.fromToken || "",
|
|
4027
|
+
toChain: options.toChain,
|
|
4028
|
+
toSymbol: options.toToken || "",
|
|
4029
|
+
amount: isMax ? "max" : String(options.amount),
|
|
4030
|
+
dryRun: true
|
|
3640
4031
|
});
|
|
4032
|
+
if (!result.dryRun) throw new Error("unreachable");
|
|
3641
4033
|
spinner.succeed("Quote received");
|
|
3642
|
-
const
|
|
4034
|
+
const quote = result.quote;
|
|
4035
|
+
const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(options.amount);
|
|
3643
4036
|
if (isJsonOutput()) {
|
|
3644
4037
|
outputJson({
|
|
3645
4038
|
fromChain: options.fromChain,
|
|
3646
4039
|
toChain: options.toChain,
|
|
3647
|
-
amount:
|
|
4040
|
+
amount: isMax ? "max" : options.amount,
|
|
3648
4041
|
isMax,
|
|
3649
4042
|
quote
|
|
3650
4043
|
});
|
|
@@ -3668,30 +4061,57 @@ async function executeSwap(ctx2, options) {
|
|
|
3668
4061
|
if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
|
|
3669
4062
|
throw new Error("Invalid amount");
|
|
3670
4063
|
}
|
|
3671
|
-
const
|
|
3672
|
-
if (!isSupported) {
|
|
3673
|
-
throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
|
|
3674
|
-
}
|
|
3675
|
-
let resolvedAmount;
|
|
3676
|
-
if (isMax) {
|
|
3677
|
-
const bal = await vault.balance(options.fromChain, options.fromToken);
|
|
3678
|
-
resolvedAmount = parseFloat(bal.formattedAmount);
|
|
3679
|
-
if (resolvedAmount <= 0) {
|
|
3680
|
-
throw new Error("Zero balance \u2014 nothing to swap");
|
|
3681
|
-
}
|
|
3682
|
-
} else {
|
|
3683
|
-
resolvedAmount = options.amount;
|
|
3684
|
-
}
|
|
4064
|
+
const amountStr = isMax ? "max" : String(options.amount);
|
|
3685
4065
|
const quoteSpinner = createSpinner("Getting swap quote...");
|
|
3686
|
-
const
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
4066
|
+
const dryResult = await vault.swap({
|
|
4067
|
+
fromChain: options.fromChain,
|
|
4068
|
+
fromSymbol: options.fromToken || "",
|
|
4069
|
+
toChain: options.toChain,
|
|
4070
|
+
toSymbol: options.toToken || "",
|
|
4071
|
+
amount: amountStr,
|
|
4072
|
+
dryRun: true
|
|
3692
4073
|
});
|
|
4074
|
+
if (!dryResult.dryRun) throw new Error("unreachable");
|
|
3693
4075
|
quoteSpinner.succeed("Quote received");
|
|
3694
|
-
const
|
|
4076
|
+
const quote = dryResult.quote;
|
|
4077
|
+
const fromAmountRaw = isMax ? formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals) : String(options.amount);
|
|
4078
|
+
const fromAmountDisplay = isMax ? `${fromAmountRaw} (max)` : fromAmountRaw;
|
|
4079
|
+
if (options.dryRun) {
|
|
4080
|
+
const estimatedOutput = formatBigintAmount(quote.estimatedOutput, quote.toCoin.decimals);
|
|
4081
|
+
const result = {
|
|
4082
|
+
dryRun: true,
|
|
4083
|
+
fromChain: String(options.fromChain),
|
|
4084
|
+
fromToken: quote.fromCoin.ticker,
|
|
4085
|
+
toChain: String(options.toChain),
|
|
4086
|
+
toToken: quote.toCoin.ticker,
|
|
4087
|
+
inputAmount: fromAmountRaw,
|
|
4088
|
+
...isMax && { isMax: true },
|
|
4089
|
+
estimatedOutput,
|
|
4090
|
+
provider: quote.provider
|
|
4091
|
+
};
|
|
4092
|
+
if (quote.estimatedOutputFiat != null) {
|
|
4093
|
+
result.estimatedOutputFiat = parseFloat(quote.estimatedOutputFiat.toFixed(2));
|
|
4094
|
+
}
|
|
4095
|
+
if (quote.requiresApproval) {
|
|
4096
|
+
result.requiresApproval = true;
|
|
4097
|
+
}
|
|
4098
|
+
if (quote.warnings && quote.warnings.length > 0) {
|
|
4099
|
+
result.warnings = [...quote.warnings];
|
|
4100
|
+
}
|
|
4101
|
+
if (isJsonOutput()) {
|
|
4102
|
+
outputJson(result);
|
|
4103
|
+
} else {
|
|
4104
|
+
info(`
|
|
4105
|
+
Dry-run preview:`);
|
|
4106
|
+
info(` From: ${result.inputAmount} ${result.fromToken} (${result.fromChain})`);
|
|
4107
|
+
info(` To: ${result.estimatedOutput} ${result.toToken} (${result.toChain})`);
|
|
4108
|
+
info(` Provider: ${result.provider}`);
|
|
4109
|
+
if (result.estimatedOutputFiat != null) info(` Est. value (USD): $${result.estimatedOutputFiat}`);
|
|
4110
|
+
if (result.requiresApproval) info(` Requires approval: yes`);
|
|
4111
|
+
if (result.warnings?.length) result.warnings.forEach((w) => warn(` Warning: ${w}`));
|
|
4112
|
+
}
|
|
4113
|
+
return result;
|
|
4114
|
+
}
|
|
3695
4115
|
const feeBalance = await vault.balance(options.fromChain);
|
|
3696
4116
|
const discountTier = await vault.getDiscountTier();
|
|
3697
4117
|
if (!isJsonOutput()) {
|
|
@@ -3703,86 +4123,43 @@ async function executeSwap(ctx2, options) {
|
|
|
3703
4123
|
discountTier
|
|
3704
4124
|
});
|
|
3705
4125
|
}
|
|
3706
|
-
if (!options.yes
|
|
4126
|
+
if (!options.yes) {
|
|
4127
|
+
if (isNonInteractive()) {
|
|
4128
|
+
throw new Error("Swap requires confirmation. Use --yes to skip, or --dry-run to preview.");
|
|
4129
|
+
}
|
|
3707
4130
|
const confirmed = await confirmSwap();
|
|
3708
4131
|
if (!confirmed) {
|
|
3709
4132
|
warn("Swap cancelled");
|
|
3710
4133
|
throw new Error("Swap cancelled by user");
|
|
3711
4134
|
}
|
|
3712
4135
|
}
|
|
3713
|
-
const prepSpinner = createSpinner("Preparing swap transaction...");
|
|
3714
|
-
const { keysignPayload, approvalPayload } = await vault.prepareSwapTx({
|
|
3715
|
-
fromCoin: { chain: options.fromChain, token: options.fromToken },
|
|
3716
|
-
toCoin: { chain: options.toChain, token: options.toToken },
|
|
3717
|
-
amount: resolvedAmount,
|
|
3718
|
-
swapQuote: quote,
|
|
3719
|
-
autoApprove: false
|
|
3720
|
-
});
|
|
3721
|
-
prepSpinner.succeed("Swap prepared");
|
|
3722
4136
|
await ensureVaultUnlocked(vault, options.password);
|
|
3723
|
-
if (approvalPayload) {
|
|
3724
|
-
info("\nToken approval required before swap...");
|
|
3725
|
-
const approvalSpinner = createSpinner("Signing approval transaction...");
|
|
3726
|
-
vault.on("signingProgress", ({ step }) => {
|
|
3727
|
-
approvalSpinner.text = `Approval: ${step.message} (${step.progress}%)`;
|
|
3728
|
-
});
|
|
3729
|
-
try {
|
|
3730
|
-
const approvalHashes = await vault.extractMessageHashes(approvalPayload);
|
|
3731
|
-
const approvalSig = await vault.sign(
|
|
3732
|
-
{
|
|
3733
|
-
transaction: approvalPayload,
|
|
3734
|
-
chain: options.fromChain,
|
|
3735
|
-
messageHashes: approvalHashes
|
|
3736
|
-
},
|
|
3737
|
-
{ signal: options.signal }
|
|
3738
|
-
);
|
|
3739
|
-
approvalSpinner.succeed("Approval signed");
|
|
3740
|
-
const broadcastApprovalSpinner = createSpinner("Broadcasting approval...");
|
|
3741
|
-
const approvalTxHash = await vault.broadcastTx({
|
|
3742
|
-
chain: options.fromChain,
|
|
3743
|
-
keysignPayload: approvalPayload,
|
|
3744
|
-
signature: approvalSig
|
|
3745
|
-
});
|
|
3746
|
-
broadcastApprovalSpinner.succeed(`Approval broadcast: ${approvalTxHash}`);
|
|
3747
|
-
info("Waiting for approval to confirm...");
|
|
3748
|
-
await new Promise((resolve) => setTimeout(resolve, 5e3));
|
|
3749
|
-
} finally {
|
|
3750
|
-
vault.removeAllListeners("signingProgress");
|
|
3751
|
-
}
|
|
3752
|
-
}
|
|
3753
4137
|
const signSpinner = createSpinner("Signing swap transaction...");
|
|
3754
4138
|
vault.on("signingProgress", ({ step }) => {
|
|
3755
4139
|
signSpinner.text = `${step.message} (${step.progress}%)`;
|
|
3756
4140
|
});
|
|
3757
4141
|
try {
|
|
3758
|
-
const
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
3764
|
-
},
|
|
3765
|
-
{ signal: options.signal }
|
|
3766
|
-
);
|
|
3767
|
-
signSpinner.succeed("Swap transaction signed");
|
|
3768
|
-
const broadcastSpinner = createSpinner("Broadcasting swap transaction...");
|
|
3769
|
-
const txHash = await vault.broadcastTx({
|
|
3770
|
-
chain: options.fromChain,
|
|
3771
|
-
keysignPayload,
|
|
3772
|
-
signature
|
|
4142
|
+
const result = await vault.swap({
|
|
4143
|
+
fromChain: options.fromChain,
|
|
4144
|
+
fromSymbol: options.fromToken || "",
|
|
4145
|
+
toChain: options.toChain,
|
|
4146
|
+
toSymbol: options.toToken || "",
|
|
4147
|
+
amount: amountStr
|
|
3773
4148
|
});
|
|
3774
|
-
|
|
4149
|
+
if (result.dryRun) throw new Error("unreachable");
|
|
4150
|
+
const broadcast = result;
|
|
4151
|
+
signSpinner.succeed(`Swap broadcast: ${broadcast.txHash}`);
|
|
3775
4152
|
if (isJsonOutput()) {
|
|
3776
4153
|
outputJson({
|
|
3777
|
-
txHash,
|
|
4154
|
+
txHash: broadcast.txHash,
|
|
3778
4155
|
fromChain: options.fromChain,
|
|
3779
4156
|
toChain: options.toChain,
|
|
3780
4157
|
quote
|
|
3781
4158
|
});
|
|
3782
4159
|
} else {
|
|
3783
|
-
displaySwapResult(options.fromChain, options.toChain, txHash, quote, quote.toCoin.decimals);
|
|
4160
|
+
displaySwapResult(options.fromChain, options.toChain, broadcast.txHash, quote, quote.toCoin.decimals);
|
|
3784
4161
|
}
|
|
3785
|
-
return { txHash, quote };
|
|
4162
|
+
return { txHash: broadcast.txHash, quote };
|
|
3786
4163
|
} finally {
|
|
3787
4164
|
vault.removeAllListeners("signingProgress");
|
|
3788
4165
|
}
|
|
@@ -4067,20 +4444,31 @@ async function executeRujiraSwap(ctx2, options) {
|
|
|
4067
4444
|
slippageBps: options.slippageBps
|
|
4068
4445
|
});
|
|
4069
4446
|
quoteSpinner.succeed("Quote received");
|
|
4070
|
-
if (
|
|
4071
|
-
|
|
4072
|
-
|
|
4447
|
+
if (options.dryRun) {
|
|
4448
|
+
if (isJsonOutput()) {
|
|
4449
|
+
outputJson({ dryRun: true, quote });
|
|
4450
|
+
} else {
|
|
4451
|
+
printResult("FIN Swap Preview (dry-run)");
|
|
4452
|
+
printResult(` From: ${options.fromAsset}`);
|
|
4453
|
+
printResult(` To: ${options.toAsset}`);
|
|
4454
|
+
printResult(` Amount (in): ${options.amount}`);
|
|
4455
|
+
printResult(` Expected out:${quote.expectedOutput}`);
|
|
4456
|
+
printResult(` Min out: ${quote.minimumOutput}`);
|
|
4457
|
+
printResult(` Contract: ${quote.contractAddress}`);
|
|
4458
|
+
}
|
|
4073
4459
|
return;
|
|
4074
4460
|
}
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4461
|
+
if (!isJsonOutput()) {
|
|
4462
|
+
printResult("FIN Swap Preview");
|
|
4463
|
+
printResult(` From: ${options.fromAsset}`);
|
|
4464
|
+
printResult(` To: ${options.toAsset}`);
|
|
4465
|
+
printResult(` Amount (in): ${options.amount}`);
|
|
4466
|
+
printResult(` Expected out:${quote.expectedOutput}`);
|
|
4467
|
+
printResult(` Min out: ${quote.minimumOutput}`);
|
|
4468
|
+
printResult(` Contract: ${quote.contractAddress}`);
|
|
4469
|
+
if (quote.warning) {
|
|
4470
|
+
warn(quote.warning);
|
|
4471
|
+
}
|
|
4084
4472
|
}
|
|
4085
4473
|
if (!options.yes) {
|
|
4086
4474
|
warn("This command will execute a swap. Re-run with -y/--yes to skip this warning.");
|
|
@@ -4089,7 +4477,11 @@ async function executeRujiraSwap(ctx2, options) {
|
|
|
4089
4477
|
const execSpinner = createSpinner("Executing FIN swap...");
|
|
4090
4478
|
const result = await client.swap.execute(quote, { slippageBps: options.slippageBps });
|
|
4091
4479
|
execSpinner.succeed("Swap submitted");
|
|
4092
|
-
|
|
4480
|
+
if (isJsonOutput()) {
|
|
4481
|
+
outputJson({ quote, result });
|
|
4482
|
+
} else {
|
|
4483
|
+
printResult(`Tx Hash: ${result.txHash}`);
|
|
4484
|
+
}
|
|
4093
4485
|
}
|
|
4094
4486
|
async function executeRujiraWithdraw(ctx2, options) {
|
|
4095
4487
|
const vault = await ctx2.ensureActiveVault();
|
|
@@ -4103,17 +4495,27 @@ async function executeRujiraWithdraw(ctx2, options) {
|
|
|
4103
4495
|
maxFeeBps: options.maxFeeBps
|
|
4104
4496
|
});
|
|
4105
4497
|
prepSpinner.succeed("Withdrawal prepared");
|
|
4106
|
-
if (
|
|
4107
|
-
|
|
4108
|
-
|
|
4498
|
+
if (options.dryRun) {
|
|
4499
|
+
if (isJsonOutput()) {
|
|
4500
|
+
outputJson({ dryRun: true, prepared });
|
|
4501
|
+
} else {
|
|
4502
|
+
printResult("Withdraw Preview (dry-run)");
|
|
4503
|
+
printResult(` Asset: ${prepared.asset}`);
|
|
4504
|
+
printResult(` Amount: ${prepared.amount}`);
|
|
4505
|
+
printResult(` Destination: ${prepared.destination}`);
|
|
4506
|
+
printResult(` Memo: ${prepared.memo}`);
|
|
4507
|
+
printResult(` Est. fee: ${prepared.estimatedFee}`);
|
|
4508
|
+
}
|
|
4109
4509
|
return;
|
|
4110
4510
|
}
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4511
|
+
if (!isJsonOutput()) {
|
|
4512
|
+
printResult("Withdraw Preview");
|
|
4513
|
+
printResult(` Asset: ${prepared.asset}`);
|
|
4514
|
+
printResult(` Amount: ${prepared.amount}`);
|
|
4515
|
+
printResult(` Destination: ${prepared.destination}`);
|
|
4516
|
+
printResult(` Memo: ${prepared.memo}`);
|
|
4517
|
+
printResult(` Est. fee: ${prepared.estimatedFee}`);
|
|
4518
|
+
}
|
|
4117
4519
|
if (!options.yes) {
|
|
4118
4520
|
warn("This command will broadcast a THORChain MsgDeposit withdrawal. Re-run with -y/--yes to proceed.");
|
|
4119
4521
|
throw new Error("Confirmation required (use --yes)");
|
|
@@ -4121,7 +4523,11 @@ async function executeRujiraWithdraw(ctx2, options) {
|
|
|
4121
4523
|
const execSpinner = createSpinner("Broadcasting withdrawal...");
|
|
4122
4524
|
const result = await client.withdraw.execute(prepared);
|
|
4123
4525
|
execSpinner.succeed("Withdrawal submitted");
|
|
4124
|
-
|
|
4526
|
+
if (isJsonOutput()) {
|
|
4527
|
+
outputJson({ prepared, result });
|
|
4528
|
+
} else {
|
|
4529
|
+
printResult(`Tx Hash: ${result.txHash}`);
|
|
4530
|
+
}
|
|
4125
4531
|
}
|
|
4126
4532
|
|
|
4127
4533
|
// src/commands/discount.ts
|
|
@@ -4222,10 +4628,129 @@ function displayDiscountTier(tierInfo) {
|
|
|
4222
4628
|
printResult("");
|
|
4223
4629
|
}
|
|
4224
4630
|
|
|
4631
|
+
// src/commands/auth.ts
|
|
4632
|
+
import { executeAuthLogout, executeAuthSetup, executeAuthStatus } from "@vultisig/client-shared";
|
|
4633
|
+
|
|
4225
4634
|
// src/commands/agent.ts
|
|
4226
4635
|
import chalk9 from "chalk";
|
|
4227
4636
|
import Table from "cli-table3";
|
|
4228
4637
|
|
|
4638
|
+
// src/agent/agentErrors.ts
|
|
4639
|
+
import { VaultError as VaultError2, VaultErrorCode as VaultErrorCode2, VaultImportError as VaultImportError2, VaultImportErrorCode as VaultImportErrorCode2 } from "@vultisig/sdk";
|
|
4640
|
+
var AgentErrorCode = /* @__PURE__ */ ((AgentErrorCode3) => {
|
|
4641
|
+
AgentErrorCode3["BACKEND_UNREACHABLE"] = "BACKEND_UNREACHABLE";
|
|
4642
|
+
AgentErrorCode3["AUTH_FAILED"] = "AUTH_FAILED";
|
|
4643
|
+
AgentErrorCode3["VAULT_LOCKED"] = "VAULT_LOCKED";
|
|
4644
|
+
AgentErrorCode3["PASSWORD_REQUIRED"] = "PASSWORD_REQUIRED";
|
|
4645
|
+
AgentErrorCode3["CONFIRMATION_REQUIRED"] = "CONFIRMATION_REQUIRED";
|
|
4646
|
+
AgentErrorCode3["ACTION_NOT_IMPLEMENTED"] = "ACTION_NOT_IMPLEMENTED";
|
|
4647
|
+
AgentErrorCode3["INVALID_INPUT"] = "INVALID_INPUT";
|
|
4648
|
+
AgentErrorCode3["NETWORK_ERROR"] = "NETWORK_ERROR";
|
|
4649
|
+
AgentErrorCode3["TIMEOUT"] = "TIMEOUT";
|
|
4650
|
+
AgentErrorCode3["TRANSACTION_FAILED"] = "TRANSACTION_FAILED";
|
|
4651
|
+
AgentErrorCode3["SIGNING_FAILED"] = "SIGNING_FAILED";
|
|
4652
|
+
AgentErrorCode3["SESSION_NOT_INITIALIZED"] = "SESSION_NOT_INITIALIZED";
|
|
4653
|
+
AgentErrorCode3["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
|
|
4654
|
+
return AgentErrorCode3;
|
|
4655
|
+
})(AgentErrorCode || {});
|
|
4656
|
+
var AGENT_ERROR_CODE_VALUES = new Set(Object.values(AgentErrorCode));
|
|
4657
|
+
function isAgentErrorCode(value) {
|
|
4658
|
+
return AGENT_ERROR_CODE_VALUES.has(value);
|
|
4659
|
+
}
|
|
4660
|
+
function mapVaultError(err) {
|
|
4661
|
+
if (err.code === VaultErrorCode2.InvalidConfig && /failed to unlock vault/i.test(err.message)) {
|
|
4662
|
+
return "AUTH_FAILED" /* AUTH_FAILED */;
|
|
4663
|
+
}
|
|
4664
|
+
switch (err.code) {
|
|
4665
|
+
case VaultErrorCode2.Timeout:
|
|
4666
|
+
return "TIMEOUT" /* TIMEOUT */;
|
|
4667
|
+
case VaultErrorCode2.NetworkError:
|
|
4668
|
+
case VaultErrorCode2.BalanceFetchFailed:
|
|
4669
|
+
return "NETWORK_ERROR" /* NETWORK_ERROR */;
|
|
4670
|
+
case VaultErrorCode2.SigningFailed:
|
|
4671
|
+
return "SIGNING_FAILED" /* SIGNING_FAILED */;
|
|
4672
|
+
case VaultErrorCode2.BroadcastFailed:
|
|
4673
|
+
case VaultErrorCode2.GasEstimationFailed:
|
|
4674
|
+
return "TRANSACTION_FAILED" /* TRANSACTION_FAILED */;
|
|
4675
|
+
case VaultErrorCode2.NotImplemented:
|
|
4676
|
+
return "ACTION_NOT_IMPLEMENTED" /* ACTION_NOT_IMPLEMENTED */;
|
|
4677
|
+
case VaultErrorCode2.InvalidAmount:
|
|
4678
|
+
case VaultErrorCode2.UnsupportedChain:
|
|
4679
|
+
case VaultErrorCode2.UnsupportedToken:
|
|
4680
|
+
case VaultErrorCode2.ChainNotSupported:
|
|
4681
|
+
case VaultErrorCode2.InvalidVault:
|
|
4682
|
+
case VaultErrorCode2.InvalidPublicKey:
|
|
4683
|
+
case VaultErrorCode2.InvalidChainCode:
|
|
4684
|
+
case VaultErrorCode2.AddressDerivationFailed:
|
|
4685
|
+
return "INVALID_INPUT" /* INVALID_INPUT */;
|
|
4686
|
+
case VaultErrorCode2.InvalidConfig:
|
|
4687
|
+
return "INVALID_INPUT" /* INVALID_INPUT */;
|
|
4688
|
+
default:
|
|
4689
|
+
return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
4692
|
+
function mapVaultImportError(err) {
|
|
4693
|
+
switch (err.code) {
|
|
4694
|
+
case VaultImportErrorCode2.PASSWORD_REQUIRED:
|
|
4695
|
+
return "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */;
|
|
4696
|
+
case VaultImportErrorCode2.INVALID_PASSWORD:
|
|
4697
|
+
return "AUTH_FAILED" /* AUTH_FAILED */;
|
|
4698
|
+
default:
|
|
4699
|
+
return "INVALID_INPUT" /* INVALID_INPUT */;
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
function networkishMessage(msg) {
|
|
4703
|
+
return /ECONNREFUSED|ENOTFOUND|ETIMEDOUT|network|fetch failed|socket/i.test(msg) || /getaddrinfo|certificate|TLS|SSL/i.test(msg);
|
|
4704
|
+
}
|
|
4705
|
+
function inferAgentErrorCodeFromMessage(message) {
|
|
4706
|
+
const m = message.trim();
|
|
4707
|
+
if (!m) return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
|
|
4708
|
+
if (/agent backend unreachable/i.test(m)) return "BACKEND_UNREACHABLE" /* BACKEND_UNREACHABLE */;
|
|
4709
|
+
if (/authentication failed|^auth failed/i.test(m)) return "AUTH_FAILED" /* AUTH_FAILED */;
|
|
4710
|
+
if (m === "PASSWORD_REQUIRED") return "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */;
|
|
4711
|
+
if (/^CONFIRMATION_REQUIRED:/i.test(m)) return "CONFIRMATION_REQUIRED" /* CONFIRMATION_REQUIRED */;
|
|
4712
|
+
if (/password required|password not provided|use --password/i.test(m)) return "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */;
|
|
4713
|
+
if (/session not initialized/i.test(m)) return "SESSION_NOT_INITIALIZED" /* SESSION_NOT_INITIALIZED */;
|
|
4714
|
+
if (/not implemented locally|is not yet implemented|is not implemented locally|action type .*not implemented/i.test(m)) {
|
|
4715
|
+
return "ACTION_NOT_IMPLEMENTED" /* ACTION_NOT_IMPLEMENTED */;
|
|
4716
|
+
}
|
|
4717
|
+
if (/\(401\)|\(403\)|\b401\b|\b403\b|unauthorized|forbidden/i.test(m)) return "AUTH_FAILED" /* AUTH_FAILED */;
|
|
4718
|
+
if (/failed to unlock vault/i.test(m)) return "AUTH_FAILED" /* AUTH_FAILED */;
|
|
4719
|
+
if (/vault.*locked|must unlock|unlock.*vault/i.test(m)) return "VAULT_LOCKED" /* VAULT_LOCKED */;
|
|
4720
|
+
if (/timed out|timeout/i.test(m)) return "TIMEOUT" /* TIMEOUT */;
|
|
4721
|
+
if (networkishMessage(m)) return "NETWORK_ERROR" /* NETWORK_ERROR */;
|
|
4722
|
+
if (/unknown chain|unknown from_chain|unknown to_chain/i.test(m) || /\bis required\b|\bmissing\b|\brequires\b/i.test(m) || /no pending transaction|invalid or empty tx|could not stage calldata|server transaction missing/i.test(m) || /build_custom_tx requires|incomplete for a contract call|invalid der:/i.test(m)) {
|
|
4723
|
+
return "INVALID_INPUT" /* INVALID_INPUT */;
|
|
4724
|
+
}
|
|
4725
|
+
return "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
|
|
4726
|
+
}
|
|
4727
|
+
function nodeErrCode(err) {
|
|
4728
|
+
if (err && typeof err === "object" && "code" in err && typeof err.code === "string") {
|
|
4729
|
+
return err.code;
|
|
4730
|
+
}
|
|
4731
|
+
return void 0;
|
|
4732
|
+
}
|
|
4733
|
+
function normalizeAgentError(err) {
|
|
4734
|
+
if (err instanceof VaultError2) {
|
|
4735
|
+
return { code: mapVaultError(err), message: err.message };
|
|
4736
|
+
}
|
|
4737
|
+
if (err instanceof VaultImportError2) {
|
|
4738
|
+
return { code: mapVaultImportError(err), message: err.message };
|
|
4739
|
+
}
|
|
4740
|
+
const name = err instanceof Error ? err.name : "";
|
|
4741
|
+
if (name === "AbortError") {
|
|
4742
|
+
const message2 = err instanceof Error ? err.message : "Aborted";
|
|
4743
|
+
const code = /timed out|timeout/i.test(message2) ? "TIMEOUT" /* TIMEOUT */ : "UNKNOWN_ERROR" /* UNKNOWN_ERROR */;
|
|
4744
|
+
return { code, message: message2 };
|
|
4745
|
+
}
|
|
4746
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4747
|
+
const nc = nodeErrCode(err);
|
|
4748
|
+
if (nc === "ECONNREFUSED" || nc === "ENOTFOUND" || nc === "ETIMEDOUT") {
|
|
4749
|
+
return { code: "NETWORK_ERROR" /* NETWORK_ERROR */, message };
|
|
4750
|
+
}
|
|
4751
|
+
return { code: inferAgentErrorCodeFromMessage(message), message };
|
|
4752
|
+
}
|
|
4753
|
+
|
|
4229
4754
|
// src/agent/ask.ts
|
|
4230
4755
|
var AskInterface = class {
|
|
4231
4756
|
session;
|
|
@@ -4252,10 +4777,10 @@ var AskInterface = class {
|
|
|
4252
4777
|
`);
|
|
4253
4778
|
}
|
|
4254
4779
|
},
|
|
4255
|
-
onToolResult: (_id, action, success2, data, error2) => {
|
|
4256
|
-
this.toolCalls.push({ action, success: success2, data, error: error2 });
|
|
4780
|
+
onToolResult: (_id, action, success2, data, error2, code) => {
|
|
4781
|
+
this.toolCalls.push({ action, success: success2, data, error: error2, code });
|
|
4257
4782
|
if (this.verbose) {
|
|
4258
|
-
const status = success2 ? "ok" : `error: ${error2}`;
|
|
4783
|
+
const status = success2 ? "ok" : `error: ${error2}${code ? ` [${code}]` : ""}`;
|
|
4259
4784
|
process.stderr.write(`[tool] ${action}: ${status}
|
|
4260
4785
|
`);
|
|
4261
4786
|
}
|
|
@@ -4274,8 +4799,8 @@ var AskInterface = class {
|
|
|
4274
4799
|
`);
|
|
4275
4800
|
}
|
|
4276
4801
|
},
|
|
4277
|
-
onError: (message) => {
|
|
4278
|
-
process.stderr.write(`[error] ${message}
|
|
4802
|
+
onError: (message, code) => {
|
|
4803
|
+
process.stderr.write(`[error] ${message} [${code}]
|
|
4279
4804
|
`);
|
|
4280
4805
|
},
|
|
4281
4806
|
onDone: () => {
|
|
@@ -4406,6 +4931,17 @@ function padTo32Bytes(buf) {
|
|
|
4406
4931
|
}
|
|
4407
4932
|
|
|
4408
4933
|
// src/agent/client.ts
|
|
4934
|
+
function sseErrorToMessage(value) {
|
|
4935
|
+
if (value == null) return "";
|
|
4936
|
+
if (typeof value === "string") return value;
|
|
4937
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") return String(value);
|
|
4938
|
+
if (value instanceof Error) return value.message;
|
|
4939
|
+
try {
|
|
4940
|
+
return JSON.stringify(value);
|
|
4941
|
+
} catch {
|
|
4942
|
+
return String(value);
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4409
4945
|
var AgentClient = class {
|
|
4410
4946
|
baseUrl;
|
|
4411
4947
|
authToken = null;
|
|
@@ -4460,7 +4996,9 @@ var AgentClient = class {
|
|
|
4460
4996
|
return this.post(`/agent/conversations/${conversationId}`, req);
|
|
4461
4997
|
}
|
|
4462
4998
|
async deleteConversation(conversationId, publicKey) {
|
|
4463
|
-
await this.delete(`/agent/conversations/${conversationId}`, {
|
|
4999
|
+
await this.delete(`/agent/conversations/${conversationId}`, {
|
|
5000
|
+
public_key: publicKey
|
|
5001
|
+
});
|
|
4464
5002
|
}
|
|
4465
5003
|
// ============================================================================
|
|
4466
5004
|
// Messages - JSON mode
|
|
@@ -4542,8 +5080,30 @@ var AgentClient = class {
|
|
|
4542
5080
|
}
|
|
4543
5081
|
handleSSEEvent(event, data, result, callbacks) {
|
|
4544
5082
|
try {
|
|
4545
|
-
const
|
|
4546
|
-
|
|
5083
|
+
const rawParsed = JSON.parse(data);
|
|
5084
|
+
let resolvedEvent = event;
|
|
5085
|
+
let parsed = rawParsed;
|
|
5086
|
+
if (event === "message" && typeof rawParsed?.type === "string") {
|
|
5087
|
+
const v1Type = rawParsed.type;
|
|
5088
|
+
if (v1Type === "text-delta") {
|
|
5089
|
+
resolvedEvent = "text_delta";
|
|
5090
|
+
} else if (v1Type === "finish") {
|
|
5091
|
+
resolvedEvent = "done";
|
|
5092
|
+
} else if (v1Type === "error") {
|
|
5093
|
+
resolvedEvent = "error";
|
|
5094
|
+
parsed = { error: rawParsed.errorText ?? rawParsed.error };
|
|
5095
|
+
} else if (v1Type.startsWith("data-")) {
|
|
5096
|
+
resolvedEvent = v1Type.slice(5);
|
|
5097
|
+
if (rawParsed.data && typeof rawParsed.data === "object" && !Array.isArray(rawParsed.data)) {
|
|
5098
|
+
parsed = rawParsed.data;
|
|
5099
|
+
}
|
|
5100
|
+
} else {
|
|
5101
|
+
resolvedEvent = v1Type;
|
|
5102
|
+
if (this.verbose) process.stderr.write(`[SSE:unmapped v1 type: ${v1Type}]
|
|
5103
|
+
`);
|
|
5104
|
+
}
|
|
5105
|
+
}
|
|
5106
|
+
switch (resolvedEvent) {
|
|
4547
5107
|
case "text_delta":
|
|
4548
5108
|
if (typeof parsed.delta === "string") {
|
|
4549
5109
|
result.fullText += parsed.delta;
|
|
@@ -4581,9 +5141,12 @@ var AgentClient = class {
|
|
|
4581
5141
|
result.message = parsed.message || parsed;
|
|
4582
5142
|
callbacks.onMessage?.(result.message);
|
|
4583
5143
|
break;
|
|
4584
|
-
case "error":
|
|
4585
|
-
|
|
5144
|
+
case "error": {
|
|
5145
|
+
const msg = sseErrorToMessage(parsed.error);
|
|
5146
|
+
const codeFromBackend = typeof parsed.code === "string" && isAgentErrorCode(parsed.code) ? parsed.code : inferAgentErrorCodeFromMessage(msg);
|
|
5147
|
+
callbacks.onError?.(msg, codeFromBackend);
|
|
4586
5148
|
break;
|
|
5149
|
+
}
|
|
4587
5150
|
case "done":
|
|
4588
5151
|
break;
|
|
4589
5152
|
}
|
|
@@ -5015,6 +5578,7 @@ var AgentExecutor = class {
|
|
|
5015
5578
|
stateStore = null;
|
|
5016
5579
|
/** Held chain lock release functions, keyed by chain name */
|
|
5017
5580
|
chainLockReleases = /* @__PURE__ */ new Map();
|
|
5581
|
+
evmLastBroadcast = /* @__PURE__ */ new Map();
|
|
5018
5582
|
/** Backend client for resolving calldata_id references. */
|
|
5019
5583
|
backendClient = null;
|
|
5020
5584
|
constructor(vault, verbose = false, vaultId, vultisig) {
|
|
@@ -5092,11 +5656,13 @@ var AgentExecutor = class {
|
|
|
5092
5656
|
data
|
|
5093
5657
|
};
|
|
5094
5658
|
} catch (err) {
|
|
5659
|
+
const { code, message } = normalizeAgentError(err);
|
|
5095
5660
|
return {
|
|
5096
5661
|
action: action.type,
|
|
5097
5662
|
action_id: action.id,
|
|
5098
5663
|
success: false,
|
|
5099
|
-
error:
|
|
5664
|
+
error: message,
|
|
5665
|
+
code
|
|
5100
5666
|
};
|
|
5101
5667
|
}
|
|
5102
5668
|
}
|
|
@@ -5559,6 +6125,7 @@ var AgentExecutor = class {
|
|
|
5559
6125
|
keysignPayload: payload,
|
|
5560
6126
|
signature
|
|
5561
6127
|
});
|
|
6128
|
+
this.evmLastBroadcast.set(chain.toString(), Date.now());
|
|
5562
6129
|
try {
|
|
5563
6130
|
this.recordEvmNonceFromPayload(chain, payload, messageHashes.length);
|
|
5564
6131
|
} catch (nonceErr) {
|
|
@@ -5656,6 +6223,7 @@ var AgentExecutor = class {
|
|
|
5656
6223
|
keysignPayload,
|
|
5657
6224
|
signature
|
|
5658
6225
|
});
|
|
6226
|
+
this.evmLastBroadcast.set(chain.toString(), Date.now());
|
|
5659
6227
|
try {
|
|
5660
6228
|
this.recordEvmNonceFromPayload(chain, keysignPayload, messageHashes.length);
|
|
5661
6229
|
} catch (nonceErr) {
|
|
@@ -5814,6 +6382,16 @@ var AgentExecutor = class {
|
|
|
5814
6382
|
const rpcNonce = bs.value.nonce;
|
|
5815
6383
|
const nextNonce = this.stateStore.getNextEvmNonce(chain, rpcNonce);
|
|
5816
6384
|
if (nextNonce !== rpcNonce) {
|
|
6385
|
+
const lastBroadcast = this.evmLastBroadcast.get(chain.toString()) ?? 0;
|
|
6386
|
+
if (Date.now() - lastBroadcast < 15e3) {
|
|
6387
|
+
if (this.verbose)
|
|
6388
|
+
process.stderr.write(
|
|
6389
|
+
`[nonce] Keeping local nonce ${nextNonce} for ${chain} (broadcast ${Date.now() - lastBroadcast}ms ago)
|
|
6390
|
+
`
|
|
6391
|
+
);
|
|
6392
|
+
bs.value.nonce = nextNonce;
|
|
6393
|
+
return;
|
|
6394
|
+
}
|
|
5817
6395
|
const pendingNonce = await this.fetchEvmPendingNonce(chain);
|
|
5818
6396
|
if (pendingNonce !== null && pendingNonce === rpcNonce) {
|
|
5819
6397
|
if (this.verbose)
|
|
@@ -6449,7 +7027,12 @@ var PipeInterface = class {
|
|
|
6449
7027
|
const cmd = JSON.parse(nextLine);
|
|
6450
7028
|
await this.handleCommand(cmd);
|
|
6451
7029
|
} catch (err) {
|
|
6452
|
-
|
|
7030
|
+
const { message, code } = normalizeAgentError(err);
|
|
7031
|
+
this.emit({
|
|
7032
|
+
type: "error",
|
|
7033
|
+
message: `Invalid input: ${message}`,
|
|
7034
|
+
code: code === "UNKNOWN_ERROR" /* UNKNOWN_ERROR */ ? "INVALID_INPUT" /* INVALID_INPUT */ : code
|
|
7035
|
+
});
|
|
6453
7036
|
}
|
|
6454
7037
|
}
|
|
6455
7038
|
processing = false;
|
|
@@ -6490,8 +7073,16 @@ var PipeInterface = class {
|
|
|
6490
7073
|
onToolCall: (id, action, params) => {
|
|
6491
7074
|
this.emit({ type: "tool_call", id, action, params, status: "running" });
|
|
6492
7075
|
},
|
|
6493
|
-
onToolResult: (id, action, success2, data, error2) => {
|
|
6494
|
-
this.emit({
|
|
7076
|
+
onToolResult: (id, action, success2, data, error2, code) => {
|
|
7077
|
+
this.emit({
|
|
7078
|
+
type: "tool_result",
|
|
7079
|
+
id,
|
|
7080
|
+
action,
|
|
7081
|
+
success: success2,
|
|
7082
|
+
data,
|
|
7083
|
+
error: error2,
|
|
7084
|
+
...!success2 && code ? { code } : {}
|
|
7085
|
+
});
|
|
6495
7086
|
},
|
|
6496
7087
|
onAssistantMessage: (content) => {
|
|
6497
7088
|
this.emit({ type: "assistant", content });
|
|
@@ -6508,8 +7099,8 @@ var PipeInterface = class {
|
|
|
6508
7099
|
explorer_url: explorerUrl
|
|
6509
7100
|
});
|
|
6510
7101
|
},
|
|
6511
|
-
onError: (message) => {
|
|
6512
|
-
this.emit({ type: "error", message });
|
|
7102
|
+
onError: (message, code) => {
|
|
7103
|
+
this.emit({ type: "error", message, code });
|
|
6513
7104
|
},
|
|
6514
7105
|
onDone: () => {
|
|
6515
7106
|
this.emit({ type: "done" });
|
|
@@ -6517,13 +7108,17 @@ var PipeInterface = class {
|
|
|
6517
7108
|
requestPassword: async () => {
|
|
6518
7109
|
return new Promise((resolve) => {
|
|
6519
7110
|
this.pendingPasswordResolve = resolve;
|
|
6520
|
-
this.emit({ type: "error", message: "PASSWORD_REQUIRED" });
|
|
7111
|
+
this.emit({ type: "error", message: "PASSWORD_REQUIRED", code: "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */ });
|
|
6521
7112
|
});
|
|
6522
7113
|
},
|
|
6523
7114
|
requestConfirmation: async (message) => {
|
|
6524
7115
|
return new Promise((resolve) => {
|
|
6525
7116
|
this.pendingConfirmResolve = resolve;
|
|
6526
|
-
this.emit({
|
|
7117
|
+
this.emit({
|
|
7118
|
+
type: "error",
|
|
7119
|
+
message: `CONFIRMATION_REQUIRED: ${message}`,
|
|
7120
|
+
code: "CONFIRMATION_REQUIRED" /* CONFIRMATION_REQUIRED */
|
|
7121
|
+
});
|
|
6527
7122
|
});
|
|
6528
7123
|
}
|
|
6529
7124
|
};
|
|
@@ -6535,7 +7130,8 @@ var PipeInterface = class {
|
|
|
6535
7130
|
try {
|
|
6536
7131
|
await this.session.sendMessage(cmd.content, callbacks);
|
|
6537
7132
|
} catch (err) {
|
|
6538
|
-
|
|
7133
|
+
const { message, code } = normalizeAgentError(err);
|
|
7134
|
+
this.emit({ type: "error", message, code });
|
|
6539
7135
|
this.emit({ type: "done" });
|
|
6540
7136
|
}
|
|
6541
7137
|
break;
|
|
@@ -6555,7 +7151,11 @@ var PipeInterface = class {
|
|
|
6555
7151
|
break;
|
|
6556
7152
|
}
|
|
6557
7153
|
default:
|
|
6558
|
-
this.emit({
|
|
7154
|
+
this.emit({
|
|
7155
|
+
type: "error",
|
|
7156
|
+
message: `Unknown command type: ${cmd.type}`,
|
|
7157
|
+
code: "INVALID_INPUT" /* INVALID_INPUT */
|
|
7158
|
+
});
|
|
6559
7159
|
}
|
|
6560
7160
|
}
|
|
6561
7161
|
emit(event) {
|
|
@@ -6738,7 +7338,8 @@ var AgentSession = class {
|
|
|
6738
7338
|
action_id: result.action_id,
|
|
6739
7339
|
success: result.success,
|
|
6740
7340
|
data: result.data || {},
|
|
6741
|
-
error: result.error || ""
|
|
7341
|
+
error: result.error || "",
|
|
7342
|
+
...!result.success && result.code ? { code: result.code } : {}
|
|
6742
7343
|
};
|
|
6743
7344
|
}
|
|
6744
7345
|
let serverTxStoredFromStream = 0;
|
|
@@ -6771,22 +7372,16 @@ var AgentSession = class {
|
|
|
6771
7372
|
},
|
|
6772
7373
|
onMessage: (_msg) => {
|
|
6773
7374
|
},
|
|
6774
|
-
onError: (error2) => {
|
|
6775
|
-
ui.onError(error2);
|
|
7375
|
+
onError: (error2, code) => {
|
|
7376
|
+
ui.onError(error2, code);
|
|
6776
7377
|
}
|
|
6777
7378
|
},
|
|
6778
7379
|
this.abortController?.signal
|
|
6779
7380
|
);
|
|
6780
7381
|
const responseText = streamResult.message?.content || streamResult.fullText || "";
|
|
6781
|
-
const
|
|
6782
|
-
if (
|
|
6783
|
-
|
|
6784
|
-
if (cleanText) {
|
|
6785
|
-
ui.onAssistantMessage(cleanText);
|
|
6786
|
-
}
|
|
6787
|
-
streamResult.actions.push(...inlineActions);
|
|
6788
|
-
} else if (responseText) {
|
|
6789
|
-
ui.onAssistantMessage(responseText);
|
|
7382
|
+
const displayText = stripLeakedToolCallTags(responseText);
|
|
7383
|
+
if (displayText) {
|
|
7384
|
+
ui.onAssistantMessage(displayText);
|
|
6790
7385
|
}
|
|
6791
7386
|
const actions = streamResult.actions.filter((a) => a.type !== "sign_tx");
|
|
6792
7387
|
if (actions.length > 0) {
|
|
@@ -6860,7 +7455,8 @@ var AgentSession = class {
|
|
|
6860
7455
|
action: action.type,
|
|
6861
7456
|
action_id: action.id,
|
|
6862
7457
|
success: false,
|
|
6863
|
-
error: "Password not provided"
|
|
7458
|
+
error: "Password not provided",
|
|
7459
|
+
code: "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */
|
|
6864
7460
|
});
|
|
6865
7461
|
continue;
|
|
6866
7462
|
}
|
|
@@ -6869,7 +7465,7 @@ var AgentSession = class {
|
|
|
6869
7465
|
ui.onToolCall(action.id, action.type, action.params);
|
|
6870
7466
|
const result = await this.executor.executeAction(action);
|
|
6871
7467
|
results.push(result);
|
|
6872
|
-
ui.onToolResult(action.id, action.type, result.success, result.data, result.error);
|
|
7468
|
+
ui.onToolResult(action.id, action.type, result.success, result.data, result.error, result.code);
|
|
6873
7469
|
if (action.type === "sign_tx" && result.success && result.data) {
|
|
6874
7470
|
const txHash = result.data.tx_hash;
|
|
6875
7471
|
const chain = result.data.chain;
|
|
@@ -6899,34 +7495,12 @@ var AgentSession = class {
|
|
|
6899
7495
|
this.historyMessages = [];
|
|
6900
7496
|
}
|
|
6901
7497
|
};
|
|
6902
|
-
function
|
|
6903
|
-
|
|
6904
|
-
|
|
6905
|
-
|
|
6906
|
-
while ((match = invokeRegex.exec(text)) !== null) {
|
|
6907
|
-
const actionType = match[1];
|
|
6908
|
-
const body = match[2];
|
|
6909
|
-
const params = {};
|
|
6910
|
-
const paramRegex = /<parameter\s+name="([^"]+)">([\s\S]*?)<\/parameter>/g;
|
|
6911
|
-
let paramMatch;
|
|
6912
|
-
while ((paramMatch = paramRegex.exec(body)) !== null) {
|
|
6913
|
-
const key = paramMatch[1];
|
|
6914
|
-
const value = paramMatch[2];
|
|
6915
|
-
try {
|
|
6916
|
-
params[key] = JSON.parse(value);
|
|
6917
|
-
} catch {
|
|
6918
|
-
params[key] = value;
|
|
6919
|
-
}
|
|
6920
|
-
}
|
|
6921
|
-
actions.push({
|
|
6922
|
-
id: `inline_${actionType}_${Date.now()}`,
|
|
6923
|
-
type: actionType,
|
|
6924
|
-
title: actionType,
|
|
6925
|
-
params,
|
|
6926
|
-
auto_execute: true
|
|
6927
|
-
});
|
|
7498
|
+
function stripLeakedToolCallTags(text) {
|
|
7499
|
+
if (!text) return "";
|
|
7500
|
+
if (!/<invoke\s+name="[^"]*">/.test(text) && !text.includes("minimax:tool_call")) {
|
|
7501
|
+
return text;
|
|
6928
7502
|
}
|
|
6929
|
-
return
|
|
7503
|
+
return text.replace(/<invoke\s+name="[^"]*">[\s\S]*?<\/invoke>/g, "").replace(/<\/?minimax:tool_call>/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
6930
7504
|
}
|
|
6931
7505
|
function getTokenCachePath() {
|
|
6932
7506
|
const dir = process.env.VULTISIG_CONFIG_DIR ?? join2(homedir2(), ".vultisig");
|
|
@@ -7102,7 +7676,7 @@ var ChatTUI = class {
|
|
|
7102
7676
|
console.log(` ${chalk8.yellow("\u26A1")} ${chalk8.yellow(action)} ${chalk8.gray("...")}`);
|
|
7103
7677
|
}
|
|
7104
7678
|
},
|
|
7105
|
-
onToolResult: (_id, action, success2, data, error2) => {
|
|
7679
|
+
onToolResult: (_id, action, success2, data, error2, code) => {
|
|
7106
7680
|
if (success2) {
|
|
7107
7681
|
if (this.verbose) {
|
|
7108
7682
|
const summary = data ? summarizeData(data) : "";
|
|
@@ -7111,7 +7685,8 @@ var ChatTUI = class {
|
|
|
7111
7685
|
console.log(` ${chalk8.green("\u2713")} ${chalk8.green(action)}`);
|
|
7112
7686
|
}
|
|
7113
7687
|
} else {
|
|
7114
|
-
|
|
7688
|
+
const suffix = code && this.verbose ? chalk8.gray(` (${code})`) : "";
|
|
7689
|
+
console.log(` ${chalk8.red("\u2717")} ${chalk8.red(action)}: ${chalk8.red(error2 || "failed")}${suffix}`);
|
|
7115
7690
|
}
|
|
7116
7691
|
},
|
|
7117
7692
|
onAssistantMessage: (content) => {
|
|
@@ -7139,12 +7714,13 @@ var ChatTUI = class {
|
|
|
7139
7714
|
console.log(` ${chalk8.blue.underline(explorerUrl)}`);
|
|
7140
7715
|
}
|
|
7141
7716
|
},
|
|
7142
|
-
onError: (message) => {
|
|
7717
|
+
onError: (message, code) => {
|
|
7143
7718
|
if (this.isStreaming) {
|
|
7144
7719
|
process.stdout.write("\n");
|
|
7145
7720
|
this.isStreaming = false;
|
|
7146
7721
|
}
|
|
7147
|
-
|
|
7722
|
+
const suffix = this.verbose ? chalk8.gray(` (${code})`) : "";
|
|
7723
|
+
console.log(` ${chalk8.red("Error")}: ${message}${suffix}`);
|
|
7148
7724
|
},
|
|
7149
7725
|
onDone: () => {
|
|
7150
7726
|
if (this.isStreaming) {
|
|
@@ -7353,7 +7929,8 @@ async function executeAgent(ctx2, options) {
|
|
|
7353
7929
|
const addresses = session.getVaultAddresses();
|
|
7354
7930
|
await pipe.start(vault.name, addresses);
|
|
7355
7931
|
} catch (err) {
|
|
7356
|
-
|
|
7932
|
+
const { code, message } = normalizeAgentError(err);
|
|
7933
|
+
process.stdout.write(JSON.stringify({ type: "error", message, code }) + "\n");
|
|
7357
7934
|
process.exit(1);
|
|
7358
7935
|
}
|
|
7359
7936
|
} else {
|
|
@@ -7363,7 +7940,8 @@ async function executeAgent(ctx2, options) {
|
|
|
7363
7940
|
await session.initialize(callbacks);
|
|
7364
7941
|
await tui.start();
|
|
7365
7942
|
} catch (err) {
|
|
7366
|
-
|
|
7943
|
+
const { message } = normalizeAgentError(err);
|
|
7944
|
+
console.error(`Agent error: ${message}`);
|
|
7367
7945
|
process.exit(1);
|
|
7368
7946
|
}
|
|
7369
7947
|
}
|
|
@@ -7390,15 +7968,13 @@ async function executeAgentAsk(ctx2, message, options) {
|
|
|
7390
7968
|
const callbacks = ask.getCallbacks();
|
|
7391
7969
|
await session.initialize(callbacks);
|
|
7392
7970
|
const result = await ask.ask(message);
|
|
7393
|
-
if (options.json) {
|
|
7394
|
-
|
|
7395
|
-
|
|
7396
|
-
|
|
7397
|
-
|
|
7398
|
-
|
|
7399
|
-
|
|
7400
|
-
}) + "\n"
|
|
7401
|
-
);
|
|
7971
|
+
if (options.json || isJsonOutput()) {
|
|
7972
|
+
outputJson({
|
|
7973
|
+
session_id: result.sessionId,
|
|
7974
|
+
response: result.response,
|
|
7975
|
+
tool_calls: result.toolCalls,
|
|
7976
|
+
transactions: result.transactions
|
|
7977
|
+
});
|
|
7402
7978
|
} else {
|
|
7403
7979
|
process.stdout.write(`session:${result.sessionId}
|
|
7404
7980
|
`);
|
|
@@ -7418,10 +7994,11 @@ tx:${tx.chain}:${tx.hash}
|
|
|
7418
7994
|
}
|
|
7419
7995
|
}
|
|
7420
7996
|
} catch (err) {
|
|
7997
|
+
const { code, message: message2 } = normalizeAgentError(err);
|
|
7421
7998
|
if (options.json) {
|
|
7422
|
-
process.stdout.write(JSON.stringify({ error:
|
|
7999
|
+
process.stdout.write(JSON.stringify({ error: message2, code }) + "\n");
|
|
7423
8000
|
} else {
|
|
7424
|
-
process.stderr.write(`Error: ${
|
|
8001
|
+
process.stderr.write(`Error: ${message2} [${code}]
|
|
7425
8002
|
`);
|
|
7426
8003
|
}
|
|
7427
8004
|
process.exit(1);
|
|
@@ -7463,46 +8040,294 @@ async function executeAgentSessionsList(ctx2, options) {
|
|
|
7463
8040
|
printResult("No sessions found.");
|
|
7464
8041
|
return;
|
|
7465
8042
|
}
|
|
7466
|
-
const table = new Table({
|
|
7467
|
-
head: [chalk9.cyan("ID"), chalk9.cyan("Title"), chalk9.cyan("Created"), chalk9.cyan("Updated")]
|
|
7468
|
-
});
|
|
7469
|
-
for (const conv of allConversations) {
|
|
7470
|
-
table.push([
|
|
7471
|
-
conv.id,
|
|
7472
|
-
conv.title || chalk9.gray("(untitled)"),
|
|
7473
|
-
formatDate(conv.created_at),
|
|
7474
|
-
formatDate(conv.updated_at)
|
|
7475
|
-
]);
|
|
8043
|
+
const table = new Table({
|
|
8044
|
+
head: [chalk9.cyan("ID"), chalk9.cyan("Title"), chalk9.cyan("Created"), chalk9.cyan("Updated")]
|
|
8045
|
+
});
|
|
8046
|
+
for (const conv of allConversations) {
|
|
8047
|
+
table.push([
|
|
8048
|
+
conv.id,
|
|
8049
|
+
conv.title || chalk9.gray("(untitled)"),
|
|
8050
|
+
formatDate(conv.created_at),
|
|
8051
|
+
formatDate(conv.updated_at)
|
|
8052
|
+
]);
|
|
8053
|
+
}
|
|
8054
|
+
printResult(table.toString());
|
|
8055
|
+
printResult(chalk9.gray(`
|
|
8056
|
+
${totalCount} session(s) total`));
|
|
8057
|
+
}
|
|
8058
|
+
async function executeAgentSessionsDelete(ctx2, sessionId, options) {
|
|
8059
|
+
const vault = await ctx2.ensureActiveVault();
|
|
8060
|
+
const backendUrl = options.backendUrl || process.env.VULTISIG_AGENT_URL || "https://abe.vultisig.com";
|
|
8061
|
+
const client = await createAuthenticatedClient(backendUrl, vault, options.password);
|
|
8062
|
+
const publicKey = vault.publicKeys.ecdsa;
|
|
8063
|
+
await client.deleteConversation(sessionId, publicKey);
|
|
8064
|
+
if (isJsonOutput()) {
|
|
8065
|
+
outputJson({ deleted: sessionId });
|
|
8066
|
+
return;
|
|
8067
|
+
}
|
|
8068
|
+
printResult(chalk9.green(`Session ${sessionId} deleted.`));
|
|
8069
|
+
}
|
|
8070
|
+
async function createAuthenticatedClient(backendUrl, vault, password) {
|
|
8071
|
+
const client = new AgentClient(backendUrl);
|
|
8072
|
+
const auth = await authenticateVault(client, vault, password);
|
|
8073
|
+
client.setAuthToken(auth.token);
|
|
8074
|
+
return client;
|
|
8075
|
+
}
|
|
8076
|
+
function formatDate(iso) {
|
|
8077
|
+
try {
|
|
8078
|
+
const d = new Date(iso);
|
|
8079
|
+
return d.toLocaleDateString() + " " + d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
|
|
8080
|
+
} catch {
|
|
8081
|
+
return iso;
|
|
8082
|
+
}
|
|
8083
|
+
}
|
|
8084
|
+
|
|
8085
|
+
// src/lib/version.ts
|
|
8086
|
+
import chalk10 from "chalk";
|
|
8087
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
|
|
8088
|
+
import { homedir as homedir3 } from "os";
|
|
8089
|
+
import { join as join3 } from "path";
|
|
8090
|
+
var cachedVersion = null;
|
|
8091
|
+
function getVersion() {
|
|
8092
|
+
if (cachedVersion) return cachedVersion;
|
|
8093
|
+
if (true) {
|
|
8094
|
+
cachedVersion = "0.16.0";
|
|
8095
|
+
return cachedVersion;
|
|
8096
|
+
}
|
|
8097
|
+
try {
|
|
8098
|
+
const packagePath = new URL("../../package.json", import.meta.url);
|
|
8099
|
+
const pkg = JSON.parse(readFileSync3(packagePath, "utf-8"));
|
|
8100
|
+
cachedVersion = pkg.version;
|
|
8101
|
+
return cachedVersion;
|
|
8102
|
+
} catch {
|
|
8103
|
+
cachedVersion = "0.0.0-unknown";
|
|
8104
|
+
return cachedVersion;
|
|
8105
|
+
}
|
|
8106
|
+
}
|
|
8107
|
+
var CACHE_DIR = join3(homedir3(), ".vultisig", "cache");
|
|
8108
|
+
var VERSION_CACHE_FILE = join3(CACHE_DIR, "version-check.json");
|
|
8109
|
+
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8110
|
+
function readVersionCache() {
|
|
8111
|
+
try {
|
|
8112
|
+
if (!existsSync2(VERSION_CACHE_FILE)) return null;
|
|
8113
|
+
const data = readFileSync3(VERSION_CACHE_FILE, "utf-8");
|
|
8114
|
+
return JSON.parse(data);
|
|
8115
|
+
} catch {
|
|
8116
|
+
return null;
|
|
8117
|
+
}
|
|
8118
|
+
}
|
|
8119
|
+
function writeVersionCache(cache) {
|
|
8120
|
+
try {
|
|
8121
|
+
if (!existsSync2(CACHE_DIR)) {
|
|
8122
|
+
mkdirSync3(CACHE_DIR, { recursive: true });
|
|
8123
|
+
}
|
|
8124
|
+
writeFileSync3(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
8125
|
+
} catch {
|
|
8126
|
+
}
|
|
8127
|
+
}
|
|
8128
|
+
async function fetchLatestVersion() {
|
|
8129
|
+
try {
|
|
8130
|
+
const controller = new AbortController();
|
|
8131
|
+
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
8132
|
+
const response = await fetch("https://registry.npmjs.org/@vultisig/cli/latest", {
|
|
8133
|
+
signal: controller.signal,
|
|
8134
|
+
headers: {
|
|
8135
|
+
Accept: "application/json"
|
|
8136
|
+
}
|
|
8137
|
+
});
|
|
8138
|
+
clearTimeout(timeout);
|
|
8139
|
+
if (!response.ok) return null;
|
|
8140
|
+
const data = await response.json();
|
|
8141
|
+
return data.version ?? null;
|
|
8142
|
+
} catch {
|
|
8143
|
+
return null;
|
|
8144
|
+
}
|
|
8145
|
+
}
|
|
8146
|
+
function isNewerVersion(v1, v2) {
|
|
8147
|
+
const parse = (v) => {
|
|
8148
|
+
const clean2 = v.replace(/^v/, "").replace(/-.*$/, "");
|
|
8149
|
+
return clean2.split(".").map((n) => parseInt(n, 10) || 0);
|
|
8150
|
+
};
|
|
8151
|
+
const p1 = parse(v1);
|
|
8152
|
+
const p2 = parse(v2);
|
|
8153
|
+
for (let i = 0; i < 3; i++) {
|
|
8154
|
+
const n1 = p1[i] ?? 0;
|
|
8155
|
+
const n2 = p2[i] ?? 0;
|
|
8156
|
+
if (n2 > n1) return true;
|
|
8157
|
+
if (n2 < n1) return false;
|
|
8158
|
+
}
|
|
8159
|
+
if (v1.includes("-") && !v2.includes("-")) return true;
|
|
8160
|
+
return false;
|
|
8161
|
+
}
|
|
8162
|
+
async function checkForUpdates() {
|
|
8163
|
+
if (process.env.VULTISIG_NO_UPDATE_CHECK === "1") {
|
|
8164
|
+
return null;
|
|
8165
|
+
}
|
|
8166
|
+
const currentVersion = getVersion();
|
|
8167
|
+
const cache = readVersionCache();
|
|
8168
|
+
if (cache && Date.now() - cache.lastCheck < CACHE_TTL_MS) {
|
|
8169
|
+
return {
|
|
8170
|
+
currentVersion,
|
|
8171
|
+
latestVersion: cache.latestVersion,
|
|
8172
|
+
updateAvailable: cache.latestVersion ? isNewerVersion(currentVersion, cache.latestVersion) : false
|
|
8173
|
+
};
|
|
8174
|
+
}
|
|
8175
|
+
const latestVersion = await fetchLatestVersion();
|
|
8176
|
+
writeVersionCache({
|
|
8177
|
+
lastCheck: Date.now(),
|
|
8178
|
+
latestVersion
|
|
8179
|
+
});
|
|
8180
|
+
return {
|
|
8181
|
+
currentVersion,
|
|
8182
|
+
latestVersion,
|
|
8183
|
+
updateAvailable: latestVersion ? isNewerVersion(currentVersion, latestVersion) : false
|
|
8184
|
+
};
|
|
8185
|
+
}
|
|
8186
|
+
function formatVersionShort() {
|
|
8187
|
+
return `vultisig/${getVersion()}`;
|
|
8188
|
+
}
|
|
8189
|
+
function formatVersionDetailed() {
|
|
8190
|
+
const lines = [];
|
|
8191
|
+
lines.push(chalk10.bold(`Vultisig CLI v${getVersion()}`));
|
|
8192
|
+
lines.push("");
|
|
8193
|
+
lines.push(` Node.js: ${process.version}`);
|
|
8194
|
+
lines.push(` Platform: ${process.platform}-${process.arch}`);
|
|
8195
|
+
lines.push(` Config: ~/.vultisig/`);
|
|
8196
|
+
return lines.join("\n");
|
|
8197
|
+
}
|
|
8198
|
+
function detectInstallMethod() {
|
|
8199
|
+
const execPath = process.execPath;
|
|
8200
|
+
if (execPath.includes("homebrew") || execPath.includes("Cellar")) {
|
|
8201
|
+
return "homebrew";
|
|
8202
|
+
}
|
|
8203
|
+
if (process.env.npm_execpath?.includes("yarn")) {
|
|
8204
|
+
return "yarn";
|
|
8205
|
+
}
|
|
8206
|
+
if (process.env.npm_config_user_agent?.includes("npm")) {
|
|
8207
|
+
return "npm";
|
|
8208
|
+
}
|
|
8209
|
+
if (execPath.includes("node_modules")) {
|
|
8210
|
+
return "npm";
|
|
8211
|
+
}
|
|
8212
|
+
return "unknown";
|
|
8213
|
+
}
|
|
8214
|
+
function getUpdateCommand() {
|
|
8215
|
+
const method = detectInstallMethod();
|
|
8216
|
+
switch (method) {
|
|
8217
|
+
case "npm":
|
|
8218
|
+
return "npm update -g @vultisig/cli";
|
|
8219
|
+
case "yarn":
|
|
8220
|
+
return "yarn global upgrade @vultisig/cli";
|
|
8221
|
+
case "homebrew":
|
|
8222
|
+
return "brew upgrade vultisig";
|
|
8223
|
+
default:
|
|
8224
|
+
return "npm update -g @vultisig/cli";
|
|
7476
8225
|
}
|
|
7477
|
-
printResult(table.toString());
|
|
7478
|
-
printResult(chalk9.gray(`
|
|
7479
|
-
${totalCount} session(s) total`));
|
|
7480
8226
|
}
|
|
7481
|
-
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
8227
|
+
|
|
8228
|
+
// src/commands/schema.ts
|
|
8229
|
+
var COMMAND_EXAMPLES = {
|
|
8230
|
+
balance: {
|
|
8231
|
+
examples: [
|
|
8232
|
+
"vultisig balance",
|
|
8233
|
+
"vultisig balance Ethereum --tokens",
|
|
8234
|
+
"vultisig balance --output json --fields amount,symbol"
|
|
8235
|
+
]
|
|
8236
|
+
},
|
|
8237
|
+
addresses: {
|
|
8238
|
+
examples: ["vultisig addresses", "vultisig addresses --output json"]
|
|
8239
|
+
},
|
|
8240
|
+
send: {
|
|
8241
|
+
examples: [
|
|
8242
|
+
"vultisig send Ethereum 0x... 0.1",
|
|
8243
|
+
"vultisig send Bitcoin bc1q... --max --yes",
|
|
8244
|
+
"vultisig send Ethereum 0x... 0.5 --dry-run --output json"
|
|
8245
|
+
]
|
|
8246
|
+
},
|
|
8247
|
+
execute: {
|
|
8248
|
+
examples: [`vultisig execute THORChain <contract> '{"swap":{}}'`]
|
|
8249
|
+
},
|
|
8250
|
+
"swap-quote": {
|
|
8251
|
+
examples: ["vultisig swap-quote Ethereum Bitcoin 0.1 --output json"]
|
|
8252
|
+
},
|
|
8253
|
+
vaults: {
|
|
8254
|
+
examples: ["vultisig vaults", "vultisig vaults --output json"]
|
|
8255
|
+
},
|
|
8256
|
+
chains: {
|
|
8257
|
+
examples: ["vultisig chains", "vultisig chains --add Solana"]
|
|
8258
|
+
},
|
|
8259
|
+
import: {
|
|
8260
|
+
examples: ["vultisig import ~/vault.vult", "vultisig import ~/vault.vult --password secret"]
|
|
8261
|
+
},
|
|
8262
|
+
export: {
|
|
8263
|
+
examples: ["vultisig export ~/backup.vult"]
|
|
8264
|
+
},
|
|
8265
|
+
"create.fast": {
|
|
8266
|
+
examples: ["vultisig create fast --name mywallet --password secret --email me@example.com"]
|
|
8267
|
+
},
|
|
8268
|
+
"agent.ask": {
|
|
8269
|
+
examples: [
|
|
8270
|
+
'vultisig agent ask "What is my ETH balance?" --output json',
|
|
8271
|
+
'vultisig agent ask "Send 0.1 ETH to 0x..." --session abc123'
|
|
8272
|
+
]
|
|
7490
8273
|
}
|
|
7491
|
-
|
|
8274
|
+
};
|
|
8275
|
+
var GLOBAL_ENUM_VALUES = {
|
|
8276
|
+
"--output": ["json", "table"]
|
|
8277
|
+
};
|
|
8278
|
+
function mapOption(o, enumValues) {
|
|
8279
|
+
return {
|
|
8280
|
+
flags: o.flags,
|
|
8281
|
+
description: o.description,
|
|
8282
|
+
required: !!o.mandatory,
|
|
8283
|
+
defaultValue: o.defaultValue,
|
|
8284
|
+
...enumValues?.[o.long] ? { enumValues: enumValues[o.long] } : {}
|
|
8285
|
+
};
|
|
7492
8286
|
}
|
|
7493
|
-
|
|
7494
|
-
const
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
8287
|
+
function mapArguments(cmd) {
|
|
8288
|
+
const args = cmd.registeredArguments;
|
|
8289
|
+
if (!args?.length) return void 0;
|
|
8290
|
+
return args.map((a) => ({
|
|
8291
|
+
name: a._name ?? a.name?.(),
|
|
8292
|
+
required: a.required,
|
|
8293
|
+
description: a.description
|
|
8294
|
+
}));
|
|
7498
8295
|
}
|
|
7499
|
-
function
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
8296
|
+
function executeSchema(prog) {
|
|
8297
|
+
const schema = {
|
|
8298
|
+
name: prog.name(),
|
|
8299
|
+
version: getVersion(),
|
|
8300
|
+
exitCodes: Object.fromEntries(Object.entries(EXIT_CODE_DESCRIPTIONS).map(([k, v]) => [String(k), v])),
|
|
8301
|
+
globalOptions: prog.options.filter((o) => !o.hidden).map((o) => mapOption(o, GLOBAL_ENUM_VALUES)),
|
|
8302
|
+
commands: prog.commands.filter((c) => !c._hidden).map((c) => {
|
|
8303
|
+
const meta = COMMAND_EXAMPLES[c.name()];
|
|
8304
|
+
const args = mapArguments(c);
|
|
8305
|
+
return {
|
|
8306
|
+
name: c.name(),
|
|
8307
|
+
description: c.description(),
|
|
8308
|
+
...args ? { arguments: args } : {},
|
|
8309
|
+
options: c.options.filter((o) => !o.hidden && o.long !== "--help").map((o) => mapOption(o, meta?.enumValues)),
|
|
8310
|
+
...meta?.examples ? { examples: meta.examples } : {},
|
|
8311
|
+
subcommands: c.commands.length ? c.commands.map((sub) => {
|
|
8312
|
+
const subMeta = COMMAND_EXAMPLES[`${c.name()}.${sub.name()}`];
|
|
8313
|
+
const subArgs = mapArguments(sub);
|
|
8314
|
+
return {
|
|
8315
|
+
name: sub.name(),
|
|
8316
|
+
description: sub.description(),
|
|
8317
|
+
...subArgs ? { arguments: subArgs } : {},
|
|
8318
|
+
options: sub.options.filter((o) => !o.hidden && o.long !== "--help").map((o) => ({
|
|
8319
|
+
flags: o.flags,
|
|
8320
|
+
description: o.description,
|
|
8321
|
+
required: !!o.mandatory
|
|
8322
|
+
})),
|
|
8323
|
+
...subMeta?.examples ? { examples: subMeta.examples } : {}
|
|
8324
|
+
};
|
|
8325
|
+
}) : void 0
|
|
8326
|
+
};
|
|
8327
|
+
})
|
|
8328
|
+
};
|
|
8329
|
+
process.stdout.write(`${JSON.stringify(schema, null, 2)}
|
|
8330
|
+
`);
|
|
7506
8331
|
}
|
|
7507
8332
|
|
|
7508
8333
|
// src/core/server-endpoints.ts
|
|
@@ -7708,7 +8533,7 @@ function findChainByName(name) {
|
|
|
7708
8533
|
}
|
|
7709
8534
|
|
|
7710
8535
|
// src/interactive/event-buffer.ts
|
|
7711
|
-
import
|
|
8536
|
+
import chalk11 from "chalk";
|
|
7712
8537
|
var EventBuffer = class {
|
|
7713
8538
|
eventBuffer = [];
|
|
7714
8539
|
isCommandRunning = false;
|
|
@@ -7748,17 +8573,17 @@ var EventBuffer = class {
|
|
|
7748
8573
|
displayEvent(message, type) {
|
|
7749
8574
|
switch (type) {
|
|
7750
8575
|
case "success":
|
|
7751
|
-
console.log(
|
|
8576
|
+
console.log(chalk11.green(message));
|
|
7752
8577
|
break;
|
|
7753
8578
|
case "warning":
|
|
7754
|
-
console.log(
|
|
8579
|
+
console.log(chalk11.yellow(message));
|
|
7755
8580
|
break;
|
|
7756
8581
|
case "error":
|
|
7757
|
-
console.error(
|
|
8582
|
+
console.error(chalk11.red(message));
|
|
7758
8583
|
break;
|
|
7759
8584
|
case "info":
|
|
7760
8585
|
default:
|
|
7761
|
-
console.log(
|
|
8586
|
+
console.log(chalk11.blue(message));
|
|
7762
8587
|
break;
|
|
7763
8588
|
}
|
|
7764
8589
|
}
|
|
@@ -7769,13 +8594,13 @@ var EventBuffer = class {
|
|
|
7769
8594
|
if (this.eventBuffer.length === 0) {
|
|
7770
8595
|
return;
|
|
7771
8596
|
}
|
|
7772
|
-
console.log(
|
|
8597
|
+
console.log(chalk11.gray("\n--- Background Events ---"));
|
|
7773
8598
|
this.eventBuffer.forEach((event) => {
|
|
7774
8599
|
const timeStr = event.timestamp.toLocaleTimeString();
|
|
7775
8600
|
const message = `[${timeStr}] ${event.message}`;
|
|
7776
8601
|
this.displayEvent(message, event.type);
|
|
7777
8602
|
});
|
|
7778
|
-
console.log(
|
|
8603
|
+
console.log(chalk11.gray("--- End Events ---\n"));
|
|
7779
8604
|
}
|
|
7780
8605
|
/**
|
|
7781
8606
|
* Setup all vault event listeners
|
|
@@ -7885,12 +8710,12 @@ var EventBuffer = class {
|
|
|
7885
8710
|
|
|
7886
8711
|
// src/interactive/session.ts
|
|
7887
8712
|
import { fiatCurrencies as fiatCurrencies4 } from "@vultisig/sdk";
|
|
7888
|
-
import
|
|
8713
|
+
import chalk13 from "chalk";
|
|
7889
8714
|
import ora3 from "ora";
|
|
7890
8715
|
import * as readline3 from "readline";
|
|
7891
8716
|
|
|
7892
8717
|
// src/interactive/shell-commands.ts
|
|
7893
|
-
import
|
|
8718
|
+
import chalk12 from "chalk";
|
|
7894
8719
|
import Table2 from "cli-table3";
|
|
7895
8720
|
import inquirer6 from "inquirer";
|
|
7896
8721
|
import ora2 from "ora";
|
|
@@ -7907,25 +8732,25 @@ function formatTimeRemaining(ms) {
|
|
|
7907
8732
|
async function executeLock(ctx2) {
|
|
7908
8733
|
const vault = ctx2.getActiveVault();
|
|
7909
8734
|
if (!vault) {
|
|
7910
|
-
console.log(
|
|
7911
|
-
console.log(
|
|
8735
|
+
console.log(chalk12.red("No active vault."));
|
|
8736
|
+
console.log(chalk12.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
7912
8737
|
return;
|
|
7913
8738
|
}
|
|
7914
8739
|
ctx2.lockVault(vault.id);
|
|
7915
|
-
console.log(
|
|
7916
|
-
console.log(
|
|
8740
|
+
console.log(chalk12.green("\n+ Vault locked"));
|
|
8741
|
+
console.log(chalk12.gray("Password cache cleared. You will need to enter the password again."));
|
|
7917
8742
|
}
|
|
7918
8743
|
async function executeUnlock(ctx2) {
|
|
7919
8744
|
const vault = ctx2.getActiveVault();
|
|
7920
8745
|
if (!vault) {
|
|
7921
|
-
console.log(
|
|
7922
|
-
console.log(
|
|
8746
|
+
console.log(chalk12.red("No active vault."));
|
|
8747
|
+
console.log(chalk12.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
7923
8748
|
return;
|
|
7924
8749
|
}
|
|
7925
8750
|
if (ctx2.isVaultUnlocked(vault.id)) {
|
|
7926
8751
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
7927
|
-
console.log(
|
|
7928
|
-
console.log(
|
|
8752
|
+
console.log(chalk12.yellow("\nVault is already unlocked."));
|
|
8753
|
+
console.log(chalk12.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
|
|
7929
8754
|
return;
|
|
7930
8755
|
}
|
|
7931
8756
|
const { password } = await inquirer6.prompt([
|
|
@@ -7942,19 +8767,19 @@ async function executeUnlock(ctx2) {
|
|
|
7942
8767
|
ctx2.cachePassword(vault.id, password);
|
|
7943
8768
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
7944
8769
|
spinner.succeed("Vault unlocked");
|
|
7945
|
-
console.log(
|
|
8770
|
+
console.log(chalk12.green(`
|
|
7946
8771
|
+ Vault unlocked for ${formatTimeRemaining(timeRemaining)}`));
|
|
7947
8772
|
} catch (err) {
|
|
7948
8773
|
spinner.fail("Failed to unlock vault");
|
|
7949
|
-
console.error(
|
|
8774
|
+
console.error(chalk12.red(`
|
|
7950
8775
|
x ${err.message}`));
|
|
7951
8776
|
}
|
|
7952
8777
|
}
|
|
7953
8778
|
async function executeStatus(ctx2) {
|
|
7954
8779
|
const vault = ctx2.getActiveVault();
|
|
7955
8780
|
if (!vault) {
|
|
7956
|
-
console.log(
|
|
7957
|
-
console.log(
|
|
8781
|
+
console.log(chalk12.red("No active vault."));
|
|
8782
|
+
console.log(chalk12.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
7958
8783
|
return;
|
|
7959
8784
|
}
|
|
7960
8785
|
const isUnlocked = ctx2.isVaultUnlocked(vault.id);
|
|
@@ -7985,30 +8810,30 @@ async function executeStatus(ctx2) {
|
|
|
7985
8810
|
displayStatus(status);
|
|
7986
8811
|
}
|
|
7987
8812
|
function displayStatus(status) {
|
|
7988
|
-
console.log(
|
|
7989
|
-
console.log(
|
|
7990
|
-
console.log(
|
|
7991
|
-
console.log(
|
|
7992
|
-
console.log(` Name: ${
|
|
8813
|
+
console.log(chalk12.cyan("\n+----------------------------------------+"));
|
|
8814
|
+
console.log(chalk12.cyan("| Vault Status |"));
|
|
8815
|
+
console.log(chalk12.cyan("+----------------------------------------+\n"));
|
|
8816
|
+
console.log(chalk12.bold("Vault:"));
|
|
8817
|
+
console.log(` Name: ${chalk12.green(status.name)}`);
|
|
7993
8818
|
console.log(` ID: ${status.id}`);
|
|
7994
|
-
console.log(` Type: ${
|
|
7995
|
-
console.log(
|
|
8819
|
+
console.log(` Type: ${chalk12.yellow(status.type)}`);
|
|
8820
|
+
console.log(chalk12.bold("\nSecurity:"));
|
|
7996
8821
|
if (status.isUnlocked) {
|
|
7997
|
-
console.log(` Status: ${
|
|
8822
|
+
console.log(` Status: ${chalk12.green("Unlocked")} ${chalk12.green("\u{1F513}")}`);
|
|
7998
8823
|
console.log(` Expires: ${status.timeRemainingFormatted}`);
|
|
7999
8824
|
} else {
|
|
8000
|
-
console.log(` Status: ${
|
|
8825
|
+
console.log(` Status: ${chalk12.yellow("Locked")} ${chalk12.yellow("\u{1F512}")}`);
|
|
8001
8826
|
}
|
|
8002
|
-
console.log(` Encrypted: ${status.isEncrypted ?
|
|
8003
|
-
console.log(` Backed Up: ${status.isBackedUp ?
|
|
8004
|
-
console.log(
|
|
8827
|
+
console.log(` Encrypted: ${status.isEncrypted ? chalk12.green("Yes") : chalk12.gray("No")}`);
|
|
8828
|
+
console.log(` Backed Up: ${status.isBackedUp ? chalk12.green("Yes") : chalk12.yellow("No")}`);
|
|
8829
|
+
console.log(chalk12.bold("\nMPC Configuration:"));
|
|
8005
8830
|
console.log(` Library: ${status.libType}`);
|
|
8006
|
-
console.log(` Threshold: ${
|
|
8007
|
-
console.log(
|
|
8831
|
+
console.log(` Threshold: ${chalk12.cyan(status.threshold)} of ${chalk12.cyan(status.totalSigners)}`);
|
|
8832
|
+
console.log(chalk12.bold("\nSigning Modes:"));
|
|
8008
8833
|
status.availableSigningModes.forEach((mode) => {
|
|
8009
8834
|
console.log(` - ${mode}`);
|
|
8010
8835
|
});
|
|
8011
|
-
console.log(
|
|
8836
|
+
console.log(chalk12.bold("\nDetails:"));
|
|
8012
8837
|
console.log(` Chains: ${status.chains}`);
|
|
8013
8838
|
console.log(` Currency: ${status.currency.toUpperCase()}`);
|
|
8014
8839
|
console.log(` Created: ${new Date(status.createdAt).toLocaleString()}`);
|
|
@@ -8017,7 +8842,7 @@ function displayStatus(status) {
|
|
|
8017
8842
|
}
|
|
8018
8843
|
function showHelp() {
|
|
8019
8844
|
const table = new Table2({
|
|
8020
|
-
head: [
|
|
8845
|
+
head: [chalk12.bold("Available Commands")],
|
|
8021
8846
|
colWidths: [50],
|
|
8022
8847
|
chars: {
|
|
8023
8848
|
mid: "",
|
|
@@ -8031,7 +8856,7 @@ function showHelp() {
|
|
|
8031
8856
|
}
|
|
8032
8857
|
});
|
|
8033
8858
|
table.push(
|
|
8034
|
-
[
|
|
8859
|
+
[chalk12.bold("Vault Management:")],
|
|
8035
8860
|
[" vaults - List all vaults"],
|
|
8036
8861
|
[" vault <name> - Switch to vault"],
|
|
8037
8862
|
[" import <file> - Import vault from file"],
|
|
@@ -8040,7 +8865,7 @@ function showHelp() {
|
|
|
8040
8865
|
[" info - Show vault details"],
|
|
8041
8866
|
[" export [path] - Export vault to file"],
|
|
8042
8867
|
[""],
|
|
8043
|
-
[
|
|
8868
|
+
[chalk12.bold("Wallet Operations:")],
|
|
8044
8869
|
[" balance [chain] - Show balances"],
|
|
8045
8870
|
[" send <chain> <to> <amount> - Send transaction"],
|
|
8046
8871
|
[" tx-status <chain> <txHash> - Check transaction status"],
|
|
@@ -8049,22 +8874,22 @@ function showHelp() {
|
|
|
8049
8874
|
[" chains [--add/--remove/--add-all] - Manage chains"],
|
|
8050
8875
|
[" tokens <chain> - Manage tokens"],
|
|
8051
8876
|
[""],
|
|
8052
|
-
[
|
|
8877
|
+
[chalk12.bold("Swap Operations:")],
|
|
8053
8878
|
[" swap-chains - List swap-enabled chains"],
|
|
8054
8879
|
[" swap-quote <from> <to> <amount> - Get quote"],
|
|
8055
8880
|
[" swap <from> <to> <amount> - Execute swap"],
|
|
8056
8881
|
[""],
|
|
8057
|
-
[
|
|
8882
|
+
[chalk12.bold("Session Commands (shell only):")],
|
|
8058
8883
|
[" lock - Lock vault"],
|
|
8059
8884
|
[" unlock - Unlock vault"],
|
|
8060
8885
|
[" status - Show vault status"],
|
|
8061
8886
|
[""],
|
|
8062
|
-
[
|
|
8887
|
+
[chalk12.bold("Settings:")],
|
|
8063
8888
|
[" currency [code] - View/set currency"],
|
|
8064
8889
|
[" server - Check server status"],
|
|
8065
8890
|
[" address-book - Manage saved addresses"],
|
|
8066
8891
|
[""],
|
|
8067
|
-
[
|
|
8892
|
+
[chalk12.bold("Help & Navigation:")],
|
|
8068
8893
|
[" help, ? - Show this help"],
|
|
8069
8894
|
[" .clear - Clear screen"],
|
|
8070
8895
|
[" .exit - Exit shell"]
|
|
@@ -8202,12 +9027,12 @@ var ShellSession = class {
|
|
|
8202
9027
|
*/
|
|
8203
9028
|
async start() {
|
|
8204
9029
|
console.clear();
|
|
8205
|
-
console.log(
|
|
8206
|
-
console.log(
|
|
8207
|
-
console.log(
|
|
9030
|
+
console.log(chalk13.cyan.bold("\n=============================================="));
|
|
9031
|
+
console.log(chalk13.cyan.bold(" Vultisig Interactive Shell"));
|
|
9032
|
+
console.log(chalk13.cyan.bold("==============================================\n"));
|
|
8208
9033
|
await this.loadAllVaults();
|
|
8209
9034
|
this.displayVaultList();
|
|
8210
|
-
console.log(
|
|
9035
|
+
console.log(chalk13.gray('Type "help" for available commands, "exit" to quit\n'));
|
|
8211
9036
|
this.promptLoop().catch(() => {
|
|
8212
9037
|
});
|
|
8213
9038
|
}
|
|
@@ -8241,12 +9066,12 @@ var ShellSession = class {
|
|
|
8241
9066
|
const now = Date.now();
|
|
8242
9067
|
if (now - this.lastSigintTime < this.DOUBLE_CTRL_C_TIMEOUT) {
|
|
8243
9068
|
rl.close();
|
|
8244
|
-
console.log(
|
|
9069
|
+
console.log(chalk13.yellow("\nGoodbye!"));
|
|
8245
9070
|
this.ctx.dispose();
|
|
8246
9071
|
process.exit(0);
|
|
8247
9072
|
}
|
|
8248
9073
|
this.lastSigintTime = now;
|
|
8249
|
-
console.log(
|
|
9074
|
+
console.log(chalk13.yellow("\n(Press Ctrl+C again to exit)"));
|
|
8250
9075
|
rl.close();
|
|
8251
9076
|
resolve("");
|
|
8252
9077
|
});
|
|
@@ -8341,7 +9166,7 @@ var ShellSession = class {
|
|
|
8341
9166
|
stopAllSpinners();
|
|
8342
9167
|
process.stdout.write("\x1B[?25h");
|
|
8343
9168
|
process.stdout.write("\r\x1B[K");
|
|
8344
|
-
console.log(
|
|
9169
|
+
console.log(chalk13.yellow("\nCancelling operation..."));
|
|
8345
9170
|
};
|
|
8346
9171
|
const cleanup = () => {
|
|
8347
9172
|
process.removeListener("SIGINT", onSigint);
|
|
@@ -8378,10 +9203,10 @@ var ShellSession = class {
|
|
|
8378
9203
|
stopAllSpinners();
|
|
8379
9204
|
process.stdout.write("\x1B[?25h");
|
|
8380
9205
|
process.stdout.write("\r\x1B[K");
|
|
8381
|
-
console.log(
|
|
9206
|
+
console.log(chalk13.yellow("Operation cancelled"));
|
|
8382
9207
|
return;
|
|
8383
9208
|
}
|
|
8384
|
-
console.error(
|
|
9209
|
+
console.error(chalk13.red(`
|
|
8385
9210
|
Error: ${error2.message}`));
|
|
8386
9211
|
}
|
|
8387
9212
|
}
|
|
@@ -8414,7 +9239,7 @@ Error: ${error2.message}`));
|
|
|
8414
9239
|
break;
|
|
8415
9240
|
case "rename":
|
|
8416
9241
|
if (args.length === 0) {
|
|
8417
|
-
console.log(
|
|
9242
|
+
console.log(chalk13.yellow("Usage: rename <newName>"));
|
|
8418
9243
|
return;
|
|
8419
9244
|
}
|
|
8420
9245
|
await executeRename(this.ctx, args.join(" "));
|
|
@@ -8490,41 +9315,41 @@ Error: ${error2.message}`));
|
|
|
8490
9315
|
// Exit
|
|
8491
9316
|
case "exit":
|
|
8492
9317
|
case "quit":
|
|
8493
|
-
console.log(
|
|
9318
|
+
console.log(chalk13.yellow("\nGoodbye!"));
|
|
8494
9319
|
this.ctx.dispose();
|
|
8495
9320
|
process.exit(0);
|
|
8496
9321
|
break;
|
|
8497
9322
|
// eslint requires break even after process.exit
|
|
8498
9323
|
default:
|
|
8499
|
-
console.log(
|
|
8500
|
-
console.log(
|
|
9324
|
+
console.log(chalk13.yellow(`Unknown command: ${command}`));
|
|
9325
|
+
console.log(chalk13.gray('Type "help" for available commands'));
|
|
8501
9326
|
break;
|
|
8502
9327
|
}
|
|
8503
9328
|
}
|
|
8504
9329
|
// ===== Command Helpers =====
|
|
8505
9330
|
async switchVault(args) {
|
|
8506
9331
|
if (args.length === 0) {
|
|
8507
|
-
console.log(
|
|
8508
|
-
console.log(
|
|
9332
|
+
console.log(chalk13.yellow("Usage: vault <name>"));
|
|
9333
|
+
console.log(chalk13.gray('Run "vaults" to see available vaults'));
|
|
8509
9334
|
return;
|
|
8510
9335
|
}
|
|
8511
9336
|
const vaultName = args.join(" ");
|
|
8512
9337
|
const vault = this.ctx.findVaultByName(vaultName);
|
|
8513
9338
|
if (!vault) {
|
|
8514
|
-
console.log(
|
|
8515
|
-
console.log(
|
|
9339
|
+
console.log(chalk13.red(`Vault not found: ${vaultName}`));
|
|
9340
|
+
console.log(chalk13.gray('Run "vaults" to see available vaults'));
|
|
8516
9341
|
return;
|
|
8517
9342
|
}
|
|
8518
9343
|
await this.ctx.setActiveVault(vault);
|
|
8519
|
-
console.log(
|
|
9344
|
+
console.log(chalk13.green(`
|
|
8520
9345
|
+ Switched to: ${vault.name}`));
|
|
8521
9346
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
8522
|
-
const status = isUnlocked ?
|
|
9347
|
+
const status = isUnlocked ? chalk13.green("Unlocked") : chalk13.yellow("Locked");
|
|
8523
9348
|
console.log(`Status: ${status}`);
|
|
8524
9349
|
}
|
|
8525
9350
|
async importVault(args) {
|
|
8526
9351
|
if (args.length === 0) {
|
|
8527
|
-
console.log(
|
|
9352
|
+
console.log(chalk13.yellow("Usage: import <file>"));
|
|
8528
9353
|
return;
|
|
8529
9354
|
}
|
|
8530
9355
|
const filePath = args.join(" ");
|
|
@@ -8539,45 +9364,45 @@ Error: ${error2.message}`));
|
|
|
8539
9364
|
async createVault(args) {
|
|
8540
9365
|
const type = args[0]?.toLowerCase();
|
|
8541
9366
|
if (!type || type !== "fast" && type !== "secure") {
|
|
8542
|
-
console.log(
|
|
8543
|
-
console.log(
|
|
8544
|
-
console.log(
|
|
9367
|
+
console.log(chalk13.yellow("Usage: create <fast|secure>"));
|
|
9368
|
+
console.log(chalk13.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
|
|
9369
|
+
console.log(chalk13.gray(" create secure - Create a secure vault (multi-device MPC)"));
|
|
8545
9370
|
return;
|
|
8546
9371
|
}
|
|
8547
9372
|
let vault;
|
|
8548
9373
|
if (type === "fast") {
|
|
8549
9374
|
const name = await this.prompt("Vault name");
|
|
8550
9375
|
if (!name) {
|
|
8551
|
-
console.log(
|
|
9376
|
+
console.log(chalk13.red("Name is required"));
|
|
8552
9377
|
return;
|
|
8553
9378
|
}
|
|
8554
9379
|
const password = await this.promptPassword("Vault password");
|
|
8555
9380
|
if (!password) {
|
|
8556
|
-
console.log(
|
|
9381
|
+
console.log(chalk13.red("Password is required"));
|
|
8557
9382
|
return;
|
|
8558
9383
|
}
|
|
8559
9384
|
const email = await this.prompt("Email for verification");
|
|
8560
9385
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
8561
|
-
console.log(
|
|
9386
|
+
console.log(chalk13.red("Valid email is required"));
|
|
8562
9387
|
return;
|
|
8563
9388
|
}
|
|
8564
9389
|
vault = await this.withCancellation((signal) => executeCreateFast(this.ctx, { name, password, email, signal }));
|
|
8565
9390
|
} else {
|
|
8566
9391
|
const name = await this.prompt("Vault name");
|
|
8567
9392
|
if (!name) {
|
|
8568
|
-
console.log(
|
|
9393
|
+
console.log(chalk13.red("Name is required"));
|
|
8569
9394
|
return;
|
|
8570
9395
|
}
|
|
8571
9396
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
8572
9397
|
const shares = parseInt(sharesStr, 10);
|
|
8573
9398
|
if (isNaN(shares) || shares < 2) {
|
|
8574
|
-
console.log(
|
|
9399
|
+
console.log(chalk13.red("Must have at least 2 shares"));
|
|
8575
9400
|
return;
|
|
8576
9401
|
}
|
|
8577
9402
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
8578
9403
|
const threshold = parseInt(thresholdStr, 10);
|
|
8579
9404
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
8580
|
-
console.log(
|
|
9405
|
+
console.log(chalk13.red(`Threshold must be between 1 and ${shares}`));
|
|
8581
9406
|
return;
|
|
8582
9407
|
}
|
|
8583
9408
|
const password = await this.promptPassword("Vault password (optional, press Enter to skip)");
|
|
@@ -8599,37 +9424,37 @@ Error: ${error2.message}`));
|
|
|
8599
9424
|
async importSeedphrase(args) {
|
|
8600
9425
|
const type = args[0]?.toLowerCase();
|
|
8601
9426
|
if (!type || type !== "fast" && type !== "secure") {
|
|
8602
|
-
console.log(
|
|
8603
|
-
console.log(
|
|
8604
|
-
console.log(
|
|
9427
|
+
console.log(chalk13.cyan("Usage: create-from-seedphrase <fast|secure>"));
|
|
9428
|
+
console.log(chalk13.gray(" fast - Import with VultiServer (2-of-2)"));
|
|
9429
|
+
console.log(chalk13.gray(" secure - Import with device coordination (N-of-M)"));
|
|
8605
9430
|
return;
|
|
8606
9431
|
}
|
|
8607
|
-
console.log(
|
|
9432
|
+
console.log(chalk13.cyan("\nEnter your recovery phrase (words separated by spaces):"));
|
|
8608
9433
|
const mnemonic = await this.promptPassword("Seedphrase");
|
|
8609
9434
|
const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
|
|
8610
9435
|
if (!validation.valid) {
|
|
8611
|
-
console.log(
|
|
9436
|
+
console.log(chalk13.red(`Invalid seedphrase: ${validation.error}`));
|
|
8612
9437
|
if (validation.invalidWords?.length) {
|
|
8613
|
-
console.log(
|
|
9438
|
+
console.log(chalk13.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
|
|
8614
9439
|
}
|
|
8615
9440
|
return;
|
|
8616
9441
|
}
|
|
8617
|
-
console.log(
|
|
9442
|
+
console.log(chalk13.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
|
|
8618
9443
|
let vault;
|
|
8619
9444
|
if (type === "fast") {
|
|
8620
9445
|
const name = await this.prompt("Vault name");
|
|
8621
9446
|
if (!name) {
|
|
8622
|
-
console.log(
|
|
9447
|
+
console.log(chalk13.red("Name is required"));
|
|
8623
9448
|
return;
|
|
8624
9449
|
}
|
|
8625
9450
|
const password = await this.promptPassword("Vault password");
|
|
8626
9451
|
if (!password) {
|
|
8627
|
-
console.log(
|
|
9452
|
+
console.log(chalk13.red("Password is required"));
|
|
8628
9453
|
return;
|
|
8629
9454
|
}
|
|
8630
9455
|
const email = await this.prompt("Email for verification");
|
|
8631
9456
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
8632
|
-
console.log(
|
|
9457
|
+
console.log(chalk13.red("Valid email is required"));
|
|
8633
9458
|
return;
|
|
8634
9459
|
}
|
|
8635
9460
|
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
@@ -8647,19 +9472,19 @@ Error: ${error2.message}`));
|
|
|
8647
9472
|
} else {
|
|
8648
9473
|
const name = await this.prompt("Vault name");
|
|
8649
9474
|
if (!name) {
|
|
8650
|
-
console.log(
|
|
9475
|
+
console.log(chalk13.red("Name is required"));
|
|
8651
9476
|
return;
|
|
8652
9477
|
}
|
|
8653
9478
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
8654
9479
|
const shares = parseInt(sharesStr, 10);
|
|
8655
9480
|
if (isNaN(shares) || shares < 2) {
|
|
8656
|
-
console.log(
|
|
9481
|
+
console.log(chalk13.red("Must have at least 2 shares"));
|
|
8657
9482
|
return;
|
|
8658
9483
|
}
|
|
8659
9484
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
8660
9485
|
const threshold = parseInt(thresholdStr, 10);
|
|
8661
9486
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
8662
|
-
console.log(
|
|
9487
|
+
console.log(chalk13.red(`Threshold must be between 1 and ${shares}`));
|
|
8663
9488
|
return;
|
|
8664
9489
|
}
|
|
8665
9490
|
const password = await this.promptPassword("Vault password (optional, Enter to skip)");
|
|
@@ -8703,8 +9528,8 @@ Error: ${error2.message}`));
|
|
|
8703
9528
|
}
|
|
8704
9529
|
}
|
|
8705
9530
|
if (!fiatCurrencies4.includes(currency)) {
|
|
8706
|
-
console.log(
|
|
8707
|
-
console.log(
|
|
9531
|
+
console.log(chalk13.red(`Invalid currency: ${currency}`));
|
|
9532
|
+
console.log(chalk13.yellow(`Supported currencies: ${fiatCurrencies4.join(", ")}`));
|
|
8708
9533
|
return;
|
|
8709
9534
|
}
|
|
8710
9535
|
const raw = args.includes("--raw");
|
|
@@ -8712,7 +9537,7 @@ Error: ${error2.message}`));
|
|
|
8712
9537
|
}
|
|
8713
9538
|
async runSend(args) {
|
|
8714
9539
|
if (args.length < 3) {
|
|
8715
|
-
console.log(
|
|
9540
|
+
console.log(chalk13.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
|
|
8716
9541
|
return;
|
|
8717
9542
|
}
|
|
8718
9543
|
const [chainStr, to, amount, ...rest] = args;
|
|
@@ -8732,7 +9557,7 @@ Error: ${error2.message}`));
|
|
|
8732
9557
|
await this.withAbortHandler((signal) => executeSend(this.ctx, { chain, to, amount, tokenId, memo, signal }));
|
|
8733
9558
|
} catch (err) {
|
|
8734
9559
|
if (err.message === "Transaction cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
8735
|
-
console.log(
|
|
9560
|
+
console.log(chalk13.yellow("\nTransaction cancelled"));
|
|
8736
9561
|
return;
|
|
8737
9562
|
}
|
|
8738
9563
|
throw err;
|
|
@@ -8740,7 +9565,7 @@ Error: ${error2.message}`));
|
|
|
8740
9565
|
}
|
|
8741
9566
|
async runTxStatus(args) {
|
|
8742
9567
|
if (args.length < 2) {
|
|
8743
|
-
console.log(
|
|
9568
|
+
console.log(chalk13.yellow("Usage: tx-status <chain> <txHash> [--no-wait]"));
|
|
8744
9569
|
return;
|
|
8745
9570
|
}
|
|
8746
9571
|
const [chainStr, txHash, ...rest] = args;
|
|
@@ -8758,8 +9583,8 @@ Error: ${error2.message}`));
|
|
|
8758
9583
|
} else if (args[i] === "--add" && i + 1 < args.length) {
|
|
8759
9584
|
const chain = findChainByName(args[i + 1]);
|
|
8760
9585
|
if (!chain) {
|
|
8761
|
-
console.log(
|
|
8762
|
-
console.log(
|
|
9586
|
+
console.log(chalk13.red(`Unknown chain: ${args[i + 1]}`));
|
|
9587
|
+
console.log(chalk13.gray("Use tab completion to see available chains"));
|
|
8763
9588
|
return;
|
|
8764
9589
|
}
|
|
8765
9590
|
addChain = chain;
|
|
@@ -8767,8 +9592,8 @@ Error: ${error2.message}`));
|
|
|
8767
9592
|
} else if (args[i] === "--remove" && i + 1 < args.length) {
|
|
8768
9593
|
const chain = findChainByName(args[i + 1]);
|
|
8769
9594
|
if (!chain) {
|
|
8770
|
-
console.log(
|
|
8771
|
-
console.log(
|
|
9595
|
+
console.log(chalk13.red(`Unknown chain: ${args[i + 1]}`));
|
|
9596
|
+
console.log(chalk13.gray("Use tab completion to see available chains"));
|
|
8772
9597
|
return;
|
|
8773
9598
|
}
|
|
8774
9599
|
removeChain = chain;
|
|
@@ -8779,7 +9604,7 @@ Error: ${error2.message}`));
|
|
|
8779
9604
|
}
|
|
8780
9605
|
async runTokens(args) {
|
|
8781
9606
|
if (args.length === 0) {
|
|
8782
|
-
console.log(
|
|
9607
|
+
console.log(chalk13.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
|
|
8783
9608
|
return;
|
|
8784
9609
|
}
|
|
8785
9610
|
const chainStr = args[0];
|
|
@@ -8800,7 +9625,7 @@ Error: ${error2.message}`));
|
|
|
8800
9625
|
async runSwapQuote(args) {
|
|
8801
9626
|
if (args.length < 3) {
|
|
8802
9627
|
console.log(
|
|
8803
|
-
|
|
9628
|
+
chalk13.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
|
|
8804
9629
|
);
|
|
8805
9630
|
return;
|
|
8806
9631
|
}
|
|
@@ -8824,7 +9649,7 @@ Error: ${error2.message}`));
|
|
|
8824
9649
|
async runSwap(args) {
|
|
8825
9650
|
if (args.length < 3) {
|
|
8826
9651
|
console.log(
|
|
8827
|
-
|
|
9652
|
+
chalk13.yellow(
|
|
8828
9653
|
"Usage: swap <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>] [--slippage <pct>]"
|
|
8829
9654
|
)
|
|
8830
9655
|
);
|
|
@@ -8855,7 +9680,7 @@ Error: ${error2.message}`));
|
|
|
8855
9680
|
);
|
|
8856
9681
|
} catch (err) {
|
|
8857
9682
|
if (err.message === "Swap cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
8858
|
-
console.log(
|
|
9683
|
+
console.log(chalk13.yellow("\nSwap cancelled"));
|
|
8859
9684
|
return;
|
|
8860
9685
|
}
|
|
8861
9686
|
throw err;
|
|
@@ -8917,24 +9742,24 @@ Error: ${error2.message}`));
|
|
|
8917
9742
|
}
|
|
8918
9743
|
getPrompt() {
|
|
8919
9744
|
const vault = this.ctx.getActiveVault();
|
|
8920
|
-
if (!vault) return
|
|
9745
|
+
if (!vault) return chalk13.cyan("wallet> ");
|
|
8921
9746
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
8922
|
-
const status = isUnlocked ?
|
|
8923
|
-
return
|
|
9747
|
+
const status = isUnlocked ? chalk13.green("\u{1F513}") : chalk13.yellow("\u{1F512}");
|
|
9748
|
+
return chalk13.cyan(`wallet[${vault.name}]${status}> `);
|
|
8924
9749
|
}
|
|
8925
9750
|
displayVaultList() {
|
|
8926
9751
|
const vaults = Array.from(this.ctx.getVaults().values());
|
|
8927
9752
|
const activeVault = this.ctx.getActiveVault();
|
|
8928
9753
|
if (vaults.length === 0) {
|
|
8929
|
-
console.log(
|
|
9754
|
+
console.log(chalk13.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
|
|
8930
9755
|
return;
|
|
8931
9756
|
}
|
|
8932
|
-
console.log(
|
|
9757
|
+
console.log(chalk13.cyan("Loaded Vaults:\n"));
|
|
8933
9758
|
vaults.forEach((vault) => {
|
|
8934
9759
|
const isActive = vault.id === activeVault?.id;
|
|
8935
9760
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
8936
|
-
const activeMarker = isActive ?
|
|
8937
|
-
const lockIcon = isUnlocked ?
|
|
9761
|
+
const activeMarker = isActive ? chalk13.green(" (active)") : "";
|
|
9762
|
+
const lockIcon = isUnlocked ? chalk13.green("\u{1F513}") : chalk13.yellow("\u{1F512}");
|
|
8938
9763
|
console.log(` ${lockIcon} ${vault.name}${activeMarker} - ${vault.type}`);
|
|
8939
9764
|
});
|
|
8940
9765
|
console.log();
|
|
@@ -8942,150 +9767,7 @@ Error: ${error2.message}`));
|
|
|
8942
9767
|
};
|
|
8943
9768
|
|
|
8944
9769
|
// src/lib/errors.ts
|
|
8945
|
-
import chalk13 from "chalk";
|
|
8946
|
-
|
|
8947
|
-
// src/lib/version.ts
|
|
8948
9770
|
import chalk14 from "chalk";
|
|
8949
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3, existsSync as existsSync2 } from "fs";
|
|
8950
|
-
import { homedir as homedir3 } from "os";
|
|
8951
|
-
import { join as join3 } from "path";
|
|
8952
|
-
var cachedVersion = null;
|
|
8953
|
-
function getVersion() {
|
|
8954
|
-
if (cachedVersion) return cachedVersion;
|
|
8955
|
-
if (true) {
|
|
8956
|
-
cachedVersion = "0.15.2";
|
|
8957
|
-
return cachedVersion;
|
|
8958
|
-
}
|
|
8959
|
-
try {
|
|
8960
|
-
const packagePath = new URL("../../package.json", import.meta.url);
|
|
8961
|
-
const pkg = JSON.parse(readFileSync3(packagePath, "utf-8"));
|
|
8962
|
-
cachedVersion = pkg.version;
|
|
8963
|
-
return cachedVersion;
|
|
8964
|
-
} catch {
|
|
8965
|
-
cachedVersion = "0.0.0-unknown";
|
|
8966
|
-
return cachedVersion;
|
|
8967
|
-
}
|
|
8968
|
-
}
|
|
8969
|
-
var CACHE_DIR = join3(homedir3(), ".vultisig", "cache");
|
|
8970
|
-
var VERSION_CACHE_FILE = join3(CACHE_DIR, "version-check.json");
|
|
8971
|
-
var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
8972
|
-
function readVersionCache() {
|
|
8973
|
-
try {
|
|
8974
|
-
if (!existsSync2(VERSION_CACHE_FILE)) return null;
|
|
8975
|
-
const data = readFileSync3(VERSION_CACHE_FILE, "utf-8");
|
|
8976
|
-
return JSON.parse(data);
|
|
8977
|
-
} catch {
|
|
8978
|
-
return null;
|
|
8979
|
-
}
|
|
8980
|
-
}
|
|
8981
|
-
function writeVersionCache(cache) {
|
|
8982
|
-
try {
|
|
8983
|
-
if (!existsSync2(CACHE_DIR)) {
|
|
8984
|
-
mkdirSync3(CACHE_DIR, { recursive: true });
|
|
8985
|
-
}
|
|
8986
|
-
writeFileSync3(VERSION_CACHE_FILE, JSON.stringify(cache, null, 2));
|
|
8987
|
-
} catch {
|
|
8988
|
-
}
|
|
8989
|
-
}
|
|
8990
|
-
async function fetchLatestVersion() {
|
|
8991
|
-
try {
|
|
8992
|
-
const controller = new AbortController();
|
|
8993
|
-
const timeout = setTimeout(() => controller.abort(), 5e3);
|
|
8994
|
-
const response = await fetch("https://registry.npmjs.org/@vultisig/cli/latest", {
|
|
8995
|
-
signal: controller.signal,
|
|
8996
|
-
headers: {
|
|
8997
|
-
Accept: "application/json"
|
|
8998
|
-
}
|
|
8999
|
-
});
|
|
9000
|
-
clearTimeout(timeout);
|
|
9001
|
-
if (!response.ok) return null;
|
|
9002
|
-
const data = await response.json();
|
|
9003
|
-
return data.version ?? null;
|
|
9004
|
-
} catch {
|
|
9005
|
-
return null;
|
|
9006
|
-
}
|
|
9007
|
-
}
|
|
9008
|
-
function isNewerVersion(v1, v2) {
|
|
9009
|
-
const parse = (v) => {
|
|
9010
|
-
const clean2 = v.replace(/^v/, "").replace(/-.*$/, "");
|
|
9011
|
-
return clean2.split(".").map((n) => parseInt(n, 10) || 0);
|
|
9012
|
-
};
|
|
9013
|
-
const p1 = parse(v1);
|
|
9014
|
-
const p2 = parse(v2);
|
|
9015
|
-
for (let i = 0; i < 3; i++) {
|
|
9016
|
-
const n1 = p1[i] ?? 0;
|
|
9017
|
-
const n2 = p2[i] ?? 0;
|
|
9018
|
-
if (n2 > n1) return true;
|
|
9019
|
-
if (n2 < n1) return false;
|
|
9020
|
-
}
|
|
9021
|
-
if (v1.includes("-") && !v2.includes("-")) return true;
|
|
9022
|
-
return false;
|
|
9023
|
-
}
|
|
9024
|
-
async function checkForUpdates() {
|
|
9025
|
-
if (process.env.VULTISIG_NO_UPDATE_CHECK === "1") {
|
|
9026
|
-
return null;
|
|
9027
|
-
}
|
|
9028
|
-
const currentVersion = getVersion();
|
|
9029
|
-
const cache = readVersionCache();
|
|
9030
|
-
if (cache && Date.now() - cache.lastCheck < CACHE_TTL_MS) {
|
|
9031
|
-
return {
|
|
9032
|
-
currentVersion,
|
|
9033
|
-
latestVersion: cache.latestVersion,
|
|
9034
|
-
updateAvailable: cache.latestVersion ? isNewerVersion(currentVersion, cache.latestVersion) : false
|
|
9035
|
-
};
|
|
9036
|
-
}
|
|
9037
|
-
const latestVersion = await fetchLatestVersion();
|
|
9038
|
-
writeVersionCache({
|
|
9039
|
-
lastCheck: Date.now(),
|
|
9040
|
-
latestVersion
|
|
9041
|
-
});
|
|
9042
|
-
return {
|
|
9043
|
-
currentVersion,
|
|
9044
|
-
latestVersion,
|
|
9045
|
-
updateAvailable: latestVersion ? isNewerVersion(currentVersion, latestVersion) : false
|
|
9046
|
-
};
|
|
9047
|
-
}
|
|
9048
|
-
function formatVersionShort() {
|
|
9049
|
-
return `vultisig/${getVersion()}`;
|
|
9050
|
-
}
|
|
9051
|
-
function formatVersionDetailed() {
|
|
9052
|
-
const lines = [];
|
|
9053
|
-
lines.push(chalk14.bold(`Vultisig CLI v${getVersion()}`));
|
|
9054
|
-
lines.push("");
|
|
9055
|
-
lines.push(` Node.js: ${process.version}`);
|
|
9056
|
-
lines.push(` Platform: ${process.platform}-${process.arch}`);
|
|
9057
|
-
lines.push(` Config: ~/.vultisig/`);
|
|
9058
|
-
return lines.join("\n");
|
|
9059
|
-
}
|
|
9060
|
-
function detectInstallMethod() {
|
|
9061
|
-
const execPath = process.execPath;
|
|
9062
|
-
if (execPath.includes("homebrew") || execPath.includes("Cellar")) {
|
|
9063
|
-
return "homebrew";
|
|
9064
|
-
}
|
|
9065
|
-
if (process.env.npm_execpath?.includes("yarn")) {
|
|
9066
|
-
return "yarn";
|
|
9067
|
-
}
|
|
9068
|
-
if (process.env.npm_config_user_agent?.includes("npm")) {
|
|
9069
|
-
return "npm";
|
|
9070
|
-
}
|
|
9071
|
-
if (execPath.includes("node_modules")) {
|
|
9072
|
-
return "npm";
|
|
9073
|
-
}
|
|
9074
|
-
return "unknown";
|
|
9075
|
-
}
|
|
9076
|
-
function getUpdateCommand() {
|
|
9077
|
-
const method = detectInstallMethod();
|
|
9078
|
-
switch (method) {
|
|
9079
|
-
case "npm":
|
|
9080
|
-
return "npm update -g @vultisig/cli";
|
|
9081
|
-
case "yarn":
|
|
9082
|
-
return "yarn global upgrade @vultisig/cli";
|
|
9083
|
-
case "homebrew":
|
|
9084
|
-
return "brew upgrade vultisig";
|
|
9085
|
-
default:
|
|
9086
|
-
return "npm update -g @vultisig/cli";
|
|
9087
|
-
}
|
|
9088
|
-
}
|
|
9089
9771
|
|
|
9090
9772
|
// src/lib/completion.ts
|
|
9091
9773
|
import { homedir as homedir4 } from "os";
|
|
@@ -9426,9 +10108,32 @@ setupUserAgent();
|
|
|
9426
10108
|
if (handled) process.exit(0);
|
|
9427
10109
|
})();
|
|
9428
10110
|
var ctx;
|
|
9429
|
-
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(
|
|
10111
|
+
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(
|
|
10112
|
+
"-o, --output <format>",
|
|
10113
|
+
"Output format: json, table (defaults to json when piped)",
|
|
10114
|
+
(val) => {
|
|
10115
|
+
if (!["json", "table"].includes(val)) throw new InvalidArgumentError('Must be "json" or "table"');
|
|
10116
|
+
return val;
|
|
10117
|
+
},
|
|
10118
|
+
process.stdout.isTTY ? "table" : "json"
|
|
10119
|
+
).option("-q, --quiet", "Strip empty/zero fields from output").option("--fields <fields>", "Comma-separated list of fields to include in output").option("--non-interactive", "Disable interactive prompts (fail instead of asking)").option("--ci", "CI/automation mode (equivalent to --output json --non-interactive --quiet)").option("-i, --interactive", "Start interactive shell mode").option("--vault <nameOrId>", "Specify vault by name or ID").option("--server-url <url>", "Base Vultisig API URL for FastVault and relay endpoints").addHelpText(
|
|
10120
|
+
"after",
|
|
10121
|
+
"\nExit codes:\n" + Object.entries(EXIT_CODE_DESCRIPTIONS).map(([k, v]) => ` ${k} ${v}`).join("\n") + "\n\nEnvironment variables:\n VAULT_PASSWORD Vault password for signing (bypasses prompt)\n VULTISIG_PASSWORD Alias for VAULT_PASSWORD\n VAULT_PASSWORDS Space-separated VaultName:password pairs\n VULTISIG_VAULT Default vault name or ID\n VULTISIG_CONFIG_DIR Override config directory (~/.vultisig)\n VULTISIG_SILENT Set to 1 for silent mode\n NO_COLOR Disable colored output"
|
|
10122
|
+
).hook("preAction", (thisCommand) => {
|
|
9430
10123
|
const opts = thisCommand.opts();
|
|
10124
|
+
if (opts.ci) {
|
|
10125
|
+
const outputExplicit = process.argv.some((a) => a === "--output" || a === "-o" || a.startsWith("--output="));
|
|
10126
|
+
opts.output = opts.output === "table" && !outputExplicit ? "json" : opts.output;
|
|
10127
|
+
opts.quiet = true;
|
|
10128
|
+
opts.nonInteractive = true;
|
|
10129
|
+
}
|
|
9431
10130
|
initOutputMode({ silent: opts.silent, output: opts.output });
|
|
10131
|
+
setQuiet(!!opts.quiet);
|
|
10132
|
+
setNonInteractive(!!opts.nonInteractive);
|
|
10133
|
+
const fields = opts.fields;
|
|
10134
|
+
setFields(
|
|
10135
|
+
fields ? fields.split(",").map((f) => f.trim()).filter(Boolean) : void 0
|
|
10136
|
+
);
|
|
9432
10137
|
});
|
|
9433
10138
|
async function findVaultByNameOrId(sdk, nameOrId) {
|
|
9434
10139
|
const vaults = await sdk.listVaults();
|
|
@@ -9472,7 +10177,15 @@ async function init(vaultOverride, unlockPassword, passwordTTL) {
|
|
|
9472
10177
|
return ctx;
|
|
9473
10178
|
}
|
|
9474
10179
|
var createCmd = program.command("create").description("Create a vault");
|
|
9475
|
-
createCmd.command("fast").description("Create a fast vault (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--two-step", 'Create vault without verifying OTP (verify later with "vultisig verify")').
|
|
10180
|
+
createCmd.command("fast").description("Create a fast vault (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--two-step", 'Create vault without verifying OTP (verify later with "vultisig verify")').addHelpText(
|
|
10181
|
+
"after",
|
|
10182
|
+
`
|
|
10183
|
+
Examples:
|
|
10184
|
+
vultisig create fast --name mywallet --password secret --email me@example.com
|
|
10185
|
+
vultisig create fast --name mywallet --password secret --email me@example.com --two-step
|
|
10186
|
+
|
|
10187
|
+
See also: verify, auth setup`
|
|
10188
|
+
).action(
|
|
9476
10189
|
withExit(async (options) => {
|
|
9477
10190
|
const context = await init(program.opts().vault);
|
|
9478
10191
|
await executeCreateFast(context, { ...options, twoStep: options.twoStep });
|
|
@@ -9498,7 +10211,13 @@ program.command("add-mldsa").description("Add ML-DSA (post-quantum) keys to the
|
|
|
9498
10211
|
});
|
|
9499
10212
|
})
|
|
9500
10213
|
);
|
|
9501
|
-
program.command("import <file>").description("Import vault from .vult file").option("--password <password>", "Password to decrypt the vault file").
|
|
10214
|
+
program.command("import <file>").description("Import vault from .vult file").option("--password <password>", "Password to decrypt the vault file").addHelpText(
|
|
10215
|
+
"after",
|
|
10216
|
+
`
|
|
10217
|
+
Examples:
|
|
10218
|
+
vultisig import ~/vault-backup.vult
|
|
10219
|
+
vultisig import ~/vault-backup.vult --password mypassword`
|
|
10220
|
+
).action(
|
|
9502
10221
|
withExit(async (file, options) => {
|
|
9503
10222
|
const context = await init(program.opts().vault);
|
|
9504
10223
|
await executeImport(context, file, options.password);
|
|
@@ -9506,6 +10225,7 @@ program.command("import <file>").description("Import vault from .vult file").opt
|
|
|
9506
10225
|
);
|
|
9507
10226
|
var createFromSeedphraseCmd = program.command("create-from-seedphrase").description("Create vault from BIP39 seedphrase");
|
|
9508
10227
|
async function promptSeedphrase() {
|
|
10228
|
+
requireInteractive("Use --mnemonic flag to provide seedphrase non-interactively.");
|
|
9509
10229
|
info("\nEnter your 12 or 24-word recovery phrase.");
|
|
9510
10230
|
info("Words will be hidden as you type.\n");
|
|
9511
10231
|
const answer = await inquirer8.prompt([
|
|
@@ -9526,6 +10246,7 @@ async function promptSeedphrase() {
|
|
|
9526
10246
|
return answer.mnemonic.trim().toLowerCase();
|
|
9527
10247
|
}
|
|
9528
10248
|
async function promptQrPayload() {
|
|
10249
|
+
requireInteractive("Use --qr or --qr-file flag to provide QR payload non-interactively.");
|
|
9529
10250
|
info("\nEnter the QR code payload from the initiator device.");
|
|
9530
10251
|
info('The payload starts with "vultisig://".\n');
|
|
9531
10252
|
const answer = await inquirer8.prompt([
|
|
@@ -9651,7 +10372,15 @@ program.command("verify <vaultId>").description("Verify vault with email verific
|
|
|
9651
10372
|
}
|
|
9652
10373
|
)
|
|
9653
10374
|
);
|
|
9654
|
-
program.command("balance [chain]").description(
|
|
10375
|
+
program.command("balance [chain]").description(descriptions.balance.description).option("-t, --tokens", descriptions.balance.params.includeTokens).option("--raw", "Show raw values (wei/satoshis) for programmatic use").addHelpText(
|
|
10376
|
+
"after",
|
|
10377
|
+
`
|
|
10378
|
+
Examples:
|
|
10379
|
+
vultisig balance
|
|
10380
|
+
vultisig balance Ethereum --tokens
|
|
10381
|
+
vultisig balance --output json --fields chain,amount
|
|
10382
|
+
vultisig balance --output json -q`
|
|
10383
|
+
).action(
|
|
9655
10384
|
withExit(async (chainStr, options) => {
|
|
9656
10385
|
const context = await init(program.opts().vault);
|
|
9657
10386
|
await executeBalance(context, {
|
|
@@ -9661,7 +10390,20 @@ program.command("balance [chain]").description("Show balance for a chain or all
|
|
|
9661
10390
|
});
|
|
9662
10391
|
})
|
|
9663
10392
|
);
|
|
9664
|
-
program.command("send <chain> <to> [amount]").description(
|
|
10393
|
+
program.command("send <chain> <to> [amount]").description(descriptions.send.description).option("--max", "Send maximum amount (balance minus fees)").option("--token <tokenId>", "Token to send (default: native)").option("--memo <memo>", "Transaction memo").option("--dry-run", "Preview transaction without signing or broadcasting").option("--confirm", "Confirm and broadcast (without this flag, runs as a preview)").option("-y, --yes", "Alias for --confirm").option("--password <password>", "Vault password for signing").addHelpText(
|
|
10394
|
+
"after",
|
|
10395
|
+
`
|
|
10396
|
+
Examples:
|
|
10397
|
+
vultisig send Ethereum 0x1234...abcd 0.1
|
|
10398
|
+
vultisig send Bitcoin bc1q... --max --confirm
|
|
10399
|
+
vultisig send Ethereum 0x... 0.5 --dry-run --output json
|
|
10400
|
+
|
|
10401
|
+
Environment variables:
|
|
10402
|
+
VAULT_PASSWORD Vault password (bypasses prompt)
|
|
10403
|
+
VAULT_PASSWORDS Space-separated VaultName:password pairs
|
|
10404
|
+
|
|
10405
|
+
See also: balance, tx-status`
|
|
10406
|
+
).action(
|
|
9665
10407
|
withExit(
|
|
9666
10408
|
async (chainStr, to, amount, options) => {
|
|
9667
10409
|
if (!amount && !options.max) throw new Error("Provide an amount or use --max");
|
|
@@ -9674,7 +10416,8 @@ program.command("send <chain> <to> [amount]").description("Send tokens to an add
|
|
|
9674
10416
|
amount: amount ?? "max",
|
|
9675
10417
|
tokenId: options.token,
|
|
9676
10418
|
memo: options.memo,
|
|
9677
|
-
|
|
10419
|
+
dryRun: options.dryRun,
|
|
10420
|
+
yes: options.yes || options.confirm,
|
|
9678
10421
|
password: options.password
|
|
9679
10422
|
});
|
|
9680
10423
|
} catch (err) {
|
|
@@ -9687,7 +10430,13 @@ program.command("send <chain> <to> [amount]").description("Send tokens to an add
|
|
|
9687
10430
|
}
|
|
9688
10431
|
)
|
|
9689
10432
|
);
|
|
9690
|
-
program.command("execute <chain> <contract> <msg>").description("Execute a CosmWasm smart contract (THORChain, MayaChain)").option("--funds <funds>", 'Funds to send with execution (format: "denom:amount" or "denom:amount,denom2:amount2")').option("--memo <memo>", "Transaction memo").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").
|
|
10433
|
+
program.command("execute <chain> <contract> <msg>").description("Execute a CosmWasm smart contract (THORChain, MayaChain)").option("--funds <funds>", 'Funds to send with execution (format: "denom:amount" or "denom:amount,denom2:amount2")').option("--memo <memo>", "Transaction memo").option("--dry-run", "Preview execution without signing or broadcasting").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").addHelpText(
|
|
10434
|
+
"after",
|
|
10435
|
+
`
|
|
10436
|
+
Examples:
|
|
10437
|
+
vultisig execute THORChain <contract> '{"swap":{}}' --yes
|
|
10438
|
+
vultisig execute THORChain <contract> '{"deposit":{}}' --funds rune:1000000 --output json`
|
|
10439
|
+
).action(
|
|
9691
10440
|
withExit(
|
|
9692
10441
|
async (chainStr, contract, msg, options) => {
|
|
9693
10442
|
const context = await init(program.opts().vault, options.password);
|
|
@@ -9698,6 +10447,7 @@ program.command("execute <chain> <contract> <msg>").description("Execute a CosmW
|
|
|
9698
10447
|
msg,
|
|
9699
10448
|
funds: options.funds,
|
|
9700
10449
|
memo: options.memo,
|
|
10450
|
+
dryRun: options.dryRun,
|
|
9701
10451
|
yes: options.yes,
|
|
9702
10452
|
password: options.password
|
|
9703
10453
|
});
|
|
@@ -9730,7 +10480,13 @@ program.command("broadcast").description("Broadcast a pre-signed raw transaction
|
|
|
9730
10480
|
});
|
|
9731
10481
|
})
|
|
9732
10482
|
);
|
|
9733
|
-
program.command("tx-status").description("Check the status of a transaction (polls until confirmed)").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--tx-hash <hash>", "Transaction hash to check").option("--no-wait", "Return immediately without waiting for confirmation").
|
|
10483
|
+
program.command("tx-status").description("Check the status of a transaction (polls until confirmed)").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--tx-hash <hash>", "Transaction hash to check").option("--no-wait", "Return immediately without waiting for confirmation").addHelpText(
|
|
10484
|
+
"after",
|
|
10485
|
+
`
|
|
10486
|
+
Examples:
|
|
10487
|
+
vultisig tx-status --chain Ethereum --tx-hash 0xabc...
|
|
10488
|
+
vultisig tx-status --chain Bitcoin --tx-hash abc... --no-wait --output json`
|
|
10489
|
+
).action(
|
|
9734
10490
|
withExit(async (options) => {
|
|
9735
10491
|
const context = await init(program.opts().vault);
|
|
9736
10492
|
await executeTxStatus(context, {
|
|
@@ -9740,7 +10496,13 @@ program.command("tx-status").description("Check the status of a transaction (pol
|
|
|
9740
10496
|
});
|
|
9741
10497
|
})
|
|
9742
10498
|
);
|
|
9743
|
-
program.command("portfolio").description(
|
|
10499
|
+
program.command("portfolio").description(descriptions.portfolio.description).option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").option("--raw", "Show raw values (wei/satoshis) for programmatic use").addHelpText(
|
|
10500
|
+
"after",
|
|
10501
|
+
`
|
|
10502
|
+
Examples:
|
|
10503
|
+
vultisig portfolio
|
|
10504
|
+
vultisig portfolio --currency eur --output json`
|
|
10505
|
+
).action(
|
|
9744
10506
|
withExit(async (options) => {
|
|
9745
10507
|
const context = await init(program.opts().vault);
|
|
9746
10508
|
await executePortfolio(context, {
|
|
@@ -9767,7 +10529,13 @@ program.command("discount").description("Show your VULT discount tier for swap f
|
|
|
9767
10529
|
await executeDiscount(context, { refresh: options.refresh });
|
|
9768
10530
|
})
|
|
9769
10531
|
);
|
|
9770
|
-
program.command("export [path]").description("Export vault to file").option("--password <password>", "Password to unlock the vault (for encrypted vaults)").option("--exportPassword <password>", "Password to encrypt the exported file (defaults to --password)").
|
|
10532
|
+
program.command("export [path]").description("Export vault to file").option("--password <password>", "Password to unlock the vault (for encrypted vaults)").option("--exportPassword <password>", "Password to encrypt the exported file (defaults to --password)").addHelpText(
|
|
10533
|
+
"after",
|
|
10534
|
+
`
|
|
10535
|
+
Examples:
|
|
10536
|
+
vultisig export ~/backup.vult
|
|
10537
|
+
vultisig export ~/backup.vult --password mypass --output json`
|
|
10538
|
+
).action(
|
|
9771
10539
|
withExit(async (path4, options) => {
|
|
9772
10540
|
const context = await init(program.opts().vault, options.password);
|
|
9773
10541
|
await executeExport(context, {
|
|
@@ -9777,7 +10545,13 @@ program.command("export [path]").description("Export vault to file").option("--p
|
|
|
9777
10545
|
});
|
|
9778
10546
|
})
|
|
9779
10547
|
);
|
|
9780
|
-
program.command("addresses").description(
|
|
10548
|
+
program.command("addresses").description(descriptions.address.description).addHelpText(
|
|
10549
|
+
"after",
|
|
10550
|
+
`
|
|
10551
|
+
Examples:
|
|
10552
|
+
vultisig addresses
|
|
10553
|
+
vultisig addresses --output json --fields chain,address`
|
|
10554
|
+
).action(
|
|
9781
10555
|
withExit(async () => {
|
|
9782
10556
|
const context = await init(program.opts().vault);
|
|
9783
10557
|
await executeAddresses(context);
|
|
@@ -9795,7 +10569,14 @@ program.command("address-book").description("Manage address book entries").optio
|
|
|
9795
10569
|
});
|
|
9796
10570
|
})
|
|
9797
10571
|
);
|
|
9798
|
-
program.command("chains").description("List and manage chains").option("--add <chain>", "Add a chain").option("--add-all", "Add all supported chains").option("--remove <chain>", "Remove a chain").
|
|
10572
|
+
program.command("chains").description("List and manage chains").option("--add <chain>", "Add a chain").option("--add-all", "Add all supported chains").option("--remove <chain>", "Remove a chain").addHelpText(
|
|
10573
|
+
"after",
|
|
10574
|
+
`
|
|
10575
|
+
Examples:
|
|
10576
|
+
vultisig chains
|
|
10577
|
+
vultisig chains --add Solana
|
|
10578
|
+
vultisig chains --add-all --output json`
|
|
10579
|
+
).action(
|
|
9799
10580
|
withExit(async (options) => {
|
|
9800
10581
|
const context = await init(program.opts().vault);
|
|
9801
10582
|
await executeChains(context, {
|
|
@@ -9805,7 +10586,13 @@ program.command("chains").description("List and manage chains").option("--add <c
|
|
|
9805
10586
|
});
|
|
9806
10587
|
})
|
|
9807
10588
|
);
|
|
9808
|
-
program.command("vaults").description("List all stored vaults").
|
|
10589
|
+
program.command("vaults").description("List all stored vaults").addHelpText(
|
|
10590
|
+
"after",
|
|
10591
|
+
`
|
|
10592
|
+
Examples:
|
|
10593
|
+
vultisig vaults
|
|
10594
|
+
vultisig vaults --output json`
|
|
10595
|
+
).action(
|
|
9809
10596
|
withExit(async () => {
|
|
9810
10597
|
const context = await init(program.opts().vault);
|
|
9811
10598
|
await executeVaults(context);
|
|
@@ -9823,7 +10610,7 @@ program.command("rename <newName>").description("Rename the active vault").actio
|
|
|
9823
10610
|
await executeRename(context, newName);
|
|
9824
10611
|
})
|
|
9825
10612
|
);
|
|
9826
|
-
program.command("info").description(
|
|
10613
|
+
program.command("info").description(descriptions.vaultInfo.description).action(
|
|
9827
10614
|
withExit(async () => {
|
|
9828
10615
|
const context = await init(program.opts().vault);
|
|
9829
10616
|
await executeInfo(context);
|
|
@@ -9838,7 +10625,14 @@ program.command("delete [vault]").description("Delete a vault from local storage
|
|
|
9838
10625
|
});
|
|
9839
10626
|
})
|
|
9840
10627
|
);
|
|
9841
|
-
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("--
|
|
10628
|
+
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("--discover", "Auto-discover tokens with balances on the chain").addHelpText(
|
|
10629
|
+
"after",
|
|
10630
|
+
`
|
|
10631
|
+
Examples:
|
|
10632
|
+
vultisig tokens Ethereum
|
|
10633
|
+
vultisig tokens Ethereum --discover --output json
|
|
10634
|
+
vultisig tokens Ethereum --add 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 --symbol USDC --decimals 6`
|
|
10635
|
+
).option("--symbol <symbol>", "Token symbol (for --add)").option("--name <name>", "Token name (for --add)").option("--decimals <decimals>", "Token decimals (for --add)", "18").action(
|
|
9842
10636
|
withExit(
|
|
9843
10637
|
async (chainStr, options) => {
|
|
9844
10638
|
const context = await init(program.opts().vault);
|
|
@@ -9846,6 +10640,7 @@ program.command("tokens <chain>").description("List and manage tokens for a chai
|
|
|
9846
10640
|
chain: findChainByName(chainStr) || chainStr,
|
|
9847
10641
|
add: options.add,
|
|
9848
10642
|
remove: options.remove,
|
|
10643
|
+
discover: options.discover,
|
|
9849
10644
|
symbol: options.symbol,
|
|
9850
10645
|
name: options.name,
|
|
9851
10646
|
decimals: options.decimals ? parseInt(options.decimals, 10) : void 0
|
|
@@ -9853,13 +10648,19 @@ program.command("tokens <chain>").description("List and manage tokens for a chai
|
|
|
9853
10648
|
}
|
|
9854
10649
|
)
|
|
9855
10650
|
);
|
|
9856
|
-
program.command("swap-chains").description(
|
|
10651
|
+
program.command("swap-chains").description(descriptions.supportedChains.description).action(
|
|
9857
10652
|
withExit(async () => {
|
|
9858
10653
|
const context = await init(program.opts().vault);
|
|
9859
10654
|
await executeSwapChains(context);
|
|
9860
10655
|
})
|
|
9861
10656
|
);
|
|
9862
|
-
program.command("swap-quote <fromChain> <toChain> [amount]").description(
|
|
10657
|
+
program.command("swap-quote <fromChain> <toChain> [amount]").description(descriptions.swapQuote.description).option("--max", "Swap maximum amount (full balance minus fees for native)").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").addHelpText(
|
|
10658
|
+
"after",
|
|
10659
|
+
`
|
|
10660
|
+
Examples:
|
|
10661
|
+
vultisig swap-quote Ethereum Bitcoin 0.1
|
|
10662
|
+
vultisig swap-quote Ethereum Bitcoin --max --output json`
|
|
10663
|
+
).action(
|
|
9863
10664
|
withExit(
|
|
9864
10665
|
async (fromChainStr, toChainStr, amountStr, options) => {
|
|
9865
10666
|
if (!amountStr && !options.max) throw new Error("Provide an amount or use --max");
|
|
@@ -9875,7 +10676,16 @@ program.command("swap-quote <fromChain> <toChain> [amount]").description("Get a
|
|
|
9875
10676
|
}
|
|
9876
10677
|
)
|
|
9877
10678
|
);
|
|
9878
|
-
program.command("swap <fromChain> <toChain> [amount]").description(
|
|
10679
|
+
program.command("swap <fromChain> <toChain> [amount]").description(descriptions.swap.description).option("--max", "Swap maximum amount (full balance minus fees for native)").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("--dry-run", "Preview swap without signing or broadcasting").option("--confirm", "Confirm and broadcast (without this flag, runs as a preview)").option("-y, --yes", "Alias for --confirm").option("--password <password>", "Vault password for signing").addHelpText(
|
|
10680
|
+
"after",
|
|
10681
|
+
`
|
|
10682
|
+
Examples:
|
|
10683
|
+
vultisig swap Ethereum Bitcoin 0.1
|
|
10684
|
+
vultisig swap Ethereum Bitcoin --max --confirm
|
|
10685
|
+
vultisig swap Ethereum Bitcoin 0.5 --dry-run --output json
|
|
10686
|
+
|
|
10687
|
+
See also: swap-quote, swap-chains, balance`
|
|
10688
|
+
).action(
|
|
9879
10689
|
withExit(
|
|
9880
10690
|
async (fromChainStr, toChainStr, amountStr, options) => {
|
|
9881
10691
|
if (!amountStr && !options.max) throw new Error("Provide an amount or use --max");
|
|
@@ -9889,7 +10699,8 @@ program.command("swap <fromChain> <toChain> [amount]").description("Swap tokens
|
|
|
9889
10699
|
fromToken: options.fromToken,
|
|
9890
10700
|
toToken: options.toToken,
|
|
9891
10701
|
slippage: options.slippage ? parseFloat(options.slippage) : void 0,
|
|
9892
|
-
|
|
10702
|
+
dryRun: options.dryRun,
|
|
10703
|
+
yes: options.yes || options.confirm,
|
|
9893
10704
|
password: options.password
|
|
9894
10705
|
});
|
|
9895
10706
|
} catch (err) {
|
|
@@ -9933,7 +10744,7 @@ rujiraCmd.command("deposit").description("Show deposit instructions (inbound add
|
|
|
9933
10744
|
}
|
|
9934
10745
|
)
|
|
9935
10746
|
);
|
|
9936
|
-
rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a FIN swap (amount in base units)").option("--slippage-bps <bps>", "Slippage tolerance in basis points (default: 100 = 1%)", "100").option("--destination <thorAddress>", "Destination THOR address (default: vault THORChain address)").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
|
|
10747
|
+
rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a FIN swap (amount in base units)").option("--slippage-bps <bps>", "Slippage tolerance in basis points (default: 100 = 1%)", "100").option("--destination <thorAddress>", "Destination THOR address (default: vault THORChain address)").option("--dry-run", "Preview swap quote without executing").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
|
|
9937
10748
|
withExit(
|
|
9938
10749
|
async (fromAsset, toAsset, amount, options) => {
|
|
9939
10750
|
const context = await init(program.opts().vault, options.password);
|
|
@@ -9943,6 +10754,7 @@ rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a
|
|
|
9943
10754
|
amount,
|
|
9944
10755
|
slippageBps: options.slippageBps ? parseInt(options.slippageBps, 10) : void 0,
|
|
9945
10756
|
destination: options.destination,
|
|
10757
|
+
dryRun: options.dryRun,
|
|
9946
10758
|
yes: options.yes,
|
|
9947
10759
|
password: options.password,
|
|
9948
10760
|
rpcEndpoint: options.rpc,
|
|
@@ -9951,7 +10763,7 @@ rujiraCmd.command("swap <fromAsset> <toAsset> <amount>").description("Execute a
|
|
|
9951
10763
|
}
|
|
9952
10764
|
)
|
|
9953
10765
|
);
|
|
9954
|
-
rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw secured assets to L1 (amount in base units)").option("--max-fee-bps <bps>", "Max outbound fee as bps of amount (optional)").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
|
|
10766
|
+
rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw secured assets to L1 (amount in base units)").option("--max-fee-bps <bps>", "Max outbound fee as bps of amount (optional)").option("--dry-run", "Preview withdrawal without executing").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
|
|
9955
10767
|
withExit(
|
|
9956
10768
|
async (asset, amount, l1Address, options) => {
|
|
9957
10769
|
const context = await init(program.opts().vault, options.password);
|
|
@@ -9960,6 +10772,7 @@ rujiraCmd.command("withdraw <asset> <amount> <l1Address>").description("Withdraw
|
|
|
9960
10772
|
amount,
|
|
9961
10773
|
l1Address,
|
|
9962
10774
|
maxFeeBps: options.maxFeeBps ? parseInt(options.maxFeeBps, 10) : void 0,
|
|
10775
|
+
dryRun: options.dryRun,
|
|
9963
10776
|
yes: options.yes,
|
|
9964
10777
|
password: options.password,
|
|
9965
10778
|
rpcEndpoint: options.rpc,
|
|
@@ -9994,7 +10807,13 @@ var agentCmd = program.command("agent").description("AI-powered chat interface f
|
|
|
9994
10807
|
});
|
|
9995
10808
|
}
|
|
9996
10809
|
);
|
|
9997
|
-
agentCmd.command("ask <message>").description("Send a single message and get the response (for AI agent integration)").option("--session <id>", "Continue an existing conversation").option("--backend-url <url>", "Agent backend URL (default: https://abe.vultisig.com)").option("--password <password>", "Vault password for signing operations").option("--verbose", "Show tool calls and debug info on stderr").option("--json", "Output structured JSON
|
|
10810
|
+
agentCmd.command("ask <message>").description("Send a single message and get the response (for AI agent integration)").option("--session <id>", "Continue an existing conversation").option("--backend-url <url>", "Agent backend URL (default: https://abe.vultisig.com)").option("--password <password>", "Vault password for signing operations").option("--verbose", "Show tool calls and debug info on stderr").option("--json", "Output structured JSON (deprecated: use --output json)").addHelpText(
|
|
10811
|
+
"after",
|
|
10812
|
+
`
|
|
10813
|
+
Examples:
|
|
10814
|
+
vultisig agent ask "What is my ETH balance?" --output json
|
|
10815
|
+
vultisig agent ask "Send 0.1 ETH to 0x..." --session abc123 --yes`
|
|
10816
|
+
).action(
|
|
9998
10817
|
async (message, options) => {
|
|
9999
10818
|
const parentOpts = agentCmd.opts();
|
|
10000
10819
|
const context = await init(program.opts().vault, options.password || parentOpts.password);
|
|
@@ -10062,7 +10881,66 @@ program.command("update").description("Check for updates and show update command
|
|
|
10062
10881
|
}
|
|
10063
10882
|
})
|
|
10064
10883
|
);
|
|
10884
|
+
var authCmd = program.command("auth").description("Manage keyring-stored vault credentials");
|
|
10885
|
+
authCmd.command("setup").description("Discover .vult files, prompt for passwords, and store credentials in the OS keyring").option("--vault-file <path>", "Path to a specific .vult file").option("--non-interactive", "Fail instead of prompting (use env vars)").addHelpText(
|
|
10886
|
+
"after",
|
|
10887
|
+
`
|
|
10888
|
+
Examples:
|
|
10889
|
+
vultisig auth setup
|
|
10890
|
+
vultisig auth setup --vault-file ~/vault.vult
|
|
10891
|
+
VAULT_PASSWORD=secret VAULT_DECRYPT_PASSWORD=pass vultisig auth setup --non-interactive`
|
|
10892
|
+
).action(
|
|
10893
|
+
withExit(async (options) => {
|
|
10894
|
+
const result = await executeAuthSetup({
|
|
10895
|
+
vaultFile: options.vaultFile,
|
|
10896
|
+
nonInteractive: options.nonInteractive || isNonInteractive()
|
|
10897
|
+
});
|
|
10898
|
+
if (isJsonOutput()) {
|
|
10899
|
+
outputJson({
|
|
10900
|
+
stored: true,
|
|
10901
|
+
vaultId: result.vaultId,
|
|
10902
|
+
vaultName: result.vaultName,
|
|
10903
|
+
storageBackend: result.storageBackend
|
|
10904
|
+
});
|
|
10905
|
+
} else {
|
|
10906
|
+
printResult(
|
|
10907
|
+
chalk15.green(`Vault "${result.vaultName}" (${result.vaultId}) credentials stored in ${result.storageBackend}.`)
|
|
10908
|
+
);
|
|
10909
|
+
}
|
|
10910
|
+
})
|
|
10911
|
+
);
|
|
10912
|
+
authCmd.command("status").description("List configured vaults and their keyring credential status").action(
|
|
10913
|
+
withExit(async () => {
|
|
10914
|
+
const vaults = await executeAuthStatus();
|
|
10915
|
+
if (isJsonOutput()) {
|
|
10916
|
+
outputJson({
|
|
10917
|
+
vaults: vaults.map((v) => ({ id: v.id, name: v.name, filePath: v.filePath, hasCredentials: v.hasCredentials }))
|
|
10918
|
+
});
|
|
10919
|
+
return;
|
|
10920
|
+
}
|
|
10921
|
+
if (vaults.length === 0) {
|
|
10922
|
+
printResult("No vaults configured. Run: vsig auth setup");
|
|
10923
|
+
return;
|
|
10924
|
+
}
|
|
10925
|
+
for (const v of vaults) {
|
|
10926
|
+
const status = v.hasCredentials ? chalk15.green("authenticated") : chalk15.red("no credentials");
|
|
10927
|
+
printResult(` ${v.name} (${v.id}) - ${status}`);
|
|
10928
|
+
printResult(` File: ${v.filePath}`);
|
|
10929
|
+
}
|
|
10930
|
+
})
|
|
10931
|
+
);
|
|
10932
|
+
authCmd.command("logout").description("Clear keyring credentials for a vault").option("--vault-id <id>", "Specific vault ID to clear").option("--all", "Clear credentials for all configured vaults").action(
|
|
10933
|
+
withExit(async (options) => {
|
|
10934
|
+
await executeAuthLogout({ vaultId: options.vaultId, all: options.all });
|
|
10935
|
+
if (isJsonOutput()) {
|
|
10936
|
+
outputJson({ cleared: true, vaultId: options.vaultId ?? null, all: !!options.all });
|
|
10937
|
+
} else {
|
|
10938
|
+
printResult(chalk15.green("Credentials cleared."));
|
|
10939
|
+
}
|
|
10940
|
+
})
|
|
10941
|
+
);
|
|
10065
10942
|
setupCompletionCommand(program);
|
|
10943
|
+
program.command("schema", { hidden: true }).description("Output machine-readable command schema (JSON introspection for agents)").helpOption(false).action(withExit(async () => executeSchema(program)));
|
|
10066
10944
|
async function startInteractiveMode() {
|
|
10067
10945
|
const serverEndpoints = resolveServerEndpoints(parseServerEndpointOverridesFromArgv(process.argv.slice(2)));
|
|
10068
10946
|
const sdk = new Vultisig6({
|