@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.
Files changed (3) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/index.js +540 -60
  3. 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 qrcode4 = new QRCode(-1, this.error);
1046
- qrcode4.addData(input);
1047
- qrcode4.make();
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 = qrcode4.getModuleCount();
1052
- var moduleData = qrcode4.modules.slice();
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(qrcode4.getModuleCount() + 3);
1085
+ var border = repeat(white).times(qrcode5.getModuleCount() + 3);
1086
1086
  output += border + "\n";
1087
- qrcode4.modules.forEach(function(row2) {
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 { parseKeygenQR, Vultisig as Vultisig4 } from "@vultisig/sdk";
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, raw = false) {
1449
+ function displayBalance(chain, balance, _raw = false) {
1450
1450
  printResult(chalk2.cyan(`
1451
1451
  ${chain} Balance:`));
1452
- const displayAmount = raw ? balance.amount : formatBalanceAmount(balance.amount, balance.decimals);
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, raw = false) {
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: raw ? balance.amount : formatBalanceAmount(balance.amount, balance.decimals),
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, raw = false) {
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: raw ? balance.amount : formatBalanceAmount(balance.amount, balance.decimals),
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.amount}`));
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
- if (isNaN(parseFloat(params.amount)) || parseFloat(params.amount) <= 0) {
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 amount = BigInt(Math.floor(parseFloat(params.amount) * Math.pow(10, balance.decimals)));
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
- params.amount,
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/sign.ts
2055
+ // src/commands/execute.ts
2046
2056
  var import_qrcode_terminal2 = __toESM(require_main(), 1);
2047
- import { Chain as Chain2 } from "@vultisig/sdk";
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(Chain2).includes(params.chain)) {
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
- import_qrcode_terminal2.default.generate(qrPayload, { small: true });
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 Chain3, Vultisig as Vultisig3 } from "@vultisig/sdk";
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(Chain3).includes(params.chain)) {
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: Vultisig3.getTxExplorerUrl(params.chain, txHash)
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 import_qrcode_terminal3 = __toESM(require_main(), 1);
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
- import_qrcode_terminal3.default.generate(qrPayload, { small: true });
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
- import_qrcode_terminal3.default.generate(qrPayload, { small: true });
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
- if (isNaN(options.amount) || options.amount <= 0) {
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: options.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: options.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, String(options.amount), quote.fromCoin.ticker, quote.toCoin.ticker, {
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
- if (isNaN(options.amount) || options.amount <= 0) {
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: options.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, String(options.amount), quote.fromCoin.ticker, quote.toCoin.ticker, {
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: options.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 Chain4, fiatCurrencies as fiatCurrencies2, fiatCurrencyNameRecord as fiatCurrencyNameRecord3 } from "@vultisig/sdk";
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(Chain4)
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 Chain5 } from "@vultisig/sdk";
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(Chain5);
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(Chain5);
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.4.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 Vultisig4({
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> <amount>").description("Send tokens to an address").option("--token <tokenId>", "Token to send (default: native)").option("--memo <memo>", "Transaction memo").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
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> <amount>").description("Get a swap quote without executing").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").action(
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> <amount>").description("Swap tokens between chains").option("--from-token <address>", "Token address to swap from (default: native)").option("--to-token <address>", "Token address to swap to (default: native)").option("--slippage <percent>", "Slippage tolerance in percent", "1").option("-y, --yes", "Skip confirmation prompt").option("--password <password>", "Vault password for signing").action(
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 Vultisig4({
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.4.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
- "@vultisig/sdk": "^0.4.0",
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",