@vultisig/cli 0.3.0 → 0.5.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 +92 -0
- package/README.md +112 -29
- package/dist/index.js +949 -195
- package/package.json +7 -2
package/dist/index.js
CHANGED
|
@@ -1042,14 +1042,14 @@ var require_main = __commonJS({
|
|
|
1042
1042
|
cb = opts;
|
|
1043
1043
|
opts = {};
|
|
1044
1044
|
}
|
|
1045
|
-
var
|
|
1046
|
-
|
|
1047
|
-
|
|
1045
|
+
var qrcode5 = new QRCode(-1, this.error);
|
|
1046
|
+
qrcode5.addData(input);
|
|
1047
|
+
qrcode5.make();
|
|
1048
1048
|
var output = "";
|
|
1049
1049
|
if (opts && opts.small) {
|
|
1050
1050
|
var BLACK = true, WHITE = false;
|
|
1051
|
-
var moduleCount =
|
|
1052
|
-
var moduleData =
|
|
1051
|
+
var moduleCount = qrcode5.getModuleCount();
|
|
1052
|
+
var moduleData = qrcode5.modules.slice();
|
|
1053
1053
|
var oddRow = moduleCount % 2 === 1;
|
|
1054
1054
|
if (oddRow) {
|
|
1055
1055
|
moduleData.push(fill(moduleCount, WHITE));
|
|
@@ -1082,9 +1082,9 @@ var require_main = __commonJS({
|
|
|
1082
1082
|
output += borderBottom;
|
|
1083
1083
|
}
|
|
1084
1084
|
} else {
|
|
1085
|
-
var border = repeat(white).times(
|
|
1085
|
+
var border = repeat(white).times(qrcode5.getModuleCount() + 3);
|
|
1086
1086
|
output += border + "\n";
|
|
1087
|
-
|
|
1087
|
+
qrcode5.modules.forEach(function(row2) {
|
|
1088
1088
|
output += white;
|
|
1089
1089
|
output += row2.map(toCell).join("");
|
|
1090
1090
|
output += white + "\n";
|
|
@@ -1103,10 +1103,10 @@ var require_main = __commonJS({
|
|
|
1103
1103
|
|
|
1104
1104
|
// src/index.ts
|
|
1105
1105
|
import "dotenv/config";
|
|
1106
|
-
import {
|
|
1107
|
-
import
|
|
1106
|
+
import { promises as fs3 } from "node:fs";
|
|
1107
|
+
import { parseKeygenQR, Vultisig as Vultisig5 } from "@vultisig/sdk";
|
|
1108
|
+
import chalk13 from "chalk";
|
|
1108
1109
|
import { program } from "commander";
|
|
1109
|
-
import { promises as fs3 } from "fs";
|
|
1110
1110
|
import inquirer8 from "inquirer";
|
|
1111
1111
|
|
|
1112
1112
|
// src/core/command-context.ts
|
|
@@ -1446,25 +1446,25 @@ import { fiatCurrencies, fiatCurrencyNameRecord as fiatCurrencyNameRecord2 } fro
|
|
|
1446
1446
|
import { fiatCurrencyNameRecord, Vultisig } from "@vultisig/sdk";
|
|
1447
1447
|
import chalk2 from "chalk";
|
|
1448
1448
|
import inquirer2 from "inquirer";
|
|
1449
|
-
function displayBalance(chain, balance) {
|
|
1449
|
+
function displayBalance(chain, balance, _raw = false) {
|
|
1450
1450
|
printResult(chalk2.cyan(`
|
|
1451
1451
|
${chain} Balance:`));
|
|
1452
|
-
printResult(` Amount: ${balance.
|
|
1452
|
+
printResult(` Amount: ${balance.formattedAmount} ${balance.symbol}`);
|
|
1453
1453
|
if (balance.fiatValue && balance.fiatCurrency) {
|
|
1454
1454
|
printResult(` Value: ${balance.fiatValue.toFixed(2)} ${balance.fiatCurrency}`);
|
|
1455
1455
|
}
|
|
1456
1456
|
}
|
|
1457
|
-
function displayBalancesTable(balances) {
|
|
1457
|
+
function displayBalancesTable(balances, _raw = false) {
|
|
1458
1458
|
printResult(chalk2.cyan("\nPortfolio Balances:\n"));
|
|
1459
1459
|
const tableData = Object.entries(balances).map(([chain, balance]) => ({
|
|
1460
1460
|
Chain: chain,
|
|
1461
|
-
Amount: balance.
|
|
1461
|
+
Amount: balance.formattedAmount,
|
|
1462
1462
|
Symbol: balance.symbol,
|
|
1463
1463
|
Value: balance.fiatValue && balance.fiatCurrency ? `${balance.fiatValue.toFixed(2)} ${balance.fiatCurrency}` : "N/A"
|
|
1464
1464
|
}));
|
|
1465
1465
|
printTable(tableData);
|
|
1466
1466
|
}
|
|
1467
|
-
function displayPortfolio(portfolio, currency) {
|
|
1467
|
+
function displayPortfolio(portfolio, currency, _raw = false) {
|
|
1468
1468
|
const currencyName = fiatCurrencyNameRecord[currency];
|
|
1469
1469
|
printResult(chalk2.cyan("\n+----------------------------------------+"));
|
|
1470
1470
|
printResult(chalk2.cyan(`| Portfolio Total Value (${currencyName}) |`));
|
|
@@ -1475,7 +1475,7 @@ function displayPortfolio(portfolio, currency) {
|
|
|
1475
1475
|
printResult(chalk2.bold("Chain Breakdown:\n"));
|
|
1476
1476
|
const table = portfolio.chainBalances.map(({ chain, balance, value }) => ({
|
|
1477
1477
|
Chain: chain,
|
|
1478
|
-
Amount: balance.
|
|
1478
|
+
Amount: balance.formattedAmount,
|
|
1479
1479
|
Symbol: balance.symbol,
|
|
1480
1480
|
Value: value ? `${value.amount} ${value.currency.toUpperCase()}` : "N/A"
|
|
1481
1481
|
}));
|
|
@@ -1571,7 +1571,7 @@ async function confirmTransaction() {
|
|
|
1571
1571
|
function setupVaultEvents(vault) {
|
|
1572
1572
|
vault.on("balanceUpdated", ({ chain, balance, tokenId }) => {
|
|
1573
1573
|
const asset = tokenId ? `${balance.symbol} token` : balance.symbol;
|
|
1574
|
-
info(chalk2.blue(`i Balance updated for ${chain} (${asset}): ${balance.
|
|
1574
|
+
info(chalk2.blue(`i Balance updated for ${chain} (${asset}): ${balance.formattedAmount}`));
|
|
1575
1575
|
});
|
|
1576
1576
|
vault.on("transactionBroadcast", ({ chain, txHash }) => {
|
|
1577
1577
|
info(chalk2.green(`+ Transaction broadcast on ${chain}`));
|
|
@@ -1634,6 +1634,10 @@ function displaySwapPreview(quote, fromAmount, fromSymbol, toSymbol, options) {
|
|
|
1634
1634
|
if (quote.feesFiat?.affiliate) {
|
|
1635
1635
|
printResult(` (~$${quote.feesFiat.affiliate.toFixed(2)})`);
|
|
1636
1636
|
}
|
|
1637
|
+
if (options.discountTier) {
|
|
1638
|
+
const tierDisplay = options.discountTier.charAt(0).toUpperCase() + options.discountTier.slice(1);
|
|
1639
|
+
printResult(chalk2.green(` (${tierDisplay} tier discount applied)`));
|
|
1640
|
+
}
|
|
1637
1641
|
}
|
|
1638
1642
|
printResult(` Total: ${totalFeeFormatted} ${options.feeSymbol}`);
|
|
1639
1643
|
if (quote.feesFiat) {
|
|
@@ -1688,6 +1692,7 @@ async function confirmSwap() {
|
|
|
1688
1692
|
async function executeBalance(ctx2, options = {}) {
|
|
1689
1693
|
const vault = await ctx2.ensureActiveVault();
|
|
1690
1694
|
const spinner = createSpinner("Loading balances...");
|
|
1695
|
+
const raw = options.raw ?? false;
|
|
1691
1696
|
if (options.chain) {
|
|
1692
1697
|
const balance = await vault.balance(options.chain);
|
|
1693
1698
|
spinner.succeed("Balance loaded");
|
|
@@ -1695,7 +1700,7 @@ async function executeBalance(ctx2, options = {}) {
|
|
|
1695
1700
|
outputJson({ chain: options.chain, balance });
|
|
1696
1701
|
return;
|
|
1697
1702
|
}
|
|
1698
|
-
displayBalance(options.chain, balance);
|
|
1703
|
+
displayBalance(options.chain, balance, raw);
|
|
1699
1704
|
} else {
|
|
1700
1705
|
const balances = await vault.balances(void 0, options.includeTokens);
|
|
1701
1706
|
spinner.succeed("Balances loaded");
|
|
@@ -1703,7 +1708,7 @@ async function executeBalance(ctx2, options = {}) {
|
|
|
1703
1708
|
outputJson({ balances });
|
|
1704
1709
|
return;
|
|
1705
1710
|
}
|
|
1706
|
-
displayBalancesTable(balances);
|
|
1711
|
+
displayBalancesTable(balances, raw);
|
|
1707
1712
|
}
|
|
1708
1713
|
}
|
|
1709
1714
|
async function executePortfolio(ctx2, options = {}) {
|
|
@@ -1738,13 +1743,27 @@ async function executePortfolio(ctx2, options = {}) {
|
|
|
1738
1743
|
outputJson({ portfolio, currency });
|
|
1739
1744
|
return;
|
|
1740
1745
|
}
|
|
1741
|
-
displayPortfolio(portfolio, currency);
|
|
1746
|
+
displayPortfolio(portfolio, currency, options.raw ?? false);
|
|
1742
1747
|
}
|
|
1743
1748
|
|
|
1744
1749
|
// src/commands/chains.ts
|
|
1750
|
+
import { SUPPORTED_CHAINS } from "@vultisig/sdk";
|
|
1745
1751
|
import chalk3 from "chalk";
|
|
1746
1752
|
async function executeChains(ctx2, options = {}) {
|
|
1747
1753
|
const vault = await ctx2.ensureActiveVault();
|
|
1754
|
+
if (options.addAll) {
|
|
1755
|
+
const currentCount = vault.chains.length;
|
|
1756
|
+
const spinner = createSpinner(`Adding all ${SUPPORTED_CHAINS.length} supported chains...`);
|
|
1757
|
+
await vault.setChains([...SUPPORTED_CHAINS]);
|
|
1758
|
+
const addedCount = SUPPORTED_CHAINS.length - currentCount;
|
|
1759
|
+
spinner.succeed(`Added ${addedCount} chains (${SUPPORTED_CHAINS.length} total)`);
|
|
1760
|
+
if (isJsonOutput()) {
|
|
1761
|
+
outputJson({ chains: [...vault.chains], added: addedCount, total: SUPPORTED_CHAINS.length });
|
|
1762
|
+
return;
|
|
1763
|
+
}
|
|
1764
|
+
info(chalk3.gray("\nAll supported chains are now enabled."));
|
|
1765
|
+
return;
|
|
1766
|
+
}
|
|
1748
1767
|
if (options.add) {
|
|
1749
1768
|
await vault.addChain(options.add);
|
|
1750
1769
|
success(`
|
|
@@ -1765,7 +1784,9 @@ async function executeChains(ctx2, options = {}) {
|
|
|
1765
1784
|
chains.forEach((chain) => {
|
|
1766
1785
|
printResult(` - ${chain}`);
|
|
1767
1786
|
});
|
|
1768
|
-
info(chalk3.gray(
|
|
1787
|
+
info(chalk3.gray(`
|
|
1788
|
+
${chains.length} of ${SUPPORTED_CHAINS.length} chains enabled`));
|
|
1789
|
+
info(chalk3.gray("Use --add <chain>, --add-all, or --remove <chain>"));
|
|
1769
1790
|
}
|
|
1770
1791
|
}
|
|
1771
1792
|
async function executeAddresses(ctx2) {
|
|
@@ -1893,7 +1914,8 @@ async function executeSend(ctx2, params) {
|
|
|
1893
1914
|
if (!Object.values(Chain).includes(params.chain)) {
|
|
1894
1915
|
throw new Error(`Invalid chain: ${params.chain}`);
|
|
1895
1916
|
}
|
|
1896
|
-
|
|
1917
|
+
const isMax = params.amount === "max";
|
|
1918
|
+
if (!isMax && (isNaN(parseFloat(params.amount)) || parseFloat(params.amount) <= 0)) {
|
|
1897
1919
|
throw new Error("Invalid amount");
|
|
1898
1920
|
}
|
|
1899
1921
|
return sendTransaction(vault, params);
|
|
@@ -1909,7 +1931,25 @@ async function sendTransaction(vault, params) {
|
|
|
1909
1931
|
ticker: balance.symbol,
|
|
1910
1932
|
id: params.tokenId
|
|
1911
1933
|
};
|
|
1912
|
-
const
|
|
1934
|
+
const isMax = params.amount === "max";
|
|
1935
|
+
let amount;
|
|
1936
|
+
let displayAmount;
|
|
1937
|
+
if (isMax) {
|
|
1938
|
+
const maxInfo = await vault.getMaxSendAmount({ coin, receiver: params.to, memo: params.memo });
|
|
1939
|
+
amount = maxInfo.maxSendable;
|
|
1940
|
+
if (amount === 0n) {
|
|
1941
|
+
throw new Error("Insufficient balance to cover network fees");
|
|
1942
|
+
}
|
|
1943
|
+
displayAmount = formatBigintAmount(amount, balance.decimals);
|
|
1944
|
+
} else {
|
|
1945
|
+
const [whole, frac = ""] = params.amount.split(".");
|
|
1946
|
+
if (frac.length > balance.decimals) {
|
|
1947
|
+
throw new Error(`Amount has more than ${balance.decimals} decimal places`);
|
|
1948
|
+
}
|
|
1949
|
+
const paddedFrac = frac.padEnd(balance.decimals, "0");
|
|
1950
|
+
amount = BigInt(whole || "0") * 10n ** BigInt(balance.decimals) + BigInt(paddedFrac || "0");
|
|
1951
|
+
displayAmount = params.amount;
|
|
1952
|
+
}
|
|
1913
1953
|
const payload = await vault.prepareSendTx({
|
|
1914
1954
|
coin,
|
|
1915
1955
|
receiver: params.to,
|
|
@@ -1927,7 +1967,7 @@ async function sendTransaction(vault, params) {
|
|
|
1927
1967
|
displayTransactionPreview(
|
|
1928
1968
|
payload.coin.address,
|
|
1929
1969
|
params.to,
|
|
1930
|
-
|
|
1970
|
+
displayAmount,
|
|
1931
1971
|
payload.coin.ticker,
|
|
1932
1972
|
params.chain,
|
|
1933
1973
|
params.memo,
|
|
@@ -2012,12 +2052,175 @@ Or use this URL: ${qrPayload}
|
|
|
2012
2052
|
}
|
|
2013
2053
|
}
|
|
2014
2054
|
|
|
2015
|
-
// src/commands/
|
|
2055
|
+
// src/commands/execute.ts
|
|
2016
2056
|
var import_qrcode_terminal2 = __toESM(require_main(), 1);
|
|
2017
|
-
import {
|
|
2057
|
+
import { Vultisig as Vultisig3 } from "@vultisig/sdk";
|
|
2058
|
+
var COSMOS_CHAIN_CONFIG = {
|
|
2059
|
+
THORChain: {
|
|
2060
|
+
chainId: "thorchain-1",
|
|
2061
|
+
prefix: "thor",
|
|
2062
|
+
denom: "rune",
|
|
2063
|
+
gasLimit: "500000"
|
|
2064
|
+
},
|
|
2065
|
+
MayaChain: {
|
|
2066
|
+
chainId: "mayachain-mainnet-v1",
|
|
2067
|
+
prefix: "maya",
|
|
2068
|
+
denom: "cacao",
|
|
2069
|
+
gasLimit: "500000"
|
|
2070
|
+
}
|
|
2071
|
+
};
|
|
2072
|
+
function parseFunds(fundsStr) {
|
|
2073
|
+
if (!fundsStr) return [];
|
|
2074
|
+
return fundsStr.split(",").map((fund) => {
|
|
2075
|
+
const [denom, amount] = fund.trim().split(":");
|
|
2076
|
+
if (!denom || !amount) {
|
|
2077
|
+
throw new Error(`Invalid funds format: "${fund}". Expected "denom:amount"`);
|
|
2078
|
+
}
|
|
2079
|
+
return { denom: denom.toLowerCase(), amount };
|
|
2080
|
+
});
|
|
2081
|
+
}
|
|
2082
|
+
async function executeExecute(ctx2, params) {
|
|
2083
|
+
const vault = await ctx2.ensureActiveVault();
|
|
2084
|
+
const chainConfig = COSMOS_CHAIN_CONFIG[params.chain];
|
|
2085
|
+
if (!chainConfig) {
|
|
2086
|
+
throw new Error(`Chain ${params.chain} does not support CosmWasm execute. Supported chains: ${Object.keys(COSMOS_CHAIN_CONFIG).join(", ")}`);
|
|
2087
|
+
}
|
|
2088
|
+
let msg;
|
|
2089
|
+
try {
|
|
2090
|
+
msg = JSON.parse(params.msg);
|
|
2091
|
+
} catch {
|
|
2092
|
+
throw new Error(`Invalid JSON message: ${params.msg}`);
|
|
2093
|
+
}
|
|
2094
|
+
const funds = parseFunds(params.funds);
|
|
2095
|
+
return executeContractTransaction(vault, params, chainConfig, msg, funds);
|
|
2096
|
+
}
|
|
2097
|
+
async function executeContractTransaction(vault, params, chainConfig, msg, funds) {
|
|
2098
|
+
const prepareSpinner = createSpinner("Preparing contract execution...");
|
|
2099
|
+
const address = await vault.address(params.chain);
|
|
2100
|
+
prepareSpinner.succeed("Transaction prepared");
|
|
2101
|
+
if (!isJsonOutput()) {
|
|
2102
|
+
info("\n\u{1F4DD} Contract Execution Preview");
|
|
2103
|
+
info("\u2501".repeat(50));
|
|
2104
|
+
info(`Chain: ${params.chain}`);
|
|
2105
|
+
info(`From: ${address}`);
|
|
2106
|
+
info(`Contract: ${params.contract}`);
|
|
2107
|
+
info(`Message: ${JSON.stringify(msg, null, 2).substring(0, 200)}${JSON.stringify(msg).length > 200 ? "..." : ""}`);
|
|
2108
|
+
if (funds.length > 0) {
|
|
2109
|
+
info(`Funds: ${funds.map((f) => `${f.amount} ${f.denom}`).join(", ")}`);
|
|
2110
|
+
}
|
|
2111
|
+
if (params.memo) {
|
|
2112
|
+
info(`Memo: ${params.memo}`);
|
|
2113
|
+
}
|
|
2114
|
+
info("\u2501".repeat(50));
|
|
2115
|
+
}
|
|
2116
|
+
if (!params.yes && !isJsonOutput()) {
|
|
2117
|
+
const confirmed = await confirmTransaction();
|
|
2118
|
+
if (!confirmed) {
|
|
2119
|
+
warn("Transaction cancelled");
|
|
2120
|
+
throw new Error("Transaction cancelled by user");
|
|
2121
|
+
}
|
|
2122
|
+
}
|
|
2123
|
+
await ensureVaultUnlocked(vault, params.password);
|
|
2124
|
+
const isSecureVault = vault.type === "secure";
|
|
2125
|
+
const signSpinner = createSpinner(isSecureVault ? "Preparing secure signing session..." : "Signing transaction...");
|
|
2126
|
+
vault.on("signingProgress", ({ step }) => {
|
|
2127
|
+
signSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2128
|
+
});
|
|
2129
|
+
if (isSecureVault) {
|
|
2130
|
+
vault.on("qrCodeReady", ({ qrPayload }) => {
|
|
2131
|
+
if (isJsonOutput()) {
|
|
2132
|
+
printResult(qrPayload);
|
|
2133
|
+
} else if (isSilent()) {
|
|
2134
|
+
printResult(`QR Payload: ${qrPayload}`);
|
|
2135
|
+
} else {
|
|
2136
|
+
signSpinner.stop();
|
|
2137
|
+
info("\nScan this QR code with your Vultisig mobile app to sign:");
|
|
2138
|
+
import_qrcode_terminal2.default.generate(qrPayload, { small: true });
|
|
2139
|
+
info(`
|
|
2140
|
+
Or use this URL: ${qrPayload}
|
|
2141
|
+
`);
|
|
2142
|
+
signSpinner.start("Waiting for devices to join signing session...");
|
|
2143
|
+
}
|
|
2144
|
+
});
|
|
2145
|
+
vault.on("deviceJoined", ({ deviceId, totalJoined, required }) => {
|
|
2146
|
+
if (!isSilent()) {
|
|
2147
|
+
signSpinner.text = `Device joined: ${totalJoined}/${required} (${deviceId})`;
|
|
2148
|
+
} else if (!isJsonOutput()) {
|
|
2149
|
+
printResult(`Device joined: ${totalJoined}/${required}`);
|
|
2150
|
+
}
|
|
2151
|
+
});
|
|
2152
|
+
}
|
|
2153
|
+
try {
|
|
2154
|
+
const coin = {
|
|
2155
|
+
chain: params.chain,
|
|
2156
|
+
address,
|
|
2157
|
+
decimals: 8,
|
|
2158
|
+
// THORChain uses 8 decimals
|
|
2159
|
+
ticker: chainConfig.denom.toUpperCase()
|
|
2160
|
+
};
|
|
2161
|
+
const executeContractMsg = {
|
|
2162
|
+
type: "wasm/MsgExecuteContract",
|
|
2163
|
+
value: JSON.stringify({
|
|
2164
|
+
sender: address,
|
|
2165
|
+
contract: params.contract,
|
|
2166
|
+
msg,
|
|
2167
|
+
funds: funds.map((f) => ({ denom: f.denom, amount: f.amount }))
|
|
2168
|
+
})
|
|
2169
|
+
};
|
|
2170
|
+
const fee = {
|
|
2171
|
+
amount: [{ denom: chainConfig.denom, amount: "0" }],
|
|
2172
|
+
gas: chainConfig.gasLimit
|
|
2173
|
+
};
|
|
2174
|
+
const keysignPayload = await vault.prepareSignAminoTx({
|
|
2175
|
+
chain: params.chain,
|
|
2176
|
+
coin,
|
|
2177
|
+
msgs: [executeContractMsg],
|
|
2178
|
+
fee,
|
|
2179
|
+
memo: params.memo
|
|
2180
|
+
});
|
|
2181
|
+
const messageHashes = await vault.extractMessageHashes(keysignPayload);
|
|
2182
|
+
const signature = await vault.sign(
|
|
2183
|
+
{
|
|
2184
|
+
transaction: keysignPayload,
|
|
2185
|
+
chain: params.chain,
|
|
2186
|
+
messageHashes
|
|
2187
|
+
},
|
|
2188
|
+
{ signal: params.signal }
|
|
2189
|
+
);
|
|
2190
|
+
signSpinner.succeed("Transaction signed");
|
|
2191
|
+
const broadcastSpinner = createSpinner("Broadcasting transaction...");
|
|
2192
|
+
const txHash = await vault.broadcastTx({
|
|
2193
|
+
chain: params.chain,
|
|
2194
|
+
keysignPayload,
|
|
2195
|
+
signature
|
|
2196
|
+
});
|
|
2197
|
+
broadcastSpinner.succeed(`Transaction broadcast: ${txHash}`);
|
|
2198
|
+
const result = {
|
|
2199
|
+
txHash,
|
|
2200
|
+
chain: params.chain,
|
|
2201
|
+
explorerUrl: Vultisig3.getTxExplorerUrl(params.chain, txHash)
|
|
2202
|
+
};
|
|
2203
|
+
if (isJsonOutput()) {
|
|
2204
|
+
outputJson(result);
|
|
2205
|
+
} else {
|
|
2206
|
+
displayTransactionResult(params.chain, txHash);
|
|
2207
|
+
}
|
|
2208
|
+
return result;
|
|
2209
|
+
} finally {
|
|
2210
|
+
vault.removeAllListeners("signingProgress");
|
|
2211
|
+
if (isSecureVault) {
|
|
2212
|
+
vault.removeAllListeners("qrCodeReady");
|
|
2213
|
+
vault.removeAllListeners("deviceJoined");
|
|
2214
|
+
}
|
|
2215
|
+
}
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
// src/commands/sign.ts
|
|
2219
|
+
var import_qrcode_terminal3 = __toESM(require_main(), 1);
|
|
2220
|
+
import { Chain as Chain3 } from "@vultisig/sdk";
|
|
2018
2221
|
async function executeSignBytes(ctx2, params) {
|
|
2019
2222
|
const vault = await ctx2.ensureActiveVault();
|
|
2020
|
-
if (!Object.values(
|
|
2223
|
+
if (!Object.values(Chain3).includes(params.chain)) {
|
|
2021
2224
|
throw new Error(`Invalid chain: ${params.chain}`);
|
|
2022
2225
|
}
|
|
2023
2226
|
return signBytes(vault, params);
|
|
@@ -2039,7 +2242,7 @@ async function signBytes(vault, params) {
|
|
|
2039
2242
|
} else {
|
|
2040
2243
|
signSpinner.stop();
|
|
2041
2244
|
info("\nScan this QR code with your Vultisig mobile app to sign:");
|
|
2042
|
-
|
|
2245
|
+
import_qrcode_terminal3.default.generate(qrPayload, { small: true });
|
|
2043
2246
|
info(`
|
|
2044
2247
|
Or use this URL: ${qrPayload}
|
|
2045
2248
|
`);
|
|
@@ -2093,10 +2296,10 @@ Or use this URL: ${qrPayload}
|
|
|
2093
2296
|
}
|
|
2094
2297
|
|
|
2095
2298
|
// src/commands/broadcast.ts
|
|
2096
|
-
import { Chain as
|
|
2299
|
+
import { Chain as Chain4, Vultisig as Vultisig4 } from "@vultisig/sdk";
|
|
2097
2300
|
async function executeBroadcast(ctx2, params) {
|
|
2098
2301
|
const vault = await ctx2.ensureActiveVault();
|
|
2099
|
-
if (!Object.values(
|
|
2302
|
+
if (!Object.values(Chain4).includes(params.chain)) {
|
|
2100
2303
|
throw new Error(`Invalid chain: ${params.chain}`);
|
|
2101
2304
|
}
|
|
2102
2305
|
const broadcastSpinner = createSpinner("Broadcasting transaction...");
|
|
@@ -2109,7 +2312,7 @@ async function executeBroadcast(ctx2, params) {
|
|
|
2109
2312
|
const result = {
|
|
2110
2313
|
txHash,
|
|
2111
2314
|
chain: params.chain,
|
|
2112
|
-
explorerUrl:
|
|
2315
|
+
explorerUrl: Vultisig4.getTxExplorerUrl(params.chain, txHash)
|
|
2113
2316
|
};
|
|
2114
2317
|
if (isJsonOutput()) {
|
|
2115
2318
|
outputJson(result);
|
|
@@ -2125,7 +2328,7 @@ async function executeBroadcast(ctx2, params) {
|
|
|
2125
2328
|
}
|
|
2126
2329
|
|
|
2127
2330
|
// src/commands/vault-management.ts
|
|
2128
|
-
var
|
|
2331
|
+
var import_qrcode_terminal4 = __toESM(require_main(), 1);
|
|
2129
2332
|
import chalk5 from "chalk";
|
|
2130
2333
|
import { promises as fs } from "fs";
|
|
2131
2334
|
import inquirer4 from "inquirer";
|
|
@@ -2246,7 +2449,7 @@ async function executeCreateSecure(ctx2, options) {
|
|
|
2246
2449
|
} else {
|
|
2247
2450
|
spinner.stop();
|
|
2248
2451
|
info("\nScan this QR code with your Vultisig mobile app:");
|
|
2249
|
-
|
|
2452
|
+
import_qrcode_terminal4.default.generate(qrPayload, { small: true });
|
|
2250
2453
|
info(`
|
|
2251
2454
|
Or use this URL: ${qrPayload}
|
|
2252
2455
|
`);
|
|
@@ -2508,7 +2711,7 @@ async function executeInfo(ctx2) {
|
|
|
2508
2711
|
displayVaultInfo(vault);
|
|
2509
2712
|
}
|
|
2510
2713
|
async function executeCreateFromSeedphraseFast(ctx2, options) {
|
|
2511
|
-
const { mnemonic, name, password, email, discoverChains, chains, signal } = options;
|
|
2714
|
+
const { mnemonic, name, password, email, discoverChains, chains, signal, usePhantomSolanaPath } = options;
|
|
2512
2715
|
const validateSpinner = createSpinner("Validating seedphrase...");
|
|
2513
2716
|
const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
|
|
2514
2717
|
if (!validation.valid) {
|
|
@@ -2519,19 +2722,26 @@ async function executeCreateFromSeedphraseFast(ctx2, options) {
|
|
|
2519
2722
|
throw new Error(validation.error || "Invalid mnemonic phrase");
|
|
2520
2723
|
}
|
|
2521
2724
|
validateSpinner.succeed(`Valid ${validation.wordCount}-word seedphrase`);
|
|
2725
|
+
let detectedUsePhantomSolanaPath = usePhantomSolanaPath;
|
|
2522
2726
|
if (discoverChains) {
|
|
2523
2727
|
const discoverSpinner = createSpinner("Discovering chains with balances...");
|
|
2524
2728
|
try {
|
|
2525
|
-
const discovered = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2729
|
+
const { results: discovered, usePhantomSolanaPath: detectedPhantomPath } = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2526
2730
|
discoverSpinner.text = `Discovering: ${p.chain || "scanning"} (${p.chainsProcessed}/${p.chainsTotal})`;
|
|
2527
2731
|
});
|
|
2528
2732
|
const chainsWithBalance = discovered.filter((c) => c.hasBalance);
|
|
2529
2733
|
discoverSpinner.succeed(`Found ${chainsWithBalance.length} chains with balances`);
|
|
2734
|
+
if (usePhantomSolanaPath === void 0) {
|
|
2735
|
+
detectedUsePhantomSolanaPath = detectedPhantomPath;
|
|
2736
|
+
}
|
|
2530
2737
|
if (chainsWithBalance.length > 0 && !isSilent()) {
|
|
2531
2738
|
info("\nChains with balances:");
|
|
2532
2739
|
for (const result of chainsWithBalance) {
|
|
2533
2740
|
info(` ${result.chain}: ${result.balance} ${result.symbol}`);
|
|
2534
2741
|
}
|
|
2742
|
+
if (detectedUsePhantomSolanaPath) {
|
|
2743
|
+
info(" (Using Phantom wallet derivation path for Solana)");
|
|
2744
|
+
}
|
|
2535
2745
|
info("");
|
|
2536
2746
|
}
|
|
2537
2747
|
} catch {
|
|
@@ -2547,6 +2757,7 @@ async function executeCreateFromSeedphraseFast(ctx2, options) {
|
|
|
2547
2757
|
email,
|
|
2548
2758
|
// Don't pass discoverChains - CLI handles discovery above
|
|
2549
2759
|
chains,
|
|
2760
|
+
usePhantomSolanaPath: detectedUsePhantomSolanaPath,
|
|
2550
2761
|
onProgress: (step) => {
|
|
2551
2762
|
importSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2552
2763
|
}
|
|
@@ -2625,7 +2836,17 @@ async function executeCreateFromSeedphraseFast(ctx2, options) {
|
|
|
2625
2836
|
throw new Error("Verification loop exited unexpectedly");
|
|
2626
2837
|
}
|
|
2627
2838
|
async function executeCreateFromSeedphraseSecure(ctx2, options) {
|
|
2628
|
-
const {
|
|
2839
|
+
const {
|
|
2840
|
+
mnemonic,
|
|
2841
|
+
name,
|
|
2842
|
+
password,
|
|
2843
|
+
threshold,
|
|
2844
|
+
shares: totalShares,
|
|
2845
|
+
discoverChains,
|
|
2846
|
+
chains,
|
|
2847
|
+
signal,
|
|
2848
|
+
usePhantomSolanaPath
|
|
2849
|
+
} = options;
|
|
2629
2850
|
const validateSpinner = createSpinner("Validating seedphrase...");
|
|
2630
2851
|
const validation = await ctx2.sdk.validateSeedphrase(mnemonic);
|
|
2631
2852
|
if (!validation.valid) {
|
|
@@ -2636,19 +2857,26 @@ async function executeCreateFromSeedphraseSecure(ctx2, options) {
|
|
|
2636
2857
|
throw new Error(validation.error || "Invalid mnemonic phrase");
|
|
2637
2858
|
}
|
|
2638
2859
|
validateSpinner.succeed(`Valid ${validation.wordCount}-word seedphrase`);
|
|
2860
|
+
let detectedUsePhantomSolanaPath = usePhantomSolanaPath;
|
|
2639
2861
|
if (discoverChains) {
|
|
2640
2862
|
const discoverSpinner = createSpinner("Discovering chains with balances...");
|
|
2641
2863
|
try {
|
|
2642
|
-
const discovered = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2864
|
+
const { results: discovered, usePhantomSolanaPath: detectedPhantomPath } = await ctx2.sdk.discoverChainsFromSeedphrase(mnemonic, chains, (p) => {
|
|
2643
2865
|
discoverSpinner.text = `Discovering: ${p.chain || "scanning"} (${p.chainsProcessed}/${p.chainsTotal})`;
|
|
2644
2866
|
});
|
|
2645
2867
|
const chainsWithBalance = discovered.filter((c) => c.hasBalance);
|
|
2646
2868
|
discoverSpinner.succeed(`Found ${chainsWithBalance.length} chains with balances`);
|
|
2869
|
+
if (usePhantomSolanaPath === void 0) {
|
|
2870
|
+
detectedUsePhantomSolanaPath = detectedPhantomPath;
|
|
2871
|
+
}
|
|
2647
2872
|
if (chainsWithBalance.length > 0 && !isSilent()) {
|
|
2648
2873
|
info("\nChains with balances:");
|
|
2649
2874
|
for (const result of chainsWithBalance) {
|
|
2650
2875
|
info(` ${result.chain}: ${result.balance} ${result.symbol}`);
|
|
2651
2876
|
}
|
|
2877
|
+
if (detectedUsePhantomSolanaPath) {
|
|
2878
|
+
info(" (Using Phantom wallet derivation path for Solana)");
|
|
2879
|
+
}
|
|
2652
2880
|
info("");
|
|
2653
2881
|
}
|
|
2654
2882
|
} catch {
|
|
@@ -2666,6 +2894,7 @@ async function executeCreateFromSeedphraseSecure(ctx2, options) {
|
|
|
2666
2894
|
threshold,
|
|
2667
2895
|
// Don't pass discoverChains - CLI handles discovery above
|
|
2668
2896
|
chains,
|
|
2897
|
+
usePhantomSolanaPath: detectedUsePhantomSolanaPath,
|
|
2669
2898
|
onProgress: (step) => {
|
|
2670
2899
|
importSpinner.text = `${step.message} (${step.progress}%)`;
|
|
2671
2900
|
},
|
|
@@ -2677,7 +2906,7 @@ async function executeCreateFromSeedphraseSecure(ctx2, options) {
|
|
|
2677
2906
|
} else {
|
|
2678
2907
|
importSpinner.stop();
|
|
2679
2908
|
info("\nScan this QR code with your Vultisig mobile app:");
|
|
2680
|
-
|
|
2909
|
+
import_qrcode_terminal4.default.generate(qrPayload, { small: true });
|
|
2681
2910
|
info(`
|
|
2682
2911
|
Or use this URL: ${qrPayload}
|
|
2683
2912
|
`);
|
|
@@ -2770,6 +2999,82 @@ async function executeJoinSecure(ctx2, options) {
|
|
|
2770
2999
|
throw err;
|
|
2771
3000
|
}
|
|
2772
3001
|
}
|
|
3002
|
+
async function executeDelete(ctx2, options = {}) {
|
|
3003
|
+
let vault;
|
|
3004
|
+
if (options.vaultId) {
|
|
3005
|
+
const vaults = await ctx2.sdk.listVaults();
|
|
3006
|
+
vault = findVaultByIdOrName({ vaults, idOrName: options.vaultId });
|
|
3007
|
+
} else {
|
|
3008
|
+
vault = await ctx2.ensureActiveVault();
|
|
3009
|
+
}
|
|
3010
|
+
if (isJsonOutput()) {
|
|
3011
|
+
const spinner2 = createSpinner("Deleting vault...");
|
|
3012
|
+
await ctx2.sdk.deleteVault(vault);
|
|
3013
|
+
spinner2.succeed("Vault deleted");
|
|
3014
|
+
outputJson({
|
|
3015
|
+
deleted: true,
|
|
3016
|
+
vault: {
|
|
3017
|
+
id: vault.id,
|
|
3018
|
+
name: vault.name,
|
|
3019
|
+
type: vault.type
|
|
3020
|
+
}
|
|
3021
|
+
});
|
|
3022
|
+
return;
|
|
3023
|
+
}
|
|
3024
|
+
info("\n" + chalk5.bold("Vault to delete:"));
|
|
3025
|
+
info(` Name: ${chalk5.cyan(vault.name)}`);
|
|
3026
|
+
info(` Type: ${chalk5.yellow(vault.type)}`);
|
|
3027
|
+
info(` ID: ${vault.id.slice(0, 16)}...`);
|
|
3028
|
+
info(` Chains: ${vault.chains.length}`);
|
|
3029
|
+
info("");
|
|
3030
|
+
if (!options.skipConfirmation) {
|
|
3031
|
+
warn(chalk5.red.bold("WARNING: This action cannot be undone!"));
|
|
3032
|
+
warn("Make sure you have a backup of your vault (.vult file) before proceeding.");
|
|
3033
|
+
info("");
|
|
3034
|
+
const { confirmed } = await inquirer4.prompt([
|
|
3035
|
+
{
|
|
3036
|
+
type: "confirm",
|
|
3037
|
+
name: "confirmed",
|
|
3038
|
+
message: `Are you sure you want to delete vault "${vault.name}"?`,
|
|
3039
|
+
default: false
|
|
3040
|
+
}
|
|
3041
|
+
]);
|
|
3042
|
+
if (!confirmed) {
|
|
3043
|
+
info("Deletion cancelled.");
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
}
|
|
3047
|
+
const spinner = createSpinner("Deleting vault...");
|
|
3048
|
+
await ctx2.sdk.deleteVault(vault);
|
|
3049
|
+
spinner.succeed(`Vault deleted: ${vault.name}`);
|
|
3050
|
+
success("\n+ Vault deleted successfully");
|
|
3051
|
+
info(chalk5.gray('\nTip: Run "vultisig vaults" to see remaining vaults'));
|
|
3052
|
+
}
|
|
3053
|
+
function findVaultByIdOrName({ vaults, idOrName }) {
|
|
3054
|
+
const byId = vaults.find((v) => v.id === idOrName);
|
|
3055
|
+
if (byId) return byId;
|
|
3056
|
+
const byName = vaults.filter((v) => v.name.toLowerCase() === idOrName.toLowerCase());
|
|
3057
|
+
if (byName.length === 1) return byName[0];
|
|
3058
|
+
if (byName.length > 1) {
|
|
3059
|
+
const matches = byName.map((v) => ` - ${v.name} (${v.id.slice(0, 8)}...)`).join("\n");
|
|
3060
|
+
throw new Error(
|
|
3061
|
+
`Multiple vaults match "${idOrName}":
|
|
3062
|
+
${matches}
|
|
3063
|
+
Please specify the full vault ID to be more specific.`
|
|
3064
|
+
);
|
|
3065
|
+
}
|
|
3066
|
+
const byPartialId = vaults.filter((v) => v.id.startsWith(idOrName));
|
|
3067
|
+
if (byPartialId.length === 1) return byPartialId[0];
|
|
3068
|
+
if (byPartialId.length > 1) {
|
|
3069
|
+
const matches = byPartialId.map((v) => ` - ${v.name} (${v.id.slice(0, 8)}...)`).join("\n");
|
|
3070
|
+
throw new Error(
|
|
3071
|
+
`Multiple vaults match prefix "${idOrName}":
|
|
3072
|
+
${matches}
|
|
3073
|
+
Please specify more characters of the vault ID.`
|
|
3074
|
+
);
|
|
3075
|
+
}
|
|
3076
|
+
throw new Error(`Vault not found: "${idOrName}"`);
|
|
3077
|
+
}
|
|
2773
3078
|
|
|
2774
3079
|
// src/commands/swap.ts
|
|
2775
3080
|
async function executeSwapChains(ctx2) {
|
|
@@ -2786,66 +3091,95 @@ async function executeSwapChains(ctx2) {
|
|
|
2786
3091
|
}
|
|
2787
3092
|
async function executeSwapQuote(ctx2, options) {
|
|
2788
3093
|
const vault = await ctx2.ensureActiveVault();
|
|
2789
|
-
|
|
3094
|
+
const isMax = options.amount === "max";
|
|
3095
|
+
if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
|
|
2790
3096
|
throw new Error("Invalid amount");
|
|
2791
3097
|
}
|
|
2792
3098
|
const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
|
|
2793
3099
|
if (!isSupported) {
|
|
2794
3100
|
throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
|
|
2795
3101
|
}
|
|
3102
|
+
let resolvedAmount;
|
|
3103
|
+
if (isMax) {
|
|
3104
|
+
const bal = await vault.balance(options.fromChain, options.fromToken);
|
|
3105
|
+
resolvedAmount = parseFloat(bal.formattedAmount);
|
|
3106
|
+
if (resolvedAmount <= 0) {
|
|
3107
|
+
throw new Error("Zero balance \u2014 nothing to swap");
|
|
3108
|
+
}
|
|
3109
|
+
} else {
|
|
3110
|
+
resolvedAmount = options.amount;
|
|
3111
|
+
}
|
|
2796
3112
|
const spinner = createSpinner("Getting swap quote...");
|
|
2797
3113
|
const quote = await vault.getSwapQuote({
|
|
2798
3114
|
fromCoin: { chain: options.fromChain, token: options.fromToken },
|
|
2799
3115
|
toCoin: { chain: options.toChain, token: options.toToken },
|
|
2800
|
-
amount:
|
|
3116
|
+
amount: resolvedAmount,
|
|
2801
3117
|
fiatCurrency: "usd"
|
|
2802
3118
|
// Request fiat conversion
|
|
2803
3119
|
});
|
|
2804
3120
|
spinner.succeed("Quote received");
|
|
3121
|
+
const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(resolvedAmount);
|
|
2805
3122
|
if (isJsonOutput()) {
|
|
2806
3123
|
outputJson({
|
|
2807
3124
|
fromChain: options.fromChain,
|
|
2808
3125
|
toChain: options.toChain,
|
|
2809
|
-
amount:
|
|
3126
|
+
amount: resolvedAmount,
|
|
3127
|
+
isMax,
|
|
2810
3128
|
quote
|
|
2811
3129
|
});
|
|
2812
3130
|
return quote;
|
|
2813
3131
|
}
|
|
2814
3132
|
const feeBalance = await vault.balance(options.fromChain);
|
|
2815
|
-
|
|
3133
|
+
const discountTier = await vault.getDiscountTier();
|
|
3134
|
+
displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
2816
3135
|
fromDecimals: quote.fromCoin.decimals,
|
|
2817
3136
|
toDecimals: quote.toCoin.decimals,
|
|
2818
3137
|
feeDecimals: feeBalance.decimals,
|
|
2819
|
-
feeSymbol: feeBalance.symbol
|
|
3138
|
+
feeSymbol: feeBalance.symbol,
|
|
3139
|
+
discountTier
|
|
2820
3140
|
});
|
|
2821
3141
|
info('\nTo execute this swap, use the "swap" command');
|
|
2822
3142
|
return quote;
|
|
2823
3143
|
}
|
|
2824
3144
|
async function executeSwap(ctx2, options) {
|
|
2825
3145
|
const vault = await ctx2.ensureActiveVault();
|
|
2826
|
-
|
|
3146
|
+
const isMax = options.amount === "max";
|
|
3147
|
+
if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
|
|
2827
3148
|
throw new Error("Invalid amount");
|
|
2828
3149
|
}
|
|
2829
3150
|
const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
|
|
2830
3151
|
if (!isSupported) {
|
|
2831
3152
|
throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
|
|
2832
3153
|
}
|
|
3154
|
+
let resolvedAmount;
|
|
3155
|
+
if (isMax) {
|
|
3156
|
+
const bal = await vault.balance(options.fromChain, options.fromToken);
|
|
3157
|
+
resolvedAmount = parseFloat(bal.formattedAmount);
|
|
3158
|
+
if (resolvedAmount <= 0) {
|
|
3159
|
+
throw new Error("Zero balance \u2014 nothing to swap");
|
|
3160
|
+
}
|
|
3161
|
+
} else {
|
|
3162
|
+
resolvedAmount = options.amount;
|
|
3163
|
+
}
|
|
2833
3164
|
const quoteSpinner = createSpinner("Getting swap quote...");
|
|
2834
3165
|
const quote = await vault.getSwapQuote({
|
|
2835
3166
|
fromCoin: { chain: options.fromChain, token: options.fromToken },
|
|
2836
3167
|
toCoin: { chain: options.toChain, token: options.toToken },
|
|
2837
|
-
amount:
|
|
3168
|
+
amount: resolvedAmount,
|
|
2838
3169
|
fiatCurrency: "usd"
|
|
2839
3170
|
// Request fiat conversion
|
|
2840
3171
|
});
|
|
2841
3172
|
quoteSpinner.succeed("Quote received");
|
|
3173
|
+
const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(resolvedAmount);
|
|
2842
3174
|
const feeBalance = await vault.balance(options.fromChain);
|
|
3175
|
+
const discountTier = await vault.getDiscountTier();
|
|
2843
3176
|
if (!isJsonOutput()) {
|
|
2844
|
-
displaySwapPreview(quote,
|
|
3177
|
+
displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
2845
3178
|
fromDecimals: quote.fromCoin.decimals,
|
|
2846
3179
|
toDecimals: quote.toCoin.decimals,
|
|
2847
3180
|
feeDecimals: feeBalance.decimals,
|
|
2848
|
-
feeSymbol: feeBalance.symbol
|
|
3181
|
+
feeSymbol: feeBalance.symbol,
|
|
3182
|
+
discountTier
|
|
2849
3183
|
});
|
|
2850
3184
|
}
|
|
2851
3185
|
if (!options.yes && !isJsonOutput()) {
|
|
@@ -2859,7 +3193,7 @@ async function executeSwap(ctx2, options) {
|
|
|
2859
3193
|
const { keysignPayload, approvalPayload } = await vault.prepareSwapTx({
|
|
2860
3194
|
fromCoin: { chain: options.fromChain, token: options.fromToken },
|
|
2861
3195
|
toCoin: { chain: options.toChain, token: options.toToken },
|
|
2862
|
-
amount:
|
|
3196
|
+
amount: resolvedAmount,
|
|
2863
3197
|
swapQuote: quote,
|
|
2864
3198
|
autoApprove: false
|
|
2865
3199
|
});
|
|
@@ -2934,7 +3268,7 @@ async function executeSwap(ctx2, options) {
|
|
|
2934
3268
|
}
|
|
2935
3269
|
|
|
2936
3270
|
// src/commands/settings.ts
|
|
2937
|
-
import { Chain as
|
|
3271
|
+
import { Chain as Chain5, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
|
|
2938
3272
|
import chalk6 from "chalk";
|
|
2939
3273
|
import inquirer5 from "inquirer";
|
|
2940
3274
|
async function executeCurrency(ctx2, newCurrency) {
|
|
@@ -3002,7 +3336,7 @@ async function executeAddressBook(ctx2, options = {}) {
|
|
|
3002
3336
|
type: "list",
|
|
3003
3337
|
name: "chain",
|
|
3004
3338
|
message: "Select chain:",
|
|
3005
|
-
choices: Object.values(
|
|
3339
|
+
choices: Object.values(Chain5)
|
|
3006
3340
|
});
|
|
3007
3341
|
}
|
|
3008
3342
|
if (!address) {
|
|
@@ -3078,8 +3412,292 @@ Address Book${options.chain ? ` (${options.chain})` : ""}:
|
|
|
3078
3412
|
return allEntries;
|
|
3079
3413
|
}
|
|
3080
3414
|
|
|
3415
|
+
// src/commands/rujira.ts
|
|
3416
|
+
import { getRoutesSummary, listEasyRoutes, RujiraClient, VultisigRujiraProvider } from "@vultisig/rujira";
|
|
3417
|
+
async function createRujiraClient(ctx2, options = {}) {
|
|
3418
|
+
const vault = await ctx2.ensureActiveVault();
|
|
3419
|
+
const provider = new VultisigRujiraProvider(vault);
|
|
3420
|
+
const client = new RujiraClient({
|
|
3421
|
+
signer: provider,
|
|
3422
|
+
rpcEndpoint: options.rpcEndpoint,
|
|
3423
|
+
config: {
|
|
3424
|
+
// Allow overriding rest endpoint via config (used for thornode calls)
|
|
3425
|
+
...options.restEndpoint ? { restEndpoint: options.restEndpoint } : {}
|
|
3426
|
+
}
|
|
3427
|
+
});
|
|
3428
|
+
const spinner = createSpinner("Connecting to Rujira/THORChain...");
|
|
3429
|
+
await client.connect();
|
|
3430
|
+
spinner.succeed("Connected");
|
|
3431
|
+
return client;
|
|
3432
|
+
}
|
|
3433
|
+
async function executeRujiraBalance(ctx2, options = {}) {
|
|
3434
|
+
const vault = await ctx2.ensureActiveVault();
|
|
3435
|
+
const thorAddress = await vault.address("THORChain");
|
|
3436
|
+
const client = await createRujiraClient(ctx2, options);
|
|
3437
|
+
const spinner = createSpinner("Loading THORChain balances...");
|
|
3438
|
+
const balances = await client.deposit.getBalances(thorAddress);
|
|
3439
|
+
spinner.succeed("Balances loaded");
|
|
3440
|
+
const filtered = options.securedOnly ? balances.filter((b) => b.denom.includes("-") || b.denom.includes("/")) : balances;
|
|
3441
|
+
if (isJsonOutput()) {
|
|
3442
|
+
outputJson({ thorAddress, balances: filtered });
|
|
3443
|
+
return;
|
|
3444
|
+
}
|
|
3445
|
+
info(`THORChain address: ${thorAddress}`);
|
|
3446
|
+
if (!filtered.length) {
|
|
3447
|
+
printResult("No balances found");
|
|
3448
|
+
return;
|
|
3449
|
+
}
|
|
3450
|
+
printTable(
|
|
3451
|
+
filtered.map((b) => ({
|
|
3452
|
+
asset: b.asset,
|
|
3453
|
+
denom: b.denom,
|
|
3454
|
+
amount: b.formatted,
|
|
3455
|
+
raw: b.amount
|
|
3456
|
+
}))
|
|
3457
|
+
);
|
|
3458
|
+
}
|
|
3459
|
+
async function executeRujiraRoutes() {
|
|
3460
|
+
const routes = listEasyRoutes();
|
|
3461
|
+
const summary = getRoutesSummary();
|
|
3462
|
+
if (isJsonOutput()) {
|
|
3463
|
+
outputJson({ routes, summary });
|
|
3464
|
+
return;
|
|
3465
|
+
}
|
|
3466
|
+
printResult(summary);
|
|
3467
|
+
printResult("");
|
|
3468
|
+
printTable(
|
|
3469
|
+
routes.map((r) => ({
|
|
3470
|
+
name: r.name,
|
|
3471
|
+
from: r.from,
|
|
3472
|
+
to: r.to,
|
|
3473
|
+
liquidity: r.liquidity,
|
|
3474
|
+
description: r.description
|
|
3475
|
+
}))
|
|
3476
|
+
);
|
|
3477
|
+
}
|
|
3478
|
+
async function executeRujiraDeposit(ctx2, options = {}) {
|
|
3479
|
+
const vault = await ctx2.ensureActiveVault();
|
|
3480
|
+
const thorAddress = await vault.address("THORChain");
|
|
3481
|
+
const client = await createRujiraClient(ctx2, options);
|
|
3482
|
+
if (!options.asset) {
|
|
3483
|
+
const spinner2 = createSpinner("Loading THORChain inbound addresses...");
|
|
3484
|
+
const inbound = await client.deposit.getInboundAddresses();
|
|
3485
|
+
spinner2.succeed("Inbound addresses loaded");
|
|
3486
|
+
if (isJsonOutput()) {
|
|
3487
|
+
outputJson({ thorAddress, inboundAddresses: inbound });
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
info(`THORChain address: ${thorAddress}`);
|
|
3491
|
+
printResult("Provide an L1 asset to get a chain-specific inbound address + memo.");
|
|
3492
|
+
printResult("Example: vultisig rujira deposit --asset BTC.BTC --amount 100000");
|
|
3493
|
+
printResult("");
|
|
3494
|
+
printTable(
|
|
3495
|
+
inbound.map((a) => ({
|
|
3496
|
+
chain: a.chain,
|
|
3497
|
+
address: a.address,
|
|
3498
|
+
halted: a.halted,
|
|
3499
|
+
globalTradingPaused: a.global_trading_paused,
|
|
3500
|
+
chainTradingPaused: a.chain_trading_paused
|
|
3501
|
+
}))
|
|
3502
|
+
);
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
const amount = options.amount ?? "1";
|
|
3506
|
+
const spinner = createSpinner("Preparing deposit instructions...");
|
|
3507
|
+
const prepared = await client.deposit.prepare({
|
|
3508
|
+
fromAsset: options.asset,
|
|
3509
|
+
amount,
|
|
3510
|
+
thorAddress,
|
|
3511
|
+
affiliate: options.affiliate,
|
|
3512
|
+
affiliateBps: options.affiliateBps
|
|
3513
|
+
});
|
|
3514
|
+
spinner.succeed("Deposit prepared");
|
|
3515
|
+
if (isJsonOutput()) {
|
|
3516
|
+
outputJson({ thorAddress, deposit: prepared });
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
info(`THORChain address: ${thorAddress}`);
|
|
3520
|
+
printResult("Deposit instructions (send from L1):");
|
|
3521
|
+
printResult(` Chain: ${prepared.chain}`);
|
|
3522
|
+
printResult(` Asset: ${prepared.asset}`);
|
|
3523
|
+
printResult(` Inbound address:${prepared.inboundAddress}`);
|
|
3524
|
+
printResult(` Memo: ${prepared.memo}`);
|
|
3525
|
+
printResult(` Min amount: ${prepared.minimumAmount}`);
|
|
3526
|
+
if (prepared.warning) {
|
|
3527
|
+
warn(prepared.warning);
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
async function executeRujiraSwap(ctx2, options) {
|
|
3531
|
+
const vault = await ctx2.ensureActiveVault();
|
|
3532
|
+
await ensureVaultUnlocked(vault, options.password);
|
|
3533
|
+
const client = await createRujiraClient(ctx2, options);
|
|
3534
|
+
const destination = options.destination ?? await vault.address("THORChain");
|
|
3535
|
+
const quoteSpinner = createSpinner("Getting FIN swap quote...");
|
|
3536
|
+
const quote = await client.swap.getQuote({
|
|
3537
|
+
fromAsset: options.fromAsset,
|
|
3538
|
+
toAsset: options.toAsset,
|
|
3539
|
+
amount: options.amount,
|
|
3540
|
+
destination,
|
|
3541
|
+
slippageBps: options.slippageBps
|
|
3542
|
+
});
|
|
3543
|
+
quoteSpinner.succeed("Quote received");
|
|
3544
|
+
if (isJsonOutput()) {
|
|
3545
|
+
const result2 = await client.swap.execute(quote, { slippageBps: options.slippageBps });
|
|
3546
|
+
outputJson({ quote, result: result2 });
|
|
3547
|
+
return;
|
|
3548
|
+
}
|
|
3549
|
+
printResult("FIN Swap Preview");
|
|
3550
|
+
printResult(` From: ${options.fromAsset}`);
|
|
3551
|
+
printResult(` To: ${options.toAsset}`);
|
|
3552
|
+
printResult(` Amount (in): ${options.amount}`);
|
|
3553
|
+
printResult(` Expected out:${quote.expectedOutput}`);
|
|
3554
|
+
printResult(` Min out: ${quote.minimumOutput}`);
|
|
3555
|
+
printResult(` Contract: ${quote.contractAddress}`);
|
|
3556
|
+
if (quote.warning) {
|
|
3557
|
+
warn(quote.warning);
|
|
3558
|
+
}
|
|
3559
|
+
if (!options.yes) {
|
|
3560
|
+
warn("This command will execute a swap. Re-run with -y/--yes to skip this warning.");
|
|
3561
|
+
throw new Error("Confirmation required (use --yes)");
|
|
3562
|
+
}
|
|
3563
|
+
const execSpinner = createSpinner("Executing FIN swap...");
|
|
3564
|
+
const result = await client.swap.execute(quote, { slippageBps: options.slippageBps });
|
|
3565
|
+
execSpinner.succeed("Swap submitted");
|
|
3566
|
+
printResult(`Tx Hash: ${result.txHash}`);
|
|
3567
|
+
}
|
|
3568
|
+
async function executeRujiraWithdraw(ctx2, options) {
|
|
3569
|
+
const vault = await ctx2.ensureActiveVault();
|
|
3570
|
+
await ensureVaultUnlocked(vault, options.password);
|
|
3571
|
+
const client = await createRujiraClient(ctx2, options);
|
|
3572
|
+
const prepSpinner = createSpinner("Preparing withdrawal (MsgDeposit)...");
|
|
3573
|
+
const prepared = await client.withdraw.prepare({
|
|
3574
|
+
asset: options.asset,
|
|
3575
|
+
amount: options.amount,
|
|
3576
|
+
l1Address: options.l1Address,
|
|
3577
|
+
maxFeeBps: options.maxFeeBps
|
|
3578
|
+
});
|
|
3579
|
+
prepSpinner.succeed("Withdrawal prepared");
|
|
3580
|
+
if (isJsonOutput()) {
|
|
3581
|
+
const result2 = await client.withdraw.execute(prepared);
|
|
3582
|
+
outputJson({ prepared, result: result2 });
|
|
3583
|
+
return;
|
|
3584
|
+
}
|
|
3585
|
+
printResult("Withdraw Preview");
|
|
3586
|
+
printResult(` Asset: ${prepared.asset}`);
|
|
3587
|
+
printResult(` Amount: ${prepared.amount}`);
|
|
3588
|
+
printResult(` Destination: ${prepared.destination}`);
|
|
3589
|
+
printResult(` Memo: ${prepared.memo}`);
|
|
3590
|
+
printResult(` Est. fee: ${prepared.estimatedFee}`);
|
|
3591
|
+
if (!options.yes) {
|
|
3592
|
+
warn("This command will broadcast a THORChain MsgDeposit withdrawal. Re-run with -y/--yes to proceed.");
|
|
3593
|
+
throw new Error("Confirmation required (use --yes)");
|
|
3594
|
+
}
|
|
3595
|
+
const execSpinner = createSpinner("Broadcasting withdrawal...");
|
|
3596
|
+
const result = await client.withdraw.execute(prepared);
|
|
3597
|
+
execSpinner.succeed("Withdrawal submitted");
|
|
3598
|
+
printResult(`Tx Hash: ${result.txHash}`);
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
// src/commands/discount.ts
|
|
3602
|
+
import {
|
|
3603
|
+
baseAffiliateBps,
|
|
3604
|
+
vultDiscountTierBps,
|
|
3605
|
+
vultDiscountTierMinBalances
|
|
3606
|
+
} from "@vultisig/sdk";
|
|
3607
|
+
import chalk7 from "chalk";
|
|
3608
|
+
var TIER_CONFIG = {
|
|
3609
|
+
none: { bps: baseAffiliateBps, discount: 0 },
|
|
3610
|
+
...Object.fromEntries(
|
|
3611
|
+
Object.entries(vultDiscountTierMinBalances).map(([tier, minVult]) => [
|
|
3612
|
+
tier,
|
|
3613
|
+
{
|
|
3614
|
+
bps: baseAffiliateBps - vultDiscountTierBps[tier],
|
|
3615
|
+
discount: vultDiscountTierBps[tier],
|
|
3616
|
+
minVult
|
|
3617
|
+
}
|
|
3618
|
+
])
|
|
3619
|
+
)
|
|
3620
|
+
};
|
|
3621
|
+
function getTierColor(tier) {
|
|
3622
|
+
const colors = {
|
|
3623
|
+
none: chalk7.gray,
|
|
3624
|
+
bronze: chalk7.hex("#CD7F32"),
|
|
3625
|
+
silver: chalk7.hex("#C0C0C0"),
|
|
3626
|
+
gold: chalk7.hex("#FFD700"),
|
|
3627
|
+
platinum: chalk7.hex("#E5E4E2"),
|
|
3628
|
+
diamond: chalk7.hex("#B9F2FF"),
|
|
3629
|
+
ultimate: chalk7.hex("#FF00FF")
|
|
3630
|
+
};
|
|
3631
|
+
return colors[tier] || chalk7.white;
|
|
3632
|
+
}
|
|
3633
|
+
function getNextTier(currentTier) {
|
|
3634
|
+
const tierOrder = ["none", "bronze", "silver", "gold", "platinum", "diamond", "ultimate"];
|
|
3635
|
+
const currentIndex = tierOrder.indexOf(currentTier);
|
|
3636
|
+
if (currentIndex === -1 || currentIndex >= tierOrder.length - 1) {
|
|
3637
|
+
return null;
|
|
3638
|
+
}
|
|
3639
|
+
const nextTierName = tierOrder[currentIndex + 1];
|
|
3640
|
+
const config = TIER_CONFIG[nextTierName];
|
|
3641
|
+
if ("minVult" in config) {
|
|
3642
|
+
return { name: nextTierName, vultRequired: config.minVult };
|
|
3643
|
+
}
|
|
3644
|
+
return null;
|
|
3645
|
+
}
|
|
3646
|
+
async function executeDiscount(ctx2, options = {}) {
|
|
3647
|
+
const vault = await ctx2.ensureActiveVault();
|
|
3648
|
+
const spinner = createSpinner(options.refresh ? "Refreshing discount tier..." : "Loading discount tier...");
|
|
3649
|
+
const tierResult = options.refresh ? await vault.updateDiscountTier() : await vault.getDiscountTier();
|
|
3650
|
+
const tier = tierResult || "none";
|
|
3651
|
+
const config = TIER_CONFIG[tier];
|
|
3652
|
+
const nextTier = getNextTier(tier);
|
|
3653
|
+
const tierInfo = {
|
|
3654
|
+
tier,
|
|
3655
|
+
feeBps: config.bps,
|
|
3656
|
+
discountBps: config.discount,
|
|
3657
|
+
nextTier
|
|
3658
|
+
};
|
|
3659
|
+
spinner.succeed("Discount tier loaded");
|
|
3660
|
+
if (isJsonOutput()) {
|
|
3661
|
+
outputJson({
|
|
3662
|
+
tier: tierInfo.tier,
|
|
3663
|
+
feeBps: tierInfo.feeBps,
|
|
3664
|
+
discountBps: tierInfo.discountBps,
|
|
3665
|
+
nextTier: tierInfo.nextTier
|
|
3666
|
+
});
|
|
3667
|
+
return tierInfo;
|
|
3668
|
+
}
|
|
3669
|
+
displayDiscountTier(tierInfo);
|
|
3670
|
+
return tierInfo;
|
|
3671
|
+
}
|
|
3672
|
+
function displayDiscountTier(tierInfo) {
|
|
3673
|
+
const tierColor = getTierColor(tierInfo.tier);
|
|
3674
|
+
printResult(chalk7.cyan("\n+----------------------------------------+"));
|
|
3675
|
+
printResult(chalk7.cyan("| VULT Discount Tier |"));
|
|
3676
|
+
printResult(chalk7.cyan("+----------------------------------------+\n"));
|
|
3677
|
+
const tierDisplay = tierInfo.tier === "none" ? chalk7.gray("No Tier") : tierColor(tierInfo.tier.charAt(0).toUpperCase() + tierInfo.tier.slice(1));
|
|
3678
|
+
printResult(` Current Tier: ${tierDisplay}`);
|
|
3679
|
+
if (tierInfo.tier === "none") {
|
|
3680
|
+
printResult(` Swap Fee: ${chalk7.gray("50 bps (0.50%)")}`);
|
|
3681
|
+
printResult(` Discount: ${chalk7.gray("None")}`);
|
|
3682
|
+
} else {
|
|
3683
|
+
printResult(` Swap Fee: ${chalk7.green(`${tierInfo.feeBps} bps (${(tierInfo.feeBps / 100).toFixed(2)}%)`)}`);
|
|
3684
|
+
printResult(` Discount: ${chalk7.green(`${tierInfo.discountBps} bps saved`)}`);
|
|
3685
|
+
}
|
|
3686
|
+
if (tierInfo.nextTier) {
|
|
3687
|
+
const nextTierColor = getTierColor(tierInfo.nextTier.name);
|
|
3688
|
+
printResult(chalk7.bold("\n Next Tier:"));
|
|
3689
|
+
printResult(
|
|
3690
|
+
` ${nextTierColor(tierInfo.nextTier.name.charAt(0).toUpperCase() + tierInfo.nextTier.name.slice(1))} - requires ${tierInfo.nextTier.vultRequired.toLocaleString()} VULT`
|
|
3691
|
+
);
|
|
3692
|
+
} else if (tierInfo.tier === "ultimate") {
|
|
3693
|
+
printResult(chalk7.bold("\n ") + chalk7.magenta("You have the highest tier! 0% swap fees."));
|
|
3694
|
+
}
|
|
3695
|
+
info(chalk7.gray("\n Tip: Thorguard NFT holders get +1 tier upgrade (up to gold)"));
|
|
3696
|
+
printResult("");
|
|
3697
|
+
}
|
|
3698
|
+
|
|
3081
3699
|
// src/interactive/completer.ts
|
|
3082
|
-
import { Chain as
|
|
3700
|
+
import { Chain as Chain6 } from "@vultisig/sdk";
|
|
3083
3701
|
import fs2 from "fs";
|
|
3084
3702
|
import path2 from "path";
|
|
3085
3703
|
var COMMANDS = [
|
|
@@ -3087,6 +3705,7 @@ var COMMANDS = [
|
|
|
3087
3705
|
"vaults",
|
|
3088
3706
|
"vault",
|
|
3089
3707
|
"import",
|
|
3708
|
+
"delete",
|
|
3090
3709
|
"create-from-seedphrase",
|
|
3091
3710
|
"create",
|
|
3092
3711
|
"join",
|
|
@@ -3134,13 +3753,16 @@ function createCompleter(ctx2) {
|
|
|
3134
3753
|
return completeVaultName(ctx2, partial);
|
|
3135
3754
|
}
|
|
3136
3755
|
if (command === "chains" && parts.length >= 2) {
|
|
3756
|
+
const lastPart = parts[parts.length - 1] || "";
|
|
3757
|
+
const lastPartLower = lastPart.toLowerCase();
|
|
3758
|
+
if (lastPartLower.startsWith("-")) {
|
|
3759
|
+
const flags = ["--add", "--add-all", "--remove"];
|
|
3760
|
+
const matches = flags.filter((f) => f.startsWith(lastPartLower));
|
|
3761
|
+
return [matches.length ? matches : flags, lastPart];
|
|
3762
|
+
}
|
|
3137
3763
|
const flag = parts[parts.length - 2]?.toLowerCase();
|
|
3138
3764
|
if (flag === "--add" || flag === "--remove") {
|
|
3139
|
-
|
|
3140
|
-
return completeChainName(partial);
|
|
3141
|
-
}
|
|
3142
|
-
if (parts[parts.length - 1]?.toLowerCase() === "--add" || parts[parts.length - 1]?.toLowerCase() === "--remove") {
|
|
3143
|
-
return completeChainName("");
|
|
3765
|
+
return completeChainName(lastPart);
|
|
3144
3766
|
}
|
|
3145
3767
|
}
|
|
3146
3768
|
if (["balance", "bal", "tokens", "send", "swap", "swap-quote"].includes(command) && parts.length === 2) {
|
|
@@ -3218,7 +3840,7 @@ function completeVaultName(ctx2, partial) {
|
|
|
3218
3840
|
return [show, partial];
|
|
3219
3841
|
}
|
|
3220
3842
|
function completeChainName(partial) {
|
|
3221
|
-
const allChains = Object.values(
|
|
3843
|
+
const allChains = Object.values(Chain6);
|
|
3222
3844
|
const partialLower = partial.toLowerCase();
|
|
3223
3845
|
const matches = allChains.filter((chain) => chain.toLowerCase().startsWith(partialLower));
|
|
3224
3846
|
matches.sort();
|
|
@@ -3226,14 +3848,14 @@ function completeChainName(partial) {
|
|
|
3226
3848
|
return [show, partial];
|
|
3227
3849
|
}
|
|
3228
3850
|
function findChainByName(name) {
|
|
3229
|
-
const allChains = Object.values(
|
|
3851
|
+
const allChains = Object.values(Chain6);
|
|
3230
3852
|
const nameLower = name.toLowerCase();
|
|
3231
3853
|
const found = allChains.find((chain) => chain.toLowerCase() === nameLower);
|
|
3232
3854
|
return found ? found : null;
|
|
3233
3855
|
}
|
|
3234
3856
|
|
|
3235
3857
|
// src/interactive/event-buffer.ts
|
|
3236
|
-
import
|
|
3858
|
+
import chalk8 from "chalk";
|
|
3237
3859
|
var EventBuffer = class {
|
|
3238
3860
|
eventBuffer = [];
|
|
3239
3861
|
isCommandRunning = false;
|
|
@@ -3273,17 +3895,17 @@ var EventBuffer = class {
|
|
|
3273
3895
|
displayEvent(message, type) {
|
|
3274
3896
|
switch (type) {
|
|
3275
3897
|
case "success":
|
|
3276
|
-
console.log(
|
|
3898
|
+
console.log(chalk8.green(message));
|
|
3277
3899
|
break;
|
|
3278
3900
|
case "warning":
|
|
3279
|
-
console.log(
|
|
3901
|
+
console.log(chalk8.yellow(message));
|
|
3280
3902
|
break;
|
|
3281
3903
|
case "error":
|
|
3282
|
-
console.error(
|
|
3904
|
+
console.error(chalk8.red(message));
|
|
3283
3905
|
break;
|
|
3284
3906
|
case "info":
|
|
3285
3907
|
default:
|
|
3286
|
-
console.log(
|
|
3908
|
+
console.log(chalk8.blue(message));
|
|
3287
3909
|
break;
|
|
3288
3910
|
}
|
|
3289
3911
|
}
|
|
@@ -3294,13 +3916,13 @@ var EventBuffer = class {
|
|
|
3294
3916
|
if (this.eventBuffer.length === 0) {
|
|
3295
3917
|
return;
|
|
3296
3918
|
}
|
|
3297
|
-
console.log(
|
|
3919
|
+
console.log(chalk8.gray("\n--- Background Events ---"));
|
|
3298
3920
|
this.eventBuffer.forEach((event) => {
|
|
3299
3921
|
const timeStr = event.timestamp.toLocaleTimeString();
|
|
3300
3922
|
const message = `[${timeStr}] ${event.message}`;
|
|
3301
3923
|
this.displayEvent(message, event.type);
|
|
3302
3924
|
});
|
|
3303
|
-
console.log(
|
|
3925
|
+
console.log(chalk8.gray("--- End Events ---\n"));
|
|
3304
3926
|
}
|
|
3305
3927
|
/**
|
|
3306
3928
|
* Setup all vault event listeners
|
|
@@ -3402,12 +4024,12 @@ var EventBuffer = class {
|
|
|
3402
4024
|
|
|
3403
4025
|
// src/interactive/session.ts
|
|
3404
4026
|
import { fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
|
|
3405
|
-
import
|
|
4027
|
+
import chalk10 from "chalk";
|
|
3406
4028
|
import ora3 from "ora";
|
|
3407
4029
|
import * as readline from "readline";
|
|
3408
4030
|
|
|
3409
4031
|
// src/interactive/shell-commands.ts
|
|
3410
|
-
import
|
|
4032
|
+
import chalk9 from "chalk";
|
|
3411
4033
|
import Table from "cli-table3";
|
|
3412
4034
|
import inquirer6 from "inquirer";
|
|
3413
4035
|
import ora2 from "ora";
|
|
@@ -3424,25 +4046,25 @@ function formatTimeRemaining(ms) {
|
|
|
3424
4046
|
async function executeLock(ctx2) {
|
|
3425
4047
|
const vault = ctx2.getActiveVault();
|
|
3426
4048
|
if (!vault) {
|
|
3427
|
-
console.log(
|
|
3428
|
-
console.log(
|
|
4049
|
+
console.log(chalk9.red("No active vault."));
|
|
4050
|
+
console.log(chalk9.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
3429
4051
|
return;
|
|
3430
4052
|
}
|
|
3431
4053
|
ctx2.lockVault(vault.id);
|
|
3432
|
-
console.log(
|
|
3433
|
-
console.log(
|
|
4054
|
+
console.log(chalk9.green("\n+ Vault locked"));
|
|
4055
|
+
console.log(chalk9.gray("Password cache cleared. You will need to enter the password again."));
|
|
3434
4056
|
}
|
|
3435
4057
|
async function executeUnlock(ctx2) {
|
|
3436
4058
|
const vault = ctx2.getActiveVault();
|
|
3437
4059
|
if (!vault) {
|
|
3438
|
-
console.log(
|
|
3439
|
-
console.log(
|
|
4060
|
+
console.log(chalk9.red("No active vault."));
|
|
4061
|
+
console.log(chalk9.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
3440
4062
|
return;
|
|
3441
4063
|
}
|
|
3442
4064
|
if (ctx2.isVaultUnlocked(vault.id)) {
|
|
3443
4065
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
3444
|
-
console.log(
|
|
3445
|
-
console.log(
|
|
4066
|
+
console.log(chalk9.yellow("\nVault is already unlocked."));
|
|
4067
|
+
console.log(chalk9.gray(`Time remaining: ${formatTimeRemaining(timeRemaining)}`));
|
|
3446
4068
|
return;
|
|
3447
4069
|
}
|
|
3448
4070
|
const { password } = await inquirer6.prompt([
|
|
@@ -3459,19 +4081,19 @@ async function executeUnlock(ctx2) {
|
|
|
3459
4081
|
ctx2.cachePassword(vault.id, password);
|
|
3460
4082
|
const timeRemaining = ctx2.getUnlockTimeRemaining(vault.id);
|
|
3461
4083
|
spinner.succeed("Vault unlocked");
|
|
3462
|
-
console.log(
|
|
4084
|
+
console.log(chalk9.green(`
|
|
3463
4085
|
+ Vault unlocked for ${formatTimeRemaining(timeRemaining)}`));
|
|
3464
4086
|
} catch (err) {
|
|
3465
4087
|
spinner.fail("Failed to unlock vault");
|
|
3466
|
-
console.error(
|
|
4088
|
+
console.error(chalk9.red(`
|
|
3467
4089
|
x ${err.message}`));
|
|
3468
4090
|
}
|
|
3469
4091
|
}
|
|
3470
4092
|
async function executeStatus(ctx2) {
|
|
3471
4093
|
const vault = ctx2.getActiveVault();
|
|
3472
4094
|
if (!vault) {
|
|
3473
|
-
console.log(
|
|
3474
|
-
console.log(
|
|
4095
|
+
console.log(chalk9.red("No active vault."));
|
|
4096
|
+
console.log(chalk9.yellow('Use "vault <name>" to switch to a vault first.'));
|
|
3475
4097
|
return;
|
|
3476
4098
|
}
|
|
3477
4099
|
const isUnlocked = ctx2.isVaultUnlocked(vault.id);
|
|
@@ -3502,30 +4124,30 @@ async function executeStatus(ctx2) {
|
|
|
3502
4124
|
displayStatus(status);
|
|
3503
4125
|
}
|
|
3504
4126
|
function displayStatus(status) {
|
|
3505
|
-
console.log(
|
|
3506
|
-
console.log(
|
|
3507
|
-
console.log(
|
|
3508
|
-
console.log(
|
|
3509
|
-
console.log(` Name: ${
|
|
4127
|
+
console.log(chalk9.cyan("\n+----------------------------------------+"));
|
|
4128
|
+
console.log(chalk9.cyan("| Vault Status |"));
|
|
4129
|
+
console.log(chalk9.cyan("+----------------------------------------+\n"));
|
|
4130
|
+
console.log(chalk9.bold("Vault:"));
|
|
4131
|
+
console.log(` Name: ${chalk9.green(status.name)}`);
|
|
3510
4132
|
console.log(` ID: ${status.id}`);
|
|
3511
|
-
console.log(` Type: ${
|
|
3512
|
-
console.log(
|
|
4133
|
+
console.log(` Type: ${chalk9.yellow(status.type)}`);
|
|
4134
|
+
console.log(chalk9.bold("\nSecurity:"));
|
|
3513
4135
|
if (status.isUnlocked) {
|
|
3514
|
-
console.log(` Status: ${
|
|
4136
|
+
console.log(` Status: ${chalk9.green("Unlocked")} ${chalk9.green("\u{1F513}")}`);
|
|
3515
4137
|
console.log(` Expires: ${status.timeRemainingFormatted}`);
|
|
3516
4138
|
} else {
|
|
3517
|
-
console.log(` Status: ${
|
|
4139
|
+
console.log(` Status: ${chalk9.yellow("Locked")} ${chalk9.yellow("\u{1F512}")}`);
|
|
3518
4140
|
}
|
|
3519
|
-
console.log(` Encrypted: ${status.isEncrypted ?
|
|
3520
|
-
console.log(` Backed Up: ${status.isBackedUp ?
|
|
3521
|
-
console.log(
|
|
4141
|
+
console.log(` Encrypted: ${status.isEncrypted ? chalk9.green("Yes") : chalk9.gray("No")}`);
|
|
4142
|
+
console.log(` Backed Up: ${status.isBackedUp ? chalk9.green("Yes") : chalk9.yellow("No")}`);
|
|
4143
|
+
console.log(chalk9.bold("\nMPC Configuration:"));
|
|
3522
4144
|
console.log(` Library: ${status.libType}`);
|
|
3523
|
-
console.log(` Threshold: ${
|
|
3524
|
-
console.log(
|
|
4145
|
+
console.log(` Threshold: ${chalk9.cyan(status.threshold)} of ${chalk9.cyan(status.totalSigners)}`);
|
|
4146
|
+
console.log(chalk9.bold("\nSigning Modes:"));
|
|
3525
4147
|
status.availableSigningModes.forEach((mode) => {
|
|
3526
4148
|
console.log(` - ${mode}`);
|
|
3527
4149
|
});
|
|
3528
|
-
console.log(
|
|
4150
|
+
console.log(chalk9.bold("\nDetails:"));
|
|
3529
4151
|
console.log(` Chains: ${status.chains}`);
|
|
3530
4152
|
console.log(` Currency: ${status.currency.toUpperCase()}`);
|
|
3531
4153
|
console.log(` Created: ${new Date(status.createdAt).toLocaleString()}`);
|
|
@@ -3534,7 +4156,7 @@ function displayStatus(status) {
|
|
|
3534
4156
|
}
|
|
3535
4157
|
function showHelp() {
|
|
3536
4158
|
const table = new Table({
|
|
3537
|
-
head: [
|
|
4159
|
+
head: [chalk9.bold("Available Commands")],
|
|
3538
4160
|
colWidths: [50],
|
|
3539
4161
|
chars: {
|
|
3540
4162
|
mid: "",
|
|
@@ -3548,38 +4170,39 @@ function showHelp() {
|
|
|
3548
4170
|
}
|
|
3549
4171
|
});
|
|
3550
4172
|
table.push(
|
|
3551
|
-
[
|
|
4173
|
+
[chalk9.bold("Vault Management:")],
|
|
3552
4174
|
[" vaults - List all vaults"],
|
|
3553
4175
|
[" vault <name> - Switch to vault"],
|
|
3554
4176
|
[" import <file> - Import vault from file"],
|
|
4177
|
+
[" delete [name] - Delete vault"],
|
|
3555
4178
|
[" create - Create new vault"],
|
|
3556
4179
|
[" info - Show vault details"],
|
|
3557
4180
|
[" export [path] - Export vault to file"],
|
|
3558
4181
|
[""],
|
|
3559
|
-
[
|
|
4182
|
+
[chalk9.bold("Wallet Operations:")],
|
|
3560
4183
|
[" balance [chain] - Show balances"],
|
|
3561
4184
|
[" send <chain> <to> <amount> - Send transaction"],
|
|
3562
4185
|
[" portfolio [-c usd] - Show portfolio value"],
|
|
3563
4186
|
[" addresses - Show all addresses"],
|
|
3564
|
-
[" chains [--add/--remove] - Manage chains"],
|
|
4187
|
+
[" chains [--add/--remove/--add-all] - Manage chains"],
|
|
3565
4188
|
[" tokens <chain> - Manage tokens"],
|
|
3566
4189
|
[""],
|
|
3567
|
-
[
|
|
4190
|
+
[chalk9.bold("Swap Operations:")],
|
|
3568
4191
|
[" swap-chains - List swap-enabled chains"],
|
|
3569
4192
|
[" swap-quote <from> <to> <amount> - Get quote"],
|
|
3570
4193
|
[" swap <from> <to> <amount> - Execute swap"],
|
|
3571
4194
|
[""],
|
|
3572
|
-
[
|
|
4195
|
+
[chalk9.bold("Session Commands (shell only):")],
|
|
3573
4196
|
[" lock - Lock vault"],
|
|
3574
4197
|
[" unlock - Unlock vault"],
|
|
3575
4198
|
[" status - Show vault status"],
|
|
3576
4199
|
[""],
|
|
3577
|
-
[
|
|
4200
|
+
[chalk9.bold("Settings:")],
|
|
3578
4201
|
[" currency [code] - View/set currency"],
|
|
3579
4202
|
[" server - Check server status"],
|
|
3580
4203
|
[" address-book - Manage saved addresses"],
|
|
3581
4204
|
[""],
|
|
3582
|
-
[
|
|
4205
|
+
[chalk9.bold("Help & Navigation:")],
|
|
3583
4206
|
[" help, ? - Show this help"],
|
|
3584
4207
|
[" .clear - Clear screen"],
|
|
3585
4208
|
[" .exit - Exit shell"]
|
|
@@ -3717,12 +4340,12 @@ var ShellSession = class {
|
|
|
3717
4340
|
*/
|
|
3718
4341
|
async start() {
|
|
3719
4342
|
console.clear();
|
|
3720
|
-
console.log(
|
|
3721
|
-
console.log(
|
|
3722
|
-
console.log(
|
|
4343
|
+
console.log(chalk10.cyan.bold("\n=============================================="));
|
|
4344
|
+
console.log(chalk10.cyan.bold(" Vultisig Interactive Shell"));
|
|
4345
|
+
console.log(chalk10.cyan.bold("==============================================\n"));
|
|
3723
4346
|
await this.loadAllVaults();
|
|
3724
4347
|
this.displayVaultList();
|
|
3725
|
-
console.log(
|
|
4348
|
+
console.log(chalk10.gray('Type "help" for available commands, "exit" to quit\n'));
|
|
3726
4349
|
this.promptLoop().catch(() => {
|
|
3727
4350
|
});
|
|
3728
4351
|
}
|
|
@@ -3756,12 +4379,12 @@ var ShellSession = class {
|
|
|
3756
4379
|
const now = Date.now();
|
|
3757
4380
|
if (now - this.lastSigintTime < this.DOUBLE_CTRL_C_TIMEOUT) {
|
|
3758
4381
|
rl.close();
|
|
3759
|
-
console.log(
|
|
4382
|
+
console.log(chalk10.yellow("\nGoodbye!"));
|
|
3760
4383
|
this.ctx.dispose();
|
|
3761
4384
|
process.exit(0);
|
|
3762
4385
|
}
|
|
3763
4386
|
this.lastSigintTime = now;
|
|
3764
|
-
console.log(
|
|
4387
|
+
console.log(chalk10.yellow("\n(Press Ctrl+C again to exit)"));
|
|
3765
4388
|
rl.close();
|
|
3766
4389
|
resolve("");
|
|
3767
4390
|
});
|
|
@@ -3856,7 +4479,7 @@ var ShellSession = class {
|
|
|
3856
4479
|
stopAllSpinners();
|
|
3857
4480
|
process.stdout.write("\x1B[?25h");
|
|
3858
4481
|
process.stdout.write("\r\x1B[K");
|
|
3859
|
-
console.log(
|
|
4482
|
+
console.log(chalk10.yellow("\nCancelling operation..."));
|
|
3860
4483
|
};
|
|
3861
4484
|
const cleanup = () => {
|
|
3862
4485
|
process.removeListener("SIGINT", onSigint);
|
|
@@ -3893,10 +4516,10 @@ var ShellSession = class {
|
|
|
3893
4516
|
stopAllSpinners();
|
|
3894
4517
|
process.stdout.write("\x1B[?25h");
|
|
3895
4518
|
process.stdout.write("\r\x1B[K");
|
|
3896
|
-
console.log(
|
|
4519
|
+
console.log(chalk10.yellow("Operation cancelled"));
|
|
3897
4520
|
return;
|
|
3898
4521
|
}
|
|
3899
|
-
console.error(
|
|
4522
|
+
console.error(chalk10.red(`
|
|
3900
4523
|
Error: ${error2.message}`));
|
|
3901
4524
|
}
|
|
3902
4525
|
}
|
|
@@ -3929,11 +4552,14 @@ Error: ${error2.message}`));
|
|
|
3929
4552
|
break;
|
|
3930
4553
|
case "rename":
|
|
3931
4554
|
if (args.length === 0) {
|
|
3932
|
-
console.log(
|
|
4555
|
+
console.log(chalk10.yellow("Usage: rename <newName>"));
|
|
3933
4556
|
return;
|
|
3934
4557
|
}
|
|
3935
4558
|
await executeRename(this.ctx, args.join(" "));
|
|
3936
4559
|
break;
|
|
4560
|
+
case "delete":
|
|
4561
|
+
await this.deleteVault(args);
|
|
4562
|
+
break;
|
|
3937
4563
|
// Balance commands
|
|
3938
4564
|
case "balance":
|
|
3939
4565
|
case "bal":
|
|
@@ -3999,41 +4625,41 @@ Error: ${error2.message}`));
|
|
|
3999
4625
|
// Exit
|
|
4000
4626
|
case "exit":
|
|
4001
4627
|
case "quit":
|
|
4002
|
-
console.log(
|
|
4628
|
+
console.log(chalk10.yellow("\nGoodbye!"));
|
|
4003
4629
|
this.ctx.dispose();
|
|
4004
4630
|
process.exit(0);
|
|
4005
4631
|
break;
|
|
4006
4632
|
// eslint requires break even after process.exit
|
|
4007
4633
|
default:
|
|
4008
|
-
console.log(
|
|
4009
|
-
console.log(
|
|
4634
|
+
console.log(chalk10.yellow(`Unknown command: ${command}`));
|
|
4635
|
+
console.log(chalk10.gray('Type "help" for available commands'));
|
|
4010
4636
|
break;
|
|
4011
4637
|
}
|
|
4012
4638
|
}
|
|
4013
4639
|
// ===== Command Helpers =====
|
|
4014
4640
|
async switchVault(args) {
|
|
4015
4641
|
if (args.length === 0) {
|
|
4016
|
-
console.log(
|
|
4017
|
-
console.log(
|
|
4642
|
+
console.log(chalk10.yellow("Usage: vault <name>"));
|
|
4643
|
+
console.log(chalk10.gray('Run "vaults" to see available vaults'));
|
|
4018
4644
|
return;
|
|
4019
4645
|
}
|
|
4020
4646
|
const vaultName = args.join(" ");
|
|
4021
4647
|
const vault = this.ctx.findVaultByName(vaultName);
|
|
4022
4648
|
if (!vault) {
|
|
4023
|
-
console.log(
|
|
4024
|
-
console.log(
|
|
4649
|
+
console.log(chalk10.red(`Vault not found: ${vaultName}`));
|
|
4650
|
+
console.log(chalk10.gray('Run "vaults" to see available vaults'));
|
|
4025
4651
|
return;
|
|
4026
4652
|
}
|
|
4027
4653
|
await this.ctx.setActiveVault(vault);
|
|
4028
|
-
console.log(
|
|
4654
|
+
console.log(chalk10.green(`
|
|
4029
4655
|
+ Switched to: ${vault.name}`));
|
|
4030
4656
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
4031
|
-
const status = isUnlocked ?
|
|
4657
|
+
const status = isUnlocked ? chalk10.green("Unlocked") : chalk10.yellow("Locked");
|
|
4032
4658
|
console.log(`Status: ${status}`);
|
|
4033
4659
|
}
|
|
4034
4660
|
async importVault(args) {
|
|
4035
4661
|
if (args.length === 0) {
|
|
4036
|
-
console.log(
|
|
4662
|
+
console.log(chalk10.yellow("Usage: import <file>"));
|
|
4037
4663
|
return;
|
|
4038
4664
|
}
|
|
4039
4665
|
const filePath = args.join(" ");
|
|
@@ -4041,48 +4667,52 @@ Error: ${error2.message}`));
|
|
|
4041
4667
|
this.ctx.addVault(vault);
|
|
4042
4668
|
this.eventBuffer.setupVaultListeners(vault);
|
|
4043
4669
|
}
|
|
4670
|
+
async deleteVault(args) {
|
|
4671
|
+
const vaultIdOrName = args.join(" ") || void 0;
|
|
4672
|
+
await executeDelete(this.ctx, { vaultId: vaultIdOrName });
|
|
4673
|
+
}
|
|
4044
4674
|
async createVault(args) {
|
|
4045
4675
|
const type = args[0]?.toLowerCase();
|
|
4046
4676
|
if (!type || type !== "fast" && type !== "secure") {
|
|
4047
|
-
console.log(
|
|
4048
|
-
console.log(
|
|
4049
|
-
console.log(
|
|
4677
|
+
console.log(chalk10.yellow("Usage: create <fast|secure>"));
|
|
4678
|
+
console.log(chalk10.gray(" create fast - Create a fast vault (server-assisted 2-of-2)"));
|
|
4679
|
+
console.log(chalk10.gray(" create secure - Create a secure vault (multi-device MPC)"));
|
|
4050
4680
|
return;
|
|
4051
4681
|
}
|
|
4052
4682
|
let vault;
|
|
4053
4683
|
if (type === "fast") {
|
|
4054
4684
|
const name = await this.prompt("Vault name");
|
|
4055
4685
|
if (!name) {
|
|
4056
|
-
console.log(
|
|
4686
|
+
console.log(chalk10.red("Name is required"));
|
|
4057
4687
|
return;
|
|
4058
4688
|
}
|
|
4059
4689
|
const password = await this.promptPassword("Vault password");
|
|
4060
4690
|
if (!password) {
|
|
4061
|
-
console.log(
|
|
4691
|
+
console.log(chalk10.red("Password is required"));
|
|
4062
4692
|
return;
|
|
4063
4693
|
}
|
|
4064
4694
|
const email = await this.prompt("Email for verification");
|
|
4065
4695
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4066
|
-
console.log(
|
|
4696
|
+
console.log(chalk10.red("Valid email is required"));
|
|
4067
4697
|
return;
|
|
4068
4698
|
}
|
|
4069
4699
|
vault = await this.withCancellation((signal) => executeCreateFast(this.ctx, { name, password, email, signal }));
|
|
4070
4700
|
} else {
|
|
4071
4701
|
const name = await this.prompt("Vault name");
|
|
4072
4702
|
if (!name) {
|
|
4073
|
-
console.log(
|
|
4703
|
+
console.log(chalk10.red("Name is required"));
|
|
4074
4704
|
return;
|
|
4075
4705
|
}
|
|
4076
4706
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
4077
4707
|
const shares = parseInt(sharesStr, 10);
|
|
4078
4708
|
if (isNaN(shares) || shares < 2) {
|
|
4079
|
-
console.log(
|
|
4709
|
+
console.log(chalk10.red("Must have at least 2 shares"));
|
|
4080
4710
|
return;
|
|
4081
4711
|
}
|
|
4082
4712
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
4083
4713
|
const threshold = parseInt(thresholdStr, 10);
|
|
4084
4714
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
4085
|
-
console.log(
|
|
4715
|
+
console.log(chalk10.red(`Threshold must be between 1 and ${shares}`));
|
|
4086
4716
|
return;
|
|
4087
4717
|
}
|
|
4088
4718
|
const password = await this.promptPassword("Vault password (optional, press Enter to skip)");
|
|
@@ -4104,37 +4734,37 @@ Error: ${error2.message}`));
|
|
|
4104
4734
|
async importSeedphrase(args) {
|
|
4105
4735
|
const type = args[0]?.toLowerCase();
|
|
4106
4736
|
if (!type || type !== "fast" && type !== "secure") {
|
|
4107
|
-
console.log(
|
|
4108
|
-
console.log(
|
|
4109
|
-
console.log(
|
|
4737
|
+
console.log(chalk10.cyan("Usage: create-from-seedphrase <fast|secure>"));
|
|
4738
|
+
console.log(chalk10.gray(" fast - Import with VultiServer (2-of-2)"));
|
|
4739
|
+
console.log(chalk10.gray(" secure - Import with device coordination (N-of-M)"));
|
|
4110
4740
|
return;
|
|
4111
4741
|
}
|
|
4112
|
-
console.log(
|
|
4742
|
+
console.log(chalk10.cyan("\nEnter your recovery phrase (words separated by spaces):"));
|
|
4113
4743
|
const mnemonic = await this.promptPassword("Seedphrase");
|
|
4114
4744
|
const validation = await this.ctx.sdk.validateSeedphrase(mnemonic);
|
|
4115
4745
|
if (!validation.valid) {
|
|
4116
|
-
console.log(
|
|
4746
|
+
console.log(chalk10.red(`Invalid seedphrase: ${validation.error}`));
|
|
4117
4747
|
if (validation.invalidWords?.length) {
|
|
4118
|
-
console.log(
|
|
4748
|
+
console.log(chalk10.yellow(`Invalid words: ${validation.invalidWords.join(", ")}`));
|
|
4119
4749
|
}
|
|
4120
4750
|
return;
|
|
4121
4751
|
}
|
|
4122
|
-
console.log(
|
|
4752
|
+
console.log(chalk10.green(`\u2713 Valid ${validation.wordCount}-word seedphrase`));
|
|
4123
4753
|
let vault;
|
|
4124
4754
|
if (type === "fast") {
|
|
4125
4755
|
const name = await this.prompt("Vault name");
|
|
4126
4756
|
if (!name) {
|
|
4127
|
-
console.log(
|
|
4757
|
+
console.log(chalk10.red("Name is required"));
|
|
4128
4758
|
return;
|
|
4129
4759
|
}
|
|
4130
4760
|
const password = await this.promptPassword("Vault password");
|
|
4131
4761
|
if (!password) {
|
|
4132
|
-
console.log(
|
|
4762
|
+
console.log(chalk10.red("Password is required"));
|
|
4133
4763
|
return;
|
|
4134
4764
|
}
|
|
4135
4765
|
const email = await this.prompt("Email for verification");
|
|
4136
4766
|
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
4137
|
-
console.log(
|
|
4767
|
+
console.log(chalk10.red("Valid email is required"));
|
|
4138
4768
|
return;
|
|
4139
4769
|
}
|
|
4140
4770
|
const discoverStr = await this.prompt("Discover chains with balances? (y/n)", "y");
|
|
@@ -4152,19 +4782,19 @@ Error: ${error2.message}`));
|
|
|
4152
4782
|
} else {
|
|
4153
4783
|
const name = await this.prompt("Vault name");
|
|
4154
4784
|
if (!name) {
|
|
4155
|
-
console.log(
|
|
4785
|
+
console.log(chalk10.red("Name is required"));
|
|
4156
4786
|
return;
|
|
4157
4787
|
}
|
|
4158
4788
|
const sharesStr = await this.prompt("Total shares (devices)", "3");
|
|
4159
4789
|
const shares = parseInt(sharesStr, 10);
|
|
4160
4790
|
if (isNaN(shares) || shares < 2) {
|
|
4161
|
-
console.log(
|
|
4791
|
+
console.log(chalk10.red("Must have at least 2 shares"));
|
|
4162
4792
|
return;
|
|
4163
4793
|
}
|
|
4164
4794
|
const thresholdStr = await this.prompt("Signing threshold", "2");
|
|
4165
4795
|
const threshold = parseInt(thresholdStr, 10);
|
|
4166
4796
|
if (isNaN(threshold) || threshold < 1 || threshold > shares) {
|
|
4167
|
-
console.log(
|
|
4797
|
+
console.log(chalk10.red(`Threshold must be between 1 and ${shares}`));
|
|
4168
4798
|
return;
|
|
4169
4799
|
}
|
|
4170
4800
|
const password = await this.promptPassword("Vault password (optional, Enter to skip)");
|
|
@@ -4188,12 +4818,14 @@ Error: ${error2.message}`));
|
|
|
4188
4818
|
}
|
|
4189
4819
|
}
|
|
4190
4820
|
async runBalance(args) {
|
|
4191
|
-
const chainStr = args
|
|
4821
|
+
const chainStr = args.find((arg) => !arg.startsWith("-"));
|
|
4192
4822
|
const includeTokens = args.includes("-t") || args.includes("--tokens");
|
|
4823
|
+
const raw = args.includes("--raw");
|
|
4193
4824
|
await this.withCancellation(
|
|
4194
4825
|
() => executeBalance(this.ctx, {
|
|
4195
4826
|
chain: chainStr ? findChainByName(chainStr) || chainStr : void 0,
|
|
4196
|
-
includeTokens
|
|
4827
|
+
includeTokens,
|
|
4828
|
+
raw
|
|
4197
4829
|
})
|
|
4198
4830
|
);
|
|
4199
4831
|
}
|
|
@@ -4206,15 +4838,16 @@ Error: ${error2.message}`));
|
|
|
4206
4838
|
}
|
|
4207
4839
|
}
|
|
4208
4840
|
if (!fiatCurrencies3.includes(currency)) {
|
|
4209
|
-
console.log(
|
|
4210
|
-
console.log(
|
|
4841
|
+
console.log(chalk10.red(`Invalid currency: ${currency}`));
|
|
4842
|
+
console.log(chalk10.yellow(`Supported currencies: ${fiatCurrencies3.join(", ")}`));
|
|
4211
4843
|
return;
|
|
4212
4844
|
}
|
|
4213
|
-
|
|
4845
|
+
const raw = args.includes("--raw");
|
|
4846
|
+
await this.withCancellation(() => executePortfolio(this.ctx, { currency, raw }));
|
|
4214
4847
|
}
|
|
4215
4848
|
async runSend(args) {
|
|
4216
4849
|
if (args.length < 3) {
|
|
4217
|
-
console.log(
|
|
4850
|
+
console.log(chalk10.yellow("Usage: send <chain> <to> <amount> [--token <tokenId>] [--memo <memo>]"));
|
|
4218
4851
|
return;
|
|
4219
4852
|
}
|
|
4220
4853
|
const [chainStr, to, amount, ...rest] = args;
|
|
@@ -4234,7 +4867,7 @@ Error: ${error2.message}`));
|
|
|
4234
4867
|
await this.withAbortHandler((signal) => executeSend(this.ctx, { chain, to, amount, tokenId, memo, signal }));
|
|
4235
4868
|
} catch (err) {
|
|
4236
4869
|
if (err.message === "Transaction cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
4237
|
-
console.log(
|
|
4870
|
+
console.log(chalk10.yellow("\nTransaction cancelled"));
|
|
4238
4871
|
return;
|
|
4239
4872
|
}
|
|
4240
4873
|
throw err;
|
|
@@ -4243,12 +4876,15 @@ Error: ${error2.message}`));
|
|
|
4243
4876
|
async runChains(args) {
|
|
4244
4877
|
let addChain;
|
|
4245
4878
|
let removeChain;
|
|
4879
|
+
let addAll = false;
|
|
4246
4880
|
for (let i = 0; i < args.length; i++) {
|
|
4247
|
-
if (args[i] === "--add"
|
|
4881
|
+
if (args[i] === "--add-all") {
|
|
4882
|
+
addAll = true;
|
|
4883
|
+
} else if (args[i] === "--add" && i + 1 < args.length) {
|
|
4248
4884
|
const chain = findChainByName(args[i + 1]);
|
|
4249
4885
|
if (!chain) {
|
|
4250
|
-
console.log(
|
|
4251
|
-
console.log(
|
|
4886
|
+
console.log(chalk10.red(`Unknown chain: ${args[i + 1]}`));
|
|
4887
|
+
console.log(chalk10.gray("Use tab completion to see available chains"));
|
|
4252
4888
|
return;
|
|
4253
4889
|
}
|
|
4254
4890
|
addChain = chain;
|
|
@@ -4256,19 +4892,19 @@ Error: ${error2.message}`));
|
|
|
4256
4892
|
} else if (args[i] === "--remove" && i + 1 < args.length) {
|
|
4257
4893
|
const chain = findChainByName(args[i + 1]);
|
|
4258
4894
|
if (!chain) {
|
|
4259
|
-
console.log(
|
|
4260
|
-
console.log(
|
|
4895
|
+
console.log(chalk10.red(`Unknown chain: ${args[i + 1]}`));
|
|
4896
|
+
console.log(chalk10.gray("Use tab completion to see available chains"));
|
|
4261
4897
|
return;
|
|
4262
4898
|
}
|
|
4263
4899
|
removeChain = chain;
|
|
4264
4900
|
i++;
|
|
4265
4901
|
}
|
|
4266
4902
|
}
|
|
4267
|
-
await executeChains(this.ctx, { add: addChain, remove: removeChain });
|
|
4903
|
+
await executeChains(this.ctx, { add: addChain, remove: removeChain, addAll });
|
|
4268
4904
|
}
|
|
4269
4905
|
async runTokens(args) {
|
|
4270
4906
|
if (args.length === 0) {
|
|
4271
|
-
console.log(
|
|
4907
|
+
console.log(chalk10.yellow("Usage: tokens <chain> [--add <address>] [--remove <tokenId>]"));
|
|
4272
4908
|
return;
|
|
4273
4909
|
}
|
|
4274
4910
|
const chainStr = args[0];
|
|
@@ -4289,7 +4925,7 @@ Error: ${error2.message}`));
|
|
|
4289
4925
|
async runSwapQuote(args) {
|
|
4290
4926
|
if (args.length < 3) {
|
|
4291
4927
|
console.log(
|
|
4292
|
-
|
|
4928
|
+
chalk10.yellow("Usage: swap-quote <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>]")
|
|
4293
4929
|
);
|
|
4294
4930
|
return;
|
|
4295
4931
|
}
|
|
@@ -4313,7 +4949,7 @@ Error: ${error2.message}`));
|
|
|
4313
4949
|
async runSwap(args) {
|
|
4314
4950
|
if (args.length < 3) {
|
|
4315
4951
|
console.log(
|
|
4316
|
-
|
|
4952
|
+
chalk10.yellow(
|
|
4317
4953
|
"Usage: swap <fromChain> <toChain> <amount> [--from-token <addr>] [--to-token <addr>] [--slippage <pct>]"
|
|
4318
4954
|
)
|
|
4319
4955
|
);
|
|
@@ -4344,7 +4980,7 @@ Error: ${error2.message}`));
|
|
|
4344
4980
|
);
|
|
4345
4981
|
} catch (err) {
|
|
4346
4982
|
if (err.message === "Swap cancelled by user" || err.message === "Operation cancelled" || err.message === "Operation aborted") {
|
|
4347
|
-
console.log(
|
|
4983
|
+
console.log(chalk10.yellow("\nSwap cancelled"));
|
|
4348
4984
|
return;
|
|
4349
4985
|
}
|
|
4350
4986
|
throw err;
|
|
@@ -4406,24 +5042,24 @@ Error: ${error2.message}`));
|
|
|
4406
5042
|
}
|
|
4407
5043
|
getPrompt() {
|
|
4408
5044
|
const vault = this.ctx.getActiveVault();
|
|
4409
|
-
if (!vault) return
|
|
5045
|
+
if (!vault) return chalk10.cyan("wallet> ");
|
|
4410
5046
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
4411
|
-
const status = isUnlocked ?
|
|
4412
|
-
return
|
|
5047
|
+
const status = isUnlocked ? chalk10.green("\u{1F513}") : chalk10.yellow("\u{1F512}");
|
|
5048
|
+
return chalk10.cyan(`wallet[${vault.name}]${status}> `);
|
|
4413
5049
|
}
|
|
4414
5050
|
displayVaultList() {
|
|
4415
5051
|
const vaults = Array.from(this.ctx.getVaults().values());
|
|
4416
5052
|
const activeVault = this.ctx.getActiveVault();
|
|
4417
5053
|
if (vaults.length === 0) {
|
|
4418
|
-
console.log(
|
|
5054
|
+
console.log(chalk10.yellow('No vaults found. Use "create" or "import <file>" to add a vault.\n'));
|
|
4419
5055
|
return;
|
|
4420
5056
|
}
|
|
4421
|
-
console.log(
|
|
5057
|
+
console.log(chalk10.cyan("Loaded Vaults:\n"));
|
|
4422
5058
|
vaults.forEach((vault) => {
|
|
4423
5059
|
const isActive = vault.id === activeVault?.id;
|
|
4424
5060
|
const isUnlocked = this.ctx.isVaultUnlocked(vault.id);
|
|
4425
|
-
const activeMarker = isActive ?
|
|
4426
|
-
const lockIcon = isUnlocked ?
|
|
5061
|
+
const activeMarker = isActive ? chalk10.green(" (active)") : "";
|
|
5062
|
+
const lockIcon = isUnlocked ? chalk10.green("\u{1F513}") : chalk10.yellow("\u{1F512}");
|
|
4427
5063
|
console.log(` ${lockIcon} ${vault.name}${activeMarker} - ${vault.type}`);
|
|
4428
5064
|
});
|
|
4429
5065
|
console.log();
|
|
@@ -4431,10 +5067,10 @@ Error: ${error2.message}`));
|
|
|
4431
5067
|
};
|
|
4432
5068
|
|
|
4433
5069
|
// src/lib/errors.ts
|
|
4434
|
-
import
|
|
5070
|
+
import chalk11 from "chalk";
|
|
4435
5071
|
|
|
4436
5072
|
// src/lib/version.ts
|
|
4437
|
-
import
|
|
5073
|
+
import chalk12 from "chalk";
|
|
4438
5074
|
import { readFileSync, writeFileSync, mkdirSync, existsSync } from "fs";
|
|
4439
5075
|
import { homedir } from "os";
|
|
4440
5076
|
import { join } from "path";
|
|
@@ -4442,7 +5078,7 @@ var cachedVersion = null;
|
|
|
4442
5078
|
function getVersion() {
|
|
4443
5079
|
if (cachedVersion) return cachedVersion;
|
|
4444
5080
|
if (true) {
|
|
4445
|
-
cachedVersion = "0.
|
|
5081
|
+
cachedVersion = "0.5.0";
|
|
4446
5082
|
return cachedVersion;
|
|
4447
5083
|
}
|
|
4448
5084
|
try {
|
|
@@ -4539,7 +5175,7 @@ function formatVersionShort() {
|
|
|
4539
5175
|
}
|
|
4540
5176
|
function formatVersionDetailed() {
|
|
4541
5177
|
const lines = [];
|
|
4542
|
-
lines.push(
|
|
5178
|
+
lines.push(chalk12.bold(`Vultisig CLI v${getVersion()}`));
|
|
4543
5179
|
lines.push("");
|
|
4544
5180
|
lines.push(` Node.js: ${process.version}`);
|
|
4545
5181
|
lines.push(` Platform: ${process.platform}-${process.arch}`);
|
|
@@ -4916,7 +5552,7 @@ async function init(vaultOverride, unlockPassword) {
|
|
|
4916
5552
|
if (unlockPassword && vaultSelector) {
|
|
4917
5553
|
cachePassword(vaultSelector, unlockPassword);
|
|
4918
5554
|
}
|
|
4919
|
-
const sdk = new
|
|
5555
|
+
const sdk = new Vultisig5({
|
|
4920
5556
|
onPasswordRequired: createPasswordCallback()
|
|
4921
5557
|
});
|
|
4922
5558
|
await sdk.initialize();
|
|
@@ -5001,7 +5637,7 @@ async function promptQrPayload() {
|
|
|
5001
5637
|
]);
|
|
5002
5638
|
return answer.qrPayload.trim();
|
|
5003
5639
|
}
|
|
5004
|
-
createFromSeedphraseCmd.command("fast").description("Create FastVault from seedphrase (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--mnemonic <words>", "Seedphrase (12 or 24 words, space-separated)").option("--discover-chains", "Scan chains for existing balances").option("--chains <chains>", "Specific chains to enable (comma-separated)").action(
|
|
5640
|
+
createFromSeedphraseCmd.command("fast").description("Create FastVault from seedphrase (server-assisted 2-of-2)").requiredOption("--name <name>", "Vault name").requiredOption("--password <password>", "Vault password").requiredOption("--email <email>", "Email for verification").option("--mnemonic <words>", "Seedphrase (12 or 24 words, space-separated)").option("--discover-chains", "Scan chains for existing balances").option("--chains <chains>", "Specific chains to enable (comma-separated)").option("--use-phantom-solana-path", "Use Phantom wallet derivation path for Solana").action(
|
|
5005
5641
|
withExit(
|
|
5006
5642
|
async (options) => {
|
|
5007
5643
|
const context = await init(program.opts().vault);
|
|
@@ -5028,12 +5664,13 @@ createFromSeedphraseCmd.command("fast").description("Create FastVault from seedp
|
|
|
5028
5664
|
password: options.password,
|
|
5029
5665
|
email: options.email,
|
|
5030
5666
|
discoverChains: options.discoverChains,
|
|
5031
|
-
chains
|
|
5667
|
+
chains,
|
|
5668
|
+
usePhantomSolanaPath: options.usePhantomSolanaPath
|
|
5032
5669
|
});
|
|
5033
5670
|
}
|
|
5034
5671
|
)
|
|
5035
5672
|
);
|
|
5036
|
-
createFromSeedphraseCmd.command("secure").description("Create SecureVault from seedphrase (multi-device MPC)").requiredOption("--name <name>", "Vault name").option("--password <password>", "Vault password (optional)").option("--threshold <m>", "Signing threshold", "2").option("--shares <n>", "Total shares", "3").option("--mnemonic <words>", "Seedphrase (12 or 24 words)").option("--discover-chains", "Scan chains for existing balances").option("--chains <chains>", "Specific chains to enable (comma-separated)").action(
|
|
5673
|
+
createFromSeedphraseCmd.command("secure").description("Create SecureVault from seedphrase (multi-device MPC)").requiredOption("--name <name>", "Vault name").option("--password <password>", "Vault password (optional)").option("--threshold <m>", "Signing threshold", "2").option("--shares <n>", "Total shares", "3").option("--mnemonic <words>", "Seedphrase (12 or 24 words)").option("--discover-chains", "Scan chains for existing balances").option("--chains <chains>", "Specific chains to enable (comma-separated)").option("--use-phantom-solana-path", "Use Phantom wallet derivation path for Solana").action(
|
|
5037
5674
|
withExit(
|
|
5038
5675
|
async (options) => {
|
|
5039
5676
|
const context = await init(program.opts().vault);
|
|
@@ -5061,7 +5698,8 @@ createFromSeedphraseCmd.command("secure").description("Create SecureVault from s
|
|
|
5061
5698
|
threshold: parseInt(options.threshold, 10),
|
|
5062
5699
|
shares: parseInt(options.shares, 10),
|
|
5063
5700
|
discoverChains: options.discoverChains,
|
|
5064
|
-
chains
|
|
5701
|
+
chains,
|
|
5702
|
+
usePhantomSolanaPath: options.usePhantomSolanaPath
|
|
5065
5703
|
});
|
|
5066
5704
|
}
|
|
5067
5705
|
)
|
|
@@ -5106,24 +5744,27 @@ program.command("verify <vaultId>").description("Verify vault with email verific
|
|
|
5106
5744
|
}
|
|
5107
5745
|
)
|
|
5108
5746
|
);
|
|
5109
|
-
program.command("balance [chain]").description("Show balance for a chain or all chains").option("-t, --tokens", "Include token balances").action(
|
|
5747
|
+
program.command("balance [chain]").description("Show balance for a chain or all chains").option("-t, --tokens", "Include token balances").option("--raw", "Show raw values (wei/satoshis) for programmatic use").action(
|
|
5110
5748
|
withExit(async (chainStr, options) => {
|
|
5111
5749
|
const context = await init(program.opts().vault);
|
|
5112
5750
|
await executeBalance(context, {
|
|
5113
5751
|
chain: chainStr ? findChainByName(chainStr) || chainStr : void 0,
|
|
5114
|
-
includeTokens: options.tokens
|
|
5752
|
+
includeTokens: options.tokens,
|
|
5753
|
+
raw: options.raw
|
|
5115
5754
|
});
|
|
5116
5755
|
})
|
|
5117
5756
|
);
|
|
5118
|
-
program.command("send <chain> <to>
|
|
5757
|
+
program.command("send <chain> <to> [amount]").description("Send tokens to an address").option("--max", "Send maximum amount (balance minus fees)").option("--token <tokenId>", "Token to send (default: native)").option("--memo <memo>", "Transaction memo").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
|
|
5119
5758
|
withExit(
|
|
5120
5759
|
async (chainStr, to, amount, options) => {
|
|
5760
|
+
if (!amount && !options.max) throw new Error("Provide an amount or use --max");
|
|
5761
|
+
if (amount && options.max) throw new Error("Cannot specify both amount and --max");
|
|
5121
5762
|
const context = await init(program.opts().vault);
|
|
5122
5763
|
try {
|
|
5123
5764
|
await executeSend(context, {
|
|
5124
5765
|
chain: findChainByName(chainStr) || chainStr,
|
|
5125
5766
|
to,
|
|
5126
|
-
amount,
|
|
5767
|
+
amount: amount ?? "max",
|
|
5127
5768
|
tokenId: options.token,
|
|
5128
5769
|
memo: options.memo,
|
|
5129
5770
|
yes: options.yes,
|
|
@@ -5139,6 +5780,30 @@ program.command("send <chain> <to> <amount>").description("Send tokens to an add
|
|
|
5139
5780
|
}
|
|
5140
5781
|
)
|
|
5141
5782
|
);
|
|
5783
|
+
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").action(
|
|
5784
|
+
withExit(
|
|
5785
|
+
async (chainStr, contract, msg, options) => {
|
|
5786
|
+
const context = await init(program.opts().vault, options.password);
|
|
5787
|
+
try {
|
|
5788
|
+
await executeExecute(context, {
|
|
5789
|
+
chain: findChainByName(chainStr) || chainStr,
|
|
5790
|
+
contract,
|
|
5791
|
+
msg,
|
|
5792
|
+
funds: options.funds,
|
|
5793
|
+
memo: options.memo,
|
|
5794
|
+
yes: options.yes,
|
|
5795
|
+
password: options.password
|
|
5796
|
+
});
|
|
5797
|
+
} catch (err) {
|
|
5798
|
+
if (err.message === "Transaction cancelled by user") {
|
|
5799
|
+
warn("\nx Transaction cancelled");
|
|
5800
|
+
return;
|
|
5801
|
+
}
|
|
5802
|
+
throw err;
|
|
5803
|
+
}
|
|
5804
|
+
}
|
|
5805
|
+
)
|
|
5806
|
+
);
|
|
5142
5807
|
program.command("sign").description("Sign pre-hashed bytes (for externally constructed transactions)").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--bytes <base64>", "Base64-encoded pre-hashed data to sign").option("--password <password>", "Vault password for signing").action(
|
|
5143
5808
|
withExit(async (options) => {
|
|
5144
5809
|
const context = await init(program.opts().vault, options.password);
|
|
@@ -5158,10 +5823,13 @@ program.command("broadcast").description("Broadcast a pre-signed raw transaction
|
|
|
5158
5823
|
});
|
|
5159
5824
|
})
|
|
5160
5825
|
);
|
|
5161
|
-
program.command("portfolio").description("Show total portfolio value").option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").action(
|
|
5826
|
+
program.command("portfolio").description("Show total portfolio value").option("-c, --currency <currency>", "Fiat currency (usd, eur, gbp, etc.)", "usd").option("--raw", "Show raw values (wei/satoshis) for programmatic use").action(
|
|
5162
5827
|
withExit(async (options) => {
|
|
5163
5828
|
const context = await init(program.opts().vault);
|
|
5164
|
-
await executePortfolio(context, {
|
|
5829
|
+
await executePortfolio(context, {
|
|
5830
|
+
currency: options.currency.toLowerCase(),
|
|
5831
|
+
raw: options.raw
|
|
5832
|
+
});
|
|
5165
5833
|
})
|
|
5166
5834
|
);
|
|
5167
5835
|
program.command("currency [newCurrency]").description("View or set the vault currency preference").action(
|
|
@@ -5176,6 +5844,12 @@ program.command("server").description("Check server connectivity and status").ac
|
|
|
5176
5844
|
await executeServer(context);
|
|
5177
5845
|
})
|
|
5178
5846
|
);
|
|
5847
|
+
program.command("discount").description("Show your VULT discount tier for swap fees").option("--refresh", "Force refresh tier from blockchain").action(
|
|
5848
|
+
withExit(async (options) => {
|
|
5849
|
+
const context = await init(program.opts().vault);
|
|
5850
|
+
await executeDiscount(context, { refresh: options.refresh });
|
|
5851
|
+
})
|
|
5852
|
+
);
|
|
5179
5853
|
program.command("export [path]").description("Export vault to file").option("--password <password>", "Password to unlock the vault (for encrypted vaults)").option("--exportPassword <password>", "Password to encrypt the exported file (defaults to --password)").action(
|
|
5180
5854
|
withExit(async (path3, options) => {
|
|
5181
5855
|
const context = await init(program.opts().vault, options.password);
|
|
@@ -5204,11 +5878,12 @@ program.command("address-book").description("Manage address book entries").optio
|
|
|
5204
5878
|
});
|
|
5205
5879
|
})
|
|
5206
5880
|
);
|
|
5207
|
-
program.command("chains").description("List and manage chains").option("--add <chain>", "Add a chain").option("--remove <chain>", "Remove a chain").action(
|
|
5881
|
+
program.command("chains").description("List and manage chains").option("--add <chain>", "Add a chain").option("--add-all", "Add all supported chains").option("--remove <chain>", "Remove a chain").action(
|
|
5208
5882
|
withExit(async (options) => {
|
|
5209
5883
|
const context = await init(program.opts().vault);
|
|
5210
5884
|
await executeChains(context, {
|
|
5211
5885
|
add: options.add ? findChainByName(options.add) || options.add : void 0,
|
|
5886
|
+
addAll: options.addAll,
|
|
5212
5887
|
remove: options.remove ? findChainByName(options.remove) || options.remove : void 0
|
|
5213
5888
|
});
|
|
5214
5889
|
})
|
|
@@ -5237,6 +5912,15 @@ program.command("info").description("Show detailed vault information").action(
|
|
|
5237
5912
|
await executeInfo(context);
|
|
5238
5913
|
})
|
|
5239
5914
|
);
|
|
5915
|
+
program.command("delete [vault]").description("Delete a vault from local storage").option("-y, --yes", "Skip confirmation prompt").action(
|
|
5916
|
+
withExit(async (vaultIdOrName, options) => {
|
|
5917
|
+
const context = await init(program.opts().vault);
|
|
5918
|
+
await executeDelete(context, {
|
|
5919
|
+
vaultId: vaultIdOrName,
|
|
5920
|
+
skipConfirmation: options.yes
|
|
5921
|
+
});
|
|
5922
|
+
})
|
|
5923
|
+
);
|
|
5240
5924
|
program.command("tokens <chain>").description("List and manage tokens for a chain").option("--add <contractAddress>", "Add a token by contract address").option("--remove <tokenId>", "Remove a token by ID").option("--symbol <symbol>", "Token symbol (for --add)").option("--name <name>", "Token name (for --add)").option("--decimals <decimals>", "Token decimals (for --add)", "18").action(
|
|
5241
5925
|
withExit(
|
|
5242
5926
|
async (chainStr, options) => {
|
|
@@ -5258,29 +5942,33 @@ program.command("swap-chains").description("List chains that support swaps").act
|
|
|
5258
5942
|
await executeSwapChains(context);
|
|
5259
5943
|
})
|
|
5260
5944
|
);
|
|
5261
|
-
program.command("swap-quote <fromChain> <toChain>
|
|
5945
|
+
program.command("swap-quote <fromChain> <toChain> [amount]").description("Get a swap quote without executing").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)").action(
|
|
5262
5946
|
withExit(
|
|
5263
5947
|
async (fromChainStr, toChainStr, amountStr, options) => {
|
|
5948
|
+
if (!amountStr && !options.max) throw new Error("Provide an amount or use --max");
|
|
5949
|
+
if (amountStr && options.max) throw new Error("Cannot specify both amount and --max");
|
|
5264
5950
|
const context = await init(program.opts().vault);
|
|
5265
5951
|
await executeSwapQuote(context, {
|
|
5266
5952
|
fromChain: findChainByName(fromChainStr) || fromChainStr,
|
|
5267
5953
|
toChain: findChainByName(toChainStr) || toChainStr,
|
|
5268
|
-
amount: parseFloat(amountStr),
|
|
5954
|
+
amount: options.max ? "max" : parseFloat(amountStr),
|
|
5269
5955
|
fromToken: options.fromToken,
|
|
5270
5956
|
toToken: options.toToken
|
|
5271
5957
|
});
|
|
5272
5958
|
}
|
|
5273
5959
|
)
|
|
5274
5960
|
);
|
|
5275
|
-
program.command("swap <fromChain> <toChain>
|
|
5961
|
+
program.command("swap <fromChain> <toChain> [amount]").description("Swap tokens between chains").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("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
|
|
5276
5962
|
withExit(
|
|
5277
5963
|
async (fromChainStr, toChainStr, amountStr, options) => {
|
|
5964
|
+
if (!amountStr && !options.max) throw new Error("Provide an amount or use --max");
|
|
5965
|
+
if (amountStr && options.max) throw new Error("Cannot specify both amount and --max");
|
|
5278
5966
|
const context = await init(program.opts().vault);
|
|
5279
5967
|
try {
|
|
5280
5968
|
await executeSwap(context, {
|
|
5281
5969
|
fromChain: findChainByName(fromChainStr) || fromChainStr,
|
|
5282
5970
|
toChain: findChainByName(toChainStr) || toChainStr,
|
|
5283
|
-
amount: parseFloat(amountStr),
|
|
5971
|
+
amount: options.max ? "max" : parseFloat(amountStr),
|
|
5284
5972
|
fromToken: options.fromToken,
|
|
5285
5973
|
toToken: options.toToken,
|
|
5286
5974
|
slippage: options.slippage ? parseFloat(options.slippage) : void 0,
|
|
@@ -5297,14 +5985,80 @@ program.command("swap <fromChain> <toChain> <amount>").description("Swap tokens
|
|
|
5297
5985
|
}
|
|
5298
5986
|
)
|
|
5299
5987
|
);
|
|
5988
|
+
var rujiraCmd = program.command("rujira").description("Rujira FIN swaps + secured asset tools on THORChain");
|
|
5989
|
+
rujiraCmd.command("balance").description("Show secured asset balances on THORChain").option("--secured-only", "Filter to secured/FIN-like denoms only").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
|
|
5990
|
+
withExit(async (options) => {
|
|
5991
|
+
const context = await init(program.opts().vault);
|
|
5992
|
+
await executeRujiraBalance(context, {
|
|
5993
|
+
securedOnly: options.securedOnly,
|
|
5994
|
+
rpcEndpoint: options.rpc,
|
|
5995
|
+
restEndpoint: options.rest
|
|
5996
|
+
});
|
|
5997
|
+
})
|
|
5998
|
+
);
|
|
5999
|
+
rujiraCmd.command("routes").description("List available FIN swap routes").action(
|
|
6000
|
+
withExit(async () => {
|
|
6001
|
+
await executeRujiraRoutes();
|
|
6002
|
+
})
|
|
6003
|
+
);
|
|
6004
|
+
rujiraCmd.command("deposit").description("Show deposit instructions (inbound address + memo)").option("--asset <asset>", "L1 asset to deposit (e.g., BTC.BTC, ETH.ETH)").option("--amount <amount>", "Amount in base units (optional; used for validation)", "1").option("--affiliate <thorAddress>", "Affiliate THOR address (optional)").option("--affiliate-bps <bps>", "Affiliate fee in basis points (optional)", "0").option("--rpc <url>", "Override THORChain RPC endpoint").option("--rest <url>", "Override THORNode REST endpoint").action(
|
|
6005
|
+
withExit(
|
|
6006
|
+
async (options) => {
|
|
6007
|
+
const context = await init(program.opts().vault);
|
|
6008
|
+
await executeRujiraDeposit(context, {
|
|
6009
|
+
asset: options.asset,
|
|
6010
|
+
amount: options.amount,
|
|
6011
|
+
affiliate: options.affiliate,
|
|
6012
|
+
affiliateBps: options.affiliateBps ? parseInt(options.affiliateBps, 10) : void 0,
|
|
6013
|
+
rpcEndpoint: options.rpc,
|
|
6014
|
+
restEndpoint: options.rest
|
|
6015
|
+
});
|
|
6016
|
+
}
|
|
6017
|
+
)
|
|
6018
|
+
);
|
|
6019
|
+
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(
|
|
6020
|
+
withExit(
|
|
6021
|
+
async (fromAsset, toAsset, amount, options) => {
|
|
6022
|
+
const context = await init(program.opts().vault, options.password);
|
|
6023
|
+
await executeRujiraSwap(context, {
|
|
6024
|
+
fromAsset,
|
|
6025
|
+
toAsset,
|
|
6026
|
+
amount,
|
|
6027
|
+
slippageBps: options.slippageBps ? parseInt(options.slippageBps, 10) : void 0,
|
|
6028
|
+
destination: options.destination,
|
|
6029
|
+
yes: options.yes,
|
|
6030
|
+
password: options.password,
|
|
6031
|
+
rpcEndpoint: options.rpc,
|
|
6032
|
+
restEndpoint: options.rest
|
|
6033
|
+
});
|
|
6034
|
+
}
|
|
6035
|
+
)
|
|
6036
|
+
);
|
|
6037
|
+
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(
|
|
6038
|
+
withExit(
|
|
6039
|
+
async (asset, amount, l1Address, options) => {
|
|
6040
|
+
const context = await init(program.opts().vault, options.password);
|
|
6041
|
+
await executeRujiraWithdraw(context, {
|
|
6042
|
+
asset,
|
|
6043
|
+
amount,
|
|
6044
|
+
l1Address,
|
|
6045
|
+
maxFeeBps: options.maxFeeBps ? parseInt(options.maxFeeBps, 10) : void 0,
|
|
6046
|
+
yes: options.yes,
|
|
6047
|
+
password: options.password,
|
|
6048
|
+
rpcEndpoint: options.rpc,
|
|
6049
|
+
restEndpoint: options.rest
|
|
6050
|
+
});
|
|
6051
|
+
}
|
|
6052
|
+
)
|
|
6053
|
+
);
|
|
5300
6054
|
program.command("version").description("Show detailed version information").action(
|
|
5301
6055
|
withExit(async () => {
|
|
5302
6056
|
printResult(formatVersionDetailed());
|
|
5303
6057
|
const result = await checkForUpdates();
|
|
5304
6058
|
if (result?.updateAvailable && result.latestVersion) {
|
|
5305
6059
|
info("");
|
|
5306
|
-
info(
|
|
5307
|
-
info(
|
|
6060
|
+
info(chalk13.yellow(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
|
|
6061
|
+
info(chalk13.gray(`Run "${getUpdateCommand()}" to update`));
|
|
5308
6062
|
}
|
|
5309
6063
|
})
|
|
5310
6064
|
);
|
|
@@ -5313,28 +6067,28 @@ program.command("update").description("Check for updates and show update command
|
|
|
5313
6067
|
info("Checking for updates...");
|
|
5314
6068
|
const result = await checkForUpdates();
|
|
5315
6069
|
if (!result) {
|
|
5316
|
-
printResult(
|
|
6070
|
+
printResult(chalk13.gray("Update checking is disabled"));
|
|
5317
6071
|
return;
|
|
5318
6072
|
}
|
|
5319
6073
|
if (result.updateAvailable && result.latestVersion) {
|
|
5320
6074
|
printResult("");
|
|
5321
|
-
printResult(
|
|
6075
|
+
printResult(chalk13.green(`Update available: ${result.currentVersion} -> ${result.latestVersion}`));
|
|
5322
6076
|
printResult("");
|
|
5323
6077
|
if (options.check) {
|
|
5324
6078
|
printResult(`Run "${getUpdateCommand()}" to update`);
|
|
5325
6079
|
} else {
|
|
5326
6080
|
const updateCmd = getUpdateCommand();
|
|
5327
6081
|
printResult(`To update, run:`);
|
|
5328
|
-
printResult(
|
|
6082
|
+
printResult(chalk13.cyan(` ${updateCmd}`));
|
|
5329
6083
|
}
|
|
5330
6084
|
} else {
|
|
5331
|
-
printResult(
|
|
6085
|
+
printResult(chalk13.green(`You're on the latest version (${result.currentVersion})`));
|
|
5332
6086
|
}
|
|
5333
6087
|
})
|
|
5334
6088
|
);
|
|
5335
6089
|
setupCompletionCommand(program);
|
|
5336
6090
|
async function startInteractiveMode() {
|
|
5337
|
-
const sdk = new
|
|
6091
|
+
const sdk = new Vultisig5({
|
|
5338
6092
|
onPasswordRequired: createPasswordCallback()
|
|
5339
6093
|
});
|
|
5340
6094
|
await sdk.initialize();
|