@vultisig/cli 0.4.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 +31 -0
- package/dist/index.js +540 -60
- package/package.json +7 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
# @vultisig/cli
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#97](https://github.com/vultisig/vultisig-sdk/pull/97) [`cd57d64`](https://github.com/vultisig/vultisig-sdk/commit/cd57d6482e08bd6172550ec4eea0e0233abd7f76) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Add max send/swap support across SDK, CLI, and example apps
|
|
8
|
+
- Add `vault.getMaxSendAmount()` returning `{ balance, fee, maxSendable }` for fee-accurate max sends
|
|
9
|
+
- Add `vault.estimateSendFee()` for gas estimation without max calculation
|
|
10
|
+
- Enrich `getSwapQuote()` with `balance` and `maxSwapable` fields
|
|
11
|
+
- CLI: Add `--max` flag to `send`, `swap`, and `swap-quote` commands
|
|
12
|
+
- Browser/Electron examples: Add "Max" button to Send and Swap screens
|
|
13
|
+
- Fix native token ticker resolution in example swap UI (was using chain name instead of ticker)
|
|
14
|
+
|
|
15
|
+
- [#97](https://github.com/vultisig/vultisig-sdk/pull/97) [`75f441c`](https://github.com/vultisig/vultisig-sdk/commit/75f441cdf711e6ba04eed412dcf34002c5705144) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - Add Rujira DEX integration with FIN order book swaps, secured asset deposits/withdrawals, and CLI commands. New package: @vultisig/rujira for THORChain DEX operations (includes asset registry).
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#97](https://github.com/vultisig/vultisig-sdk/pull/97) [`e172aff`](https://github.com/vultisig/vultisig-sdk/commit/e172aff35aff86d182646a521dc1e3ac9e381f60) Thanks [@bornslippynuxx](https://github.com/bornslippynuxx)! - fix: address PR review bugs and safety issues
|
|
20
|
+
- Fix missing ChromeExtensionPolyfills import causing build failure
|
|
21
|
+
- Fix floating-point precision loss in CLI amount parsing for high-decimal tokens
|
|
22
|
+
- Fix BigInt crash on non-integer amount strings in swap validation
|
|
23
|
+
- Fix Number exponentiation precision loss in VaultSend formatAmount
|
|
24
|
+
- Use VaultError with error codes in chain validation instead of generic Error
|
|
25
|
+
- Add chainId mismatch validation in signAndBroadcast
|
|
26
|
+
- Add hex string input validation in hexDecode
|
|
27
|
+
- Guard against empty accounts array in client getAddress
|
|
28
|
+
- Use stricter bech32 THORChain address validator in deposit module
|
|
29
|
+
|
|
30
|
+
- Updated dependencies [[`bd543af`](https://github.com/vultisig/vultisig-sdk/commit/bd543af73a50a4ce431f38e3ed77511c4ef65ea7), [`74516fa`](https://github.com/vultisig/vultisig-sdk/commit/74516fae8dabd844c9e0793b932f6284ce9aa009), [`7ceab79`](https://github.com/vultisig/vultisig-sdk/commit/7ceab79e53986bfefa3f5d4cb5d25855572fbd3f), [`cd57d64`](https://github.com/vultisig/vultisig-sdk/commit/cd57d6482e08bd6172550ec4eea0e0233abd7f76), [`e172aff`](https://github.com/vultisig/vultisig-sdk/commit/e172aff35aff86d182646a521dc1e3ac9e381f60), [`75f441c`](https://github.com/vultisig/vultisig-sdk/commit/75f441cdf711e6ba04eed412dcf34002c5705144), [`ea1e8d5`](https://github.com/vultisig/vultisig-sdk/commit/ea1e8d5dd14a7273021577471e44719609f983ca), [`3f5fdcb`](https://github.com/vultisig/vultisig-sdk/commit/3f5fdcbfbe23aa287dfbcb38e9be6c904af9caf0), [`6c5c77c`](https://github.com/vultisig/vultisig-sdk/commit/6c5c77ceb49620f711285effee98b052e6aab1f8)]:
|
|
31
|
+
- @vultisig/sdk@0.5.0
|
|
32
|
+
- @vultisig/rujira@1.0.0
|
|
33
|
+
|
|
3
34
|
## 0.4.0
|
|
4
35
|
|
|
5
36
|
### Minor Changes
|
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 {
|
|
1106
|
+
import { promises as fs3 } from "node:fs";
|
|
1107
|
+
import { parseKeygenQR, Vultisig as Vultisig5 } from "@vultisig/sdk";
|
|
1107
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,26 +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
|
-
|
|
1453
|
-
printResult(` Amount: ${displayAmount} ${balance.symbol}`);
|
|
1452
|
+
printResult(` Amount: ${balance.formattedAmount} ${balance.symbol}`);
|
|
1454
1453
|
if (balance.fiatValue && balance.fiatCurrency) {
|
|
1455
1454
|
printResult(` Value: ${balance.fiatValue.toFixed(2)} ${balance.fiatCurrency}`);
|
|
1456
1455
|
}
|
|
1457
1456
|
}
|
|
1458
|
-
function displayBalancesTable(balances,
|
|
1457
|
+
function displayBalancesTable(balances, _raw = false) {
|
|
1459
1458
|
printResult(chalk2.cyan("\nPortfolio Balances:\n"));
|
|
1460
1459
|
const tableData = Object.entries(balances).map(([chain, balance]) => ({
|
|
1461
1460
|
Chain: chain,
|
|
1462
|
-
Amount:
|
|
1461
|
+
Amount: balance.formattedAmount,
|
|
1463
1462
|
Symbol: balance.symbol,
|
|
1464
1463
|
Value: balance.fiatValue && balance.fiatCurrency ? `${balance.fiatValue.toFixed(2)} ${balance.fiatCurrency}` : "N/A"
|
|
1465
1464
|
}));
|
|
1466
1465
|
printTable(tableData);
|
|
1467
1466
|
}
|
|
1468
|
-
function displayPortfolio(portfolio, currency,
|
|
1467
|
+
function displayPortfolio(portfolio, currency, _raw = false) {
|
|
1469
1468
|
const currencyName = fiatCurrencyNameRecord[currency];
|
|
1470
1469
|
printResult(chalk2.cyan("\n+----------------------------------------+"));
|
|
1471
1470
|
printResult(chalk2.cyan(`| Portfolio Total Value (${currencyName}) |`));
|
|
@@ -1476,7 +1475,7 @@ function displayPortfolio(portfolio, currency, raw = false) {
|
|
|
1476
1475
|
printResult(chalk2.bold("Chain Breakdown:\n"));
|
|
1477
1476
|
const table = portfolio.chainBalances.map(({ chain, balance, value }) => ({
|
|
1478
1477
|
Chain: chain,
|
|
1479
|
-
Amount:
|
|
1478
|
+
Amount: balance.formattedAmount,
|
|
1480
1479
|
Symbol: balance.symbol,
|
|
1481
1480
|
Value: value ? `${value.amount} ${value.currency.toUpperCase()}` : "N/A"
|
|
1482
1481
|
}));
|
|
@@ -1572,7 +1571,7 @@ async function confirmTransaction() {
|
|
|
1572
1571
|
function setupVaultEvents(vault) {
|
|
1573
1572
|
vault.on("balanceUpdated", ({ chain, balance, tokenId }) => {
|
|
1574
1573
|
const asset = tokenId ? `${balance.symbol} token` : balance.symbol;
|
|
1575
|
-
info(chalk2.blue(`i Balance updated for ${chain} (${asset}): ${balance.
|
|
1574
|
+
info(chalk2.blue(`i Balance updated for ${chain} (${asset}): ${balance.formattedAmount}`));
|
|
1576
1575
|
});
|
|
1577
1576
|
vault.on("transactionBroadcast", ({ chain, txHash }) => {
|
|
1578
1577
|
info(chalk2.green(`+ Transaction broadcast on ${chain}`));
|
|
@@ -1612,14 +1611,6 @@ function formatBigintAmount(amount, decimals) {
|
|
|
1612
1611
|
const trimmed = fractionStr.replace(/0+$/, "");
|
|
1613
1612
|
return `${whole}.${trimmed}`;
|
|
1614
1613
|
}
|
|
1615
|
-
function formatBalanceAmount(amount, decimals) {
|
|
1616
|
-
if (!amount || amount === "0") return "0";
|
|
1617
|
-
try {
|
|
1618
|
-
return formatBigintAmount(BigInt(amount), decimals);
|
|
1619
|
-
} catch {
|
|
1620
|
-
return amount;
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
1614
|
function displaySwapPreview(quote, fromAmount, fromSymbol, toSymbol, options) {
|
|
1624
1615
|
const estimatedOutputFormatted = formatBigintAmount(quote.estimatedOutput, options.toDecimals);
|
|
1625
1616
|
printResult(chalk2.cyan("\nSwap Preview:"));
|
|
@@ -1923,7 +1914,8 @@ async function executeSend(ctx2, params) {
|
|
|
1923
1914
|
if (!Object.values(Chain).includes(params.chain)) {
|
|
1924
1915
|
throw new Error(`Invalid chain: ${params.chain}`);
|
|
1925
1916
|
}
|
|
1926
|
-
|
|
1917
|
+
const isMax = params.amount === "max";
|
|
1918
|
+
if (!isMax && (isNaN(parseFloat(params.amount)) || parseFloat(params.amount) <= 0)) {
|
|
1927
1919
|
throw new Error("Invalid amount");
|
|
1928
1920
|
}
|
|
1929
1921
|
return sendTransaction(vault, params);
|
|
@@ -1939,7 +1931,25 @@ async function sendTransaction(vault, params) {
|
|
|
1939
1931
|
ticker: balance.symbol,
|
|
1940
1932
|
id: params.tokenId
|
|
1941
1933
|
};
|
|
1942
|
-
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
|
+
}
|
|
1943
1953
|
const payload = await vault.prepareSendTx({
|
|
1944
1954
|
coin,
|
|
1945
1955
|
receiver: params.to,
|
|
@@ -1957,7 +1967,7 @@ async function sendTransaction(vault, params) {
|
|
|
1957
1967
|
displayTransactionPreview(
|
|
1958
1968
|
payload.coin.address,
|
|
1959
1969
|
params.to,
|
|
1960
|
-
|
|
1970
|
+
displayAmount,
|
|
1961
1971
|
payload.coin.ticker,
|
|
1962
1972
|
params.chain,
|
|
1963
1973
|
params.memo,
|
|
@@ -2042,12 +2052,175 @@ Or use this URL: ${qrPayload}
|
|
|
2042
2052
|
}
|
|
2043
2053
|
}
|
|
2044
2054
|
|
|
2045
|
-
// src/commands/
|
|
2055
|
+
// src/commands/execute.ts
|
|
2046
2056
|
var import_qrcode_terminal2 = __toESM(require_main(), 1);
|
|
2047
|
-
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";
|
|
2048
2221
|
async function executeSignBytes(ctx2, params) {
|
|
2049
2222
|
const vault = await ctx2.ensureActiveVault();
|
|
2050
|
-
if (!Object.values(
|
|
2223
|
+
if (!Object.values(Chain3).includes(params.chain)) {
|
|
2051
2224
|
throw new Error(`Invalid chain: ${params.chain}`);
|
|
2052
2225
|
}
|
|
2053
2226
|
return signBytes(vault, params);
|
|
@@ -2069,7 +2242,7 @@ async function signBytes(vault, params) {
|
|
|
2069
2242
|
} else {
|
|
2070
2243
|
signSpinner.stop();
|
|
2071
2244
|
info("\nScan this QR code with your Vultisig mobile app to sign:");
|
|
2072
|
-
|
|
2245
|
+
import_qrcode_terminal3.default.generate(qrPayload, { small: true });
|
|
2073
2246
|
info(`
|
|
2074
2247
|
Or use this URL: ${qrPayload}
|
|
2075
2248
|
`);
|
|
@@ -2123,10 +2296,10 @@ Or use this URL: ${qrPayload}
|
|
|
2123
2296
|
}
|
|
2124
2297
|
|
|
2125
2298
|
// src/commands/broadcast.ts
|
|
2126
|
-
import { Chain as
|
|
2299
|
+
import { Chain as Chain4, Vultisig as Vultisig4 } from "@vultisig/sdk";
|
|
2127
2300
|
async function executeBroadcast(ctx2, params) {
|
|
2128
2301
|
const vault = await ctx2.ensureActiveVault();
|
|
2129
|
-
if (!Object.values(
|
|
2302
|
+
if (!Object.values(Chain4).includes(params.chain)) {
|
|
2130
2303
|
throw new Error(`Invalid chain: ${params.chain}`);
|
|
2131
2304
|
}
|
|
2132
2305
|
const broadcastSpinner = createSpinner("Broadcasting transaction...");
|
|
@@ -2139,7 +2312,7 @@ async function executeBroadcast(ctx2, params) {
|
|
|
2139
2312
|
const result = {
|
|
2140
2313
|
txHash,
|
|
2141
2314
|
chain: params.chain,
|
|
2142
|
-
explorerUrl:
|
|
2315
|
+
explorerUrl: Vultisig4.getTxExplorerUrl(params.chain, txHash)
|
|
2143
2316
|
};
|
|
2144
2317
|
if (isJsonOutput()) {
|
|
2145
2318
|
outputJson(result);
|
|
@@ -2155,7 +2328,7 @@ async function executeBroadcast(ctx2, params) {
|
|
|
2155
2328
|
}
|
|
2156
2329
|
|
|
2157
2330
|
// src/commands/vault-management.ts
|
|
2158
|
-
var
|
|
2331
|
+
var import_qrcode_terminal4 = __toESM(require_main(), 1);
|
|
2159
2332
|
import chalk5 from "chalk";
|
|
2160
2333
|
import { promises as fs } from "fs";
|
|
2161
2334
|
import inquirer4 from "inquirer";
|
|
@@ -2276,7 +2449,7 @@ async function executeCreateSecure(ctx2, options) {
|
|
|
2276
2449
|
} else {
|
|
2277
2450
|
spinner.stop();
|
|
2278
2451
|
info("\nScan this QR code with your Vultisig mobile app:");
|
|
2279
|
-
|
|
2452
|
+
import_qrcode_terminal4.default.generate(qrPayload, { small: true });
|
|
2280
2453
|
info(`
|
|
2281
2454
|
Or use this URL: ${qrPayload}
|
|
2282
2455
|
`);
|
|
@@ -2733,7 +2906,7 @@ async function executeCreateFromSeedphraseSecure(ctx2, options) {
|
|
|
2733
2906
|
} else {
|
|
2734
2907
|
importSpinner.stop();
|
|
2735
2908
|
info("\nScan this QR code with your Vultisig mobile app:");
|
|
2736
|
-
|
|
2909
|
+
import_qrcode_terminal4.default.generate(qrPayload, { small: true });
|
|
2737
2910
|
info(`
|
|
2738
2911
|
Or use this URL: ${qrPayload}
|
|
2739
2912
|
`);
|
|
@@ -2918,34 +3091,47 @@ async function executeSwapChains(ctx2) {
|
|
|
2918
3091
|
}
|
|
2919
3092
|
async function executeSwapQuote(ctx2, options) {
|
|
2920
3093
|
const vault = await ctx2.ensureActiveVault();
|
|
2921
|
-
|
|
3094
|
+
const isMax = options.amount === "max";
|
|
3095
|
+
if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
|
|
2922
3096
|
throw new Error("Invalid amount");
|
|
2923
3097
|
}
|
|
2924
3098
|
const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
|
|
2925
3099
|
if (!isSupported) {
|
|
2926
3100
|
throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
|
|
2927
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
|
+
}
|
|
2928
3112
|
const spinner = createSpinner("Getting swap quote...");
|
|
2929
3113
|
const quote = await vault.getSwapQuote({
|
|
2930
3114
|
fromCoin: { chain: options.fromChain, token: options.fromToken },
|
|
2931
3115
|
toCoin: { chain: options.toChain, token: options.toToken },
|
|
2932
|
-
amount:
|
|
3116
|
+
amount: resolvedAmount,
|
|
2933
3117
|
fiatCurrency: "usd"
|
|
2934
3118
|
// Request fiat conversion
|
|
2935
3119
|
});
|
|
2936
3120
|
spinner.succeed("Quote received");
|
|
3121
|
+
const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(resolvedAmount);
|
|
2937
3122
|
if (isJsonOutput()) {
|
|
2938
3123
|
outputJson({
|
|
2939
3124
|
fromChain: options.fromChain,
|
|
2940
3125
|
toChain: options.toChain,
|
|
2941
|
-
amount:
|
|
3126
|
+
amount: resolvedAmount,
|
|
3127
|
+
isMax,
|
|
2942
3128
|
quote
|
|
2943
3129
|
});
|
|
2944
3130
|
return quote;
|
|
2945
3131
|
}
|
|
2946
3132
|
const feeBalance = await vault.balance(options.fromChain);
|
|
2947
3133
|
const discountTier = await vault.getDiscountTier();
|
|
2948
|
-
displaySwapPreview(quote,
|
|
3134
|
+
displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
2949
3135
|
fromDecimals: quote.fromCoin.decimals,
|
|
2950
3136
|
toDecimals: quote.toCoin.decimals,
|
|
2951
3137
|
feeDecimals: feeBalance.decimals,
|
|
@@ -2957,26 +3143,38 @@ async function executeSwapQuote(ctx2, options) {
|
|
|
2957
3143
|
}
|
|
2958
3144
|
async function executeSwap(ctx2, options) {
|
|
2959
3145
|
const vault = await ctx2.ensureActiveVault();
|
|
2960
|
-
|
|
3146
|
+
const isMax = options.amount === "max";
|
|
3147
|
+
if (!isMax && (isNaN(options.amount) || options.amount <= 0)) {
|
|
2961
3148
|
throw new Error("Invalid amount");
|
|
2962
3149
|
}
|
|
2963
3150
|
const isSupported = await vault.isSwapSupported(options.fromChain, options.toChain);
|
|
2964
3151
|
if (!isSupported) {
|
|
2965
3152
|
throw new Error(`Swaps from ${options.fromChain} to ${options.toChain} are not supported`);
|
|
2966
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
|
+
}
|
|
2967
3164
|
const quoteSpinner = createSpinner("Getting swap quote...");
|
|
2968
3165
|
const quote = await vault.getSwapQuote({
|
|
2969
3166
|
fromCoin: { chain: options.fromChain, token: options.fromToken },
|
|
2970
3167
|
toCoin: { chain: options.toChain, token: options.toToken },
|
|
2971
|
-
amount:
|
|
3168
|
+
amount: resolvedAmount,
|
|
2972
3169
|
fiatCurrency: "usd"
|
|
2973
3170
|
// Request fiat conversion
|
|
2974
3171
|
});
|
|
2975
3172
|
quoteSpinner.succeed("Quote received");
|
|
3173
|
+
const fromAmountDisplay = isMax ? `${formatBigintAmount(quote.maxSwapable, quote.fromCoin.decimals)} (max)` : String(resolvedAmount);
|
|
2976
3174
|
const feeBalance = await vault.balance(options.fromChain);
|
|
2977
3175
|
const discountTier = await vault.getDiscountTier();
|
|
2978
3176
|
if (!isJsonOutput()) {
|
|
2979
|
-
displaySwapPreview(quote,
|
|
3177
|
+
displaySwapPreview(quote, fromAmountDisplay, quote.fromCoin.ticker, quote.toCoin.ticker, {
|
|
2980
3178
|
fromDecimals: quote.fromCoin.decimals,
|
|
2981
3179
|
toDecimals: quote.toCoin.decimals,
|
|
2982
3180
|
feeDecimals: feeBalance.decimals,
|
|
@@ -2995,7 +3193,7 @@ async function executeSwap(ctx2, options) {
|
|
|
2995
3193
|
const { keysignPayload, approvalPayload } = await vault.prepareSwapTx({
|
|
2996
3194
|
fromCoin: { chain: options.fromChain, token: options.fromToken },
|
|
2997
3195
|
toCoin: { chain: options.toChain, token: options.toToken },
|
|
2998
|
-
amount:
|
|
3196
|
+
amount: resolvedAmount,
|
|
2999
3197
|
swapQuote: quote,
|
|
3000
3198
|
autoApprove: false
|
|
3001
3199
|
});
|
|
@@ -3070,7 +3268,7 @@ async function executeSwap(ctx2, options) {
|
|
|
3070
3268
|
}
|
|
3071
3269
|
|
|
3072
3270
|
// src/commands/settings.ts
|
|
3073
|
-
import { Chain as
|
|
3271
|
+
import { Chain as Chain5, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
|
|
3074
3272
|
import chalk6 from "chalk";
|
|
3075
3273
|
import inquirer5 from "inquirer";
|
|
3076
3274
|
async function executeCurrency(ctx2, newCurrency) {
|
|
@@ -3138,7 +3336,7 @@ async function executeAddressBook(ctx2, options = {}) {
|
|
|
3138
3336
|
type: "list",
|
|
3139
3337
|
name: "chain",
|
|
3140
3338
|
message: "Select chain:",
|
|
3141
|
-
choices: Object.values(
|
|
3339
|
+
choices: Object.values(Chain5)
|
|
3142
3340
|
});
|
|
3143
3341
|
}
|
|
3144
3342
|
if (!address) {
|
|
@@ -3214,6 +3412,192 @@ Address Book${options.chain ? ` (${options.chain})` : ""}:
|
|
|
3214
3412
|
return allEntries;
|
|
3215
3413
|
}
|
|
3216
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
|
+
|
|
3217
3601
|
// src/commands/discount.ts
|
|
3218
3602
|
import {
|
|
3219
3603
|
baseAffiliateBps,
|
|
@@ -3313,7 +3697,7 @@ function displayDiscountTier(tierInfo) {
|
|
|
3313
3697
|
}
|
|
3314
3698
|
|
|
3315
3699
|
// src/interactive/completer.ts
|
|
3316
|
-
import { Chain as
|
|
3700
|
+
import { Chain as Chain6 } from "@vultisig/sdk";
|
|
3317
3701
|
import fs2 from "fs";
|
|
3318
3702
|
import path2 from "path";
|
|
3319
3703
|
var COMMANDS = [
|
|
@@ -3456,7 +3840,7 @@ function completeVaultName(ctx2, partial) {
|
|
|
3456
3840
|
return [show, partial];
|
|
3457
3841
|
}
|
|
3458
3842
|
function completeChainName(partial) {
|
|
3459
|
-
const allChains = Object.values(
|
|
3843
|
+
const allChains = Object.values(Chain6);
|
|
3460
3844
|
const partialLower = partial.toLowerCase();
|
|
3461
3845
|
const matches = allChains.filter((chain) => chain.toLowerCase().startsWith(partialLower));
|
|
3462
3846
|
matches.sort();
|
|
@@ -3464,7 +3848,7 @@ function completeChainName(partial) {
|
|
|
3464
3848
|
return [show, partial];
|
|
3465
3849
|
}
|
|
3466
3850
|
function findChainByName(name) {
|
|
3467
|
-
const allChains = Object.values(
|
|
3851
|
+
const allChains = Object.values(Chain6);
|
|
3468
3852
|
const nameLower = name.toLowerCase();
|
|
3469
3853
|
const found = allChains.find((chain) => chain.toLowerCase() === nameLower);
|
|
3470
3854
|
return found ? found : null;
|
|
@@ -4694,7 +5078,7 @@ var cachedVersion = null;
|
|
|
4694
5078
|
function getVersion() {
|
|
4695
5079
|
if (cachedVersion) return cachedVersion;
|
|
4696
5080
|
if (true) {
|
|
4697
|
-
cachedVersion = "0.
|
|
5081
|
+
cachedVersion = "0.5.0";
|
|
4698
5082
|
return cachedVersion;
|
|
4699
5083
|
}
|
|
4700
5084
|
try {
|
|
@@ -5168,7 +5552,7 @@ async function init(vaultOverride, unlockPassword) {
|
|
|
5168
5552
|
if (unlockPassword && vaultSelector) {
|
|
5169
5553
|
cachePassword(vaultSelector, unlockPassword);
|
|
5170
5554
|
}
|
|
5171
|
-
const sdk = new
|
|
5555
|
+
const sdk = new Vultisig5({
|
|
5172
5556
|
onPasswordRequired: createPasswordCallback()
|
|
5173
5557
|
});
|
|
5174
5558
|
await sdk.initialize();
|
|
@@ -5370,15 +5754,17 @@ program.command("balance [chain]").description("Show balance for a chain or all
|
|
|
5370
5754
|
});
|
|
5371
5755
|
})
|
|
5372
5756
|
);
|
|
5373
|
-
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(
|
|
5374
5758
|
withExit(
|
|
5375
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");
|
|
5376
5762
|
const context = await init(program.opts().vault);
|
|
5377
5763
|
try {
|
|
5378
5764
|
await executeSend(context, {
|
|
5379
5765
|
chain: findChainByName(chainStr) || chainStr,
|
|
5380
5766
|
to,
|
|
5381
|
-
amount,
|
|
5767
|
+
amount: amount ?? "max",
|
|
5382
5768
|
tokenId: options.token,
|
|
5383
5769
|
memo: options.memo,
|
|
5384
5770
|
yes: options.yes,
|
|
@@ -5394,6 +5780,30 @@ program.command("send <chain> <to> <amount>").description("Send tokens to an add
|
|
|
5394
5780
|
}
|
|
5395
5781
|
)
|
|
5396
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
|
+
);
|
|
5397
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(
|
|
5398
5808
|
withExit(async (options) => {
|
|
5399
5809
|
const context = await init(program.opts().vault, options.password);
|
|
@@ -5532,29 +5942,33 @@ program.command("swap-chains").description("List chains that support swaps").act
|
|
|
5532
5942
|
await executeSwapChains(context);
|
|
5533
5943
|
})
|
|
5534
5944
|
);
|
|
5535
|
-
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(
|
|
5536
5946
|
withExit(
|
|
5537
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");
|
|
5538
5950
|
const context = await init(program.opts().vault);
|
|
5539
5951
|
await executeSwapQuote(context, {
|
|
5540
5952
|
fromChain: findChainByName(fromChainStr) || fromChainStr,
|
|
5541
5953
|
toChain: findChainByName(toChainStr) || toChainStr,
|
|
5542
|
-
amount: parseFloat(amountStr),
|
|
5954
|
+
amount: options.max ? "max" : parseFloat(amountStr),
|
|
5543
5955
|
fromToken: options.fromToken,
|
|
5544
5956
|
toToken: options.toToken
|
|
5545
5957
|
});
|
|
5546
5958
|
}
|
|
5547
5959
|
)
|
|
5548
5960
|
);
|
|
5549
|
-
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(
|
|
5550
5962
|
withExit(
|
|
5551
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");
|
|
5552
5966
|
const context = await init(program.opts().vault);
|
|
5553
5967
|
try {
|
|
5554
5968
|
await executeSwap(context, {
|
|
5555
5969
|
fromChain: findChainByName(fromChainStr) || fromChainStr,
|
|
5556
5970
|
toChain: findChainByName(toChainStr) || toChainStr,
|
|
5557
|
-
amount: parseFloat(amountStr),
|
|
5971
|
+
amount: options.max ? "max" : parseFloat(amountStr),
|
|
5558
5972
|
fromToken: options.fromToken,
|
|
5559
5973
|
toToken: options.toToken,
|
|
5560
5974
|
slippage: options.slippage ? parseFloat(options.slippage) : void 0,
|
|
@@ -5571,6 +5985,72 @@ program.command("swap <fromChain> <toChain> <amount>").description("Swap tokens
|
|
|
5571
5985
|
}
|
|
5572
5986
|
)
|
|
5573
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
|
+
);
|
|
5574
6054
|
program.command("version").description("Show detailed version information").action(
|
|
5575
6055
|
withExit(async () => {
|
|
5576
6056
|
printResult(formatVersionDetailed());
|
|
@@ -5608,7 +6088,7 @@ program.command("update").description("Check for updates and show update command
|
|
|
5608
6088
|
);
|
|
5609
6089
|
setupCompletionCommand(program);
|
|
5610
6090
|
async function startInteractiveMode() {
|
|
5611
|
-
const sdk = new
|
|
6091
|
+
const sdk = new Vultisig5({
|
|
5612
6092
|
onPasswordRequired: createPasswordCallback()
|
|
5613
6093
|
});
|
|
5614
6094
|
await sdk.initialize();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vultisig/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Command-line wallet for Vultisig - multi-chain MPC wallet management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -48,7 +48,12 @@
|
|
|
48
48
|
},
|
|
49
49
|
"homepage": "https://vultisig.com",
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"@
|
|
51
|
+
"@cosmjs/cosmwasm-stargate": "^0.32.4",
|
|
52
|
+
"@cosmjs/encoding": "^0.32.4",
|
|
53
|
+
"@cosmjs/proto-signing": "^0.32.4",
|
|
54
|
+
"@cosmjs/stargate": "^0.32.4",
|
|
55
|
+
"@vultisig/rujira": "^1.0.0",
|
|
56
|
+
"@vultisig/sdk": "^0.5.0",
|
|
52
57
|
"chalk": "^5.3.0",
|
|
53
58
|
"cli-table3": "^0.6.5",
|
|
54
59
|
"commander": "^12.0.0",
|