@vultisig/cli 3.0.0 → 4.0.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 CHANGED
@@ -1,5 +1,38 @@
1
1
  # @vultisig/cli
2
2
 
3
+ ## 4.0.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#1608](https://github.com/vultisig/vultisig-sdk/pull/1608) [`791d344`](https://github.com/vultisig/vultisig-sdk/commit/791d344c2dacc289c9fb7fbcbc38488aeeb47542) Thanks [@neavra](https://github.com/neavra)! - Fix tracked token balance fetching and make token removal report the truth.
8
+
9
+ A token added with `tokens --add` is stored under an id of the form `<Chain>-<address>`. That id was passed to the RPC as if it were a contract address; the RPC rejected it, the throw was swallowed per-chain, and the **whole chain's balances disappeared** — native asset included — leaving only a `console.warn`. Balance lookups now resolve a stored token reference to its real contract address / chain-level asset id before both the per-coin and batched calls. What is written to the vault file is unchanged, and balance result keys still use the stored id.
10
+
11
+ **Behavior change — `tokens --remove` now fails when nothing was removed.** Removal previously matched on an exact id comparison, so removing by symbol (or by contract address for a token added under the prefixed id form) matched nothing while the command still printed `Removed token …` and exited 0. Removal now goes through the shared token resolver, and a reference that matches no tracked token raises the existing not-found error and exits **5** (`RESOURCE_NOT_FOUND`) instead of exiting 0. A script that removed an untracked token and relied on a zero exit will now see a failure. Removal by symbol and by contract address, which previously silently no-op'd, now work.
12
+
13
+ **Behavior change — the agent `vault_coin remove` tool reports per-coin outcomes.** It previously returned `removed: true` unconditionally; it now returns the SDK's actual result, so the model is told when a coin was not tracked. Batch removals return `{ chain, tokenId, removed }` per coin rather than `{ chain, tokenId }`.
14
+
15
+ **Breaking SDK API change — `VaultBase.removeToken` now returns `Promise<boolean>` instead of `Promise<void>`.** It resolves to `true` when a tracked token was removed and persisted, and `false` when the reference matched nothing. Consumers with callbacks, interfaces, or overrides typed to the previous `Promise<void>` signature must update those types.
16
+
17
+ ### Patch Changes
18
+
19
+ - [#1642](https://github.com/vultisig/vultisig-sdk/pull/1642) [`ac5becb`](https://github.com/vultisig/vultisig-sdk/commit/ac5becb7939bc8c6b45d04cd00067e0d2caf6297) Thanks [@neavra](https://github.com/neavra)! - Show the user-visible memo and XRP destination tag from the signable transaction payload in both send dry-run and pre-signing confirmation previews, with terminal-safe dry-run rendering.
20
+
21
+ - Updated dependencies [[`b9f81af`](https://github.com/vultisig/vultisig-sdk/commit/b9f81af9065a5c0bfc2f86f8fb20aa51e670ab77), [`c0e260f`](https://github.com/vultisig/vultisig-sdk/commit/c0e260f89d159d14b170384864b24b101b23dfb0), [`2ef8f3f`](https://github.com/vultisig/vultisig-sdk/commit/2ef8f3f42f5d44673568b39a91c42bd0fe410311), [`791d344`](https://github.com/vultisig/vultisig-sdk/commit/791d344c2dacc289c9fb7fbcbc38488aeeb47542)]:
22
+ - @vultisig/core-chain@2.31.0
23
+ - @vultisig/sdk@4.0.0
24
+ - @vultisig/client-shared@0.3.2
25
+ - @vultisig/rujira@59.0.0
26
+
27
+ ## 3.1.0
28
+
29
+ ### Patch Changes
30
+
31
+ - Updated dependencies [[`645e291`](https://github.com/vultisig/vultisig-sdk/commit/645e2917aa1b6cd58c5599ddb32b1c89fa73e20e), [`9436de6`](https://github.com/vultisig/vultisig-sdk/commit/9436de627b4d123d7f9bb76e4981722cd84266d1)]:
32
+ - @vultisig/core-chain@2.30.0
33
+ - @vultisig/sdk@3.1.0
34
+ - @vultisig/rujira@58.0.0
35
+
3
36
  ## 3.0.0
4
37
 
5
38
  ### Minor Changes
package/dist/index.js CHANGED
@@ -5828,6 +5828,12 @@ import { fiatCurrencies, fiatCurrencyNameRecord as fiatCurrencyNameRecord2 } fro
5828
5828
  // src/ui.ts
5829
5829
  import { fiatCurrencyNameRecord, fromChainAmountExact, Vultisig } from "@vultisig/sdk";
5830
5830
  import chalk2 from "chalk";
5831
+ var escapeTerminalControls = (value) => Array.from(value, (character) => {
5832
+ if (character === "\\") return "\\\\";
5833
+ const codePoint = character.codePointAt(0) ?? 0;
5834
+ const isTerminalControl = codePoint <= 31 || codePoint === 127 || codePoint >= 128 && codePoint <= 159;
5835
+ return isTerminalControl ? `\\x${codePoint.toString(16).padStart(2, "0").toUpperCase()}` : character;
5836
+ }).join("");
5831
5837
  function displayBalance(chain, balance, _raw = false) {
5832
5838
  printResult(chalk2.cyan(`
5833
5839
  ${chain} Balance:`));
@@ -5933,7 +5939,7 @@ Estimated gas: ${JSON.stringify(gas, bigIntReplacer2, 2)}`));
5933
5939
  printResult(` Amount: ${amount} ${symbol}`);
5934
5940
  printResult(` Chain: ${chain}`);
5935
5941
  if (memo) {
5936
- printResult(` Memo: ${memo}`);
5942
+ printResult(` Memo: ${escapeTerminalControls(memo)}`);
5937
5943
  }
5938
5944
  if (destinationTag !== void 0) {
5939
5945
  printResult(` Destination tag: ${destinationTag}`);
@@ -6244,7 +6250,8 @@ var EthereumL2Chain = {
6244
6250
  Blast: "Blast",
6245
6251
  Optimism: "Optimism",
6246
6252
  Zksync: "Zksync",
6247
- Mantle: "Mantle"
6253
+ Mantle: "Mantle",
6254
+ Robinhood: "Robinhood"
6248
6255
  };
6249
6256
  var EvmChain = {
6250
6257
  ...EthereumL2Chain,
@@ -6323,6 +6330,7 @@ var chainKindRecord = {
6323
6330
  [EvmChain.Mantle]: "evm",
6324
6331
  [EvmChain.Hyperliquid]: "evm",
6325
6332
  [EvmChain.Sei]: "evm",
6333
+ [EvmChain.Robinhood]: "evm",
6326
6334
  [UtxoChain.Bitcoin]: "utxo",
6327
6335
  [UtxoChain.BitcoinCash]: "utxo",
6328
6336
  [UtxoChain.Litecoin]: "utxo",
@@ -7296,7 +7304,10 @@ async function addToken(ctx2, options) {
7296
7304
  }
7297
7305
  async function removeToken(ctx2, chain, tokenId) {
7298
7306
  const vault = await ctx2.ensureActiveVault();
7299
- await vault.removeToken(chain, tokenId);
7307
+ const removed = await vault.removeToken(chain, tokenId);
7308
+ if (!removed) {
7309
+ throw new TokenNotFoundError(`Token "${tokenId}" not found on ${chain}`);
7310
+ }
7300
7311
  success(`
7301
7312
  + Removed token ${tokenId} from ${chain}`);
7302
7313
  }
@@ -7411,8 +7422,57 @@ var normalizeRippleDestination = (address) => {
7411
7422
  return { address: value };
7412
7423
  };
7413
7424
 
7425
+ // ../../packages/core/mpc/dist/keysign/error.js
7426
+ var BuildKeysignPayloadError = class extends Error {
7427
+ type;
7428
+ constructor(type, message = type) {
7429
+ super(message);
7430
+ this.type = type;
7431
+ this.name = "BuildKeysignPayloadError";
7432
+ }
7433
+ };
7434
+
7435
+ // ../../packages/core/mpc/dist/keysign/utils/rippleDestinationTag.js
7436
+ var maxRippleDestinationTag = 4294967295;
7437
+ var getLegacyDestinationTag = (memo) => {
7438
+ if (!memo || !/^(0|[1-9]\d*)$/.test(memo))
7439
+ return void 0;
7440
+ const destinationTag = Number(memo);
7441
+ return destinationTag <= maxRippleDestinationTag ? destinationTag : void 0;
7442
+ };
7443
+ var validateDestinationTag = (destinationTag) => {
7444
+ if (!Number.isInteger(destinationTag) || destinationTag < 0 || destinationTag > maxRippleDestinationTag) {
7445
+ throw new BuildKeysignPayloadError("ripple-destination-tag-invalid", `Invalid XRP destination tag: expected an integer between 0 and ${maxRippleDestinationTag}`);
7446
+ }
7447
+ return destinationTag;
7448
+ };
7449
+ var resolveDestinationTag = ({ destinationTag, memo }) => {
7450
+ if (destinationTag !== void 0)
7451
+ return validateDestinationTag(destinationTag);
7452
+ return getLegacyDestinationTag(memo);
7453
+ };
7454
+
7414
7455
  // src/commands/transaction.ts
7415
7456
  import { Chain as Chain4, Vultisig as Vultisig2 } from "@vultisig/sdk";
7457
+ var getSendPreviewDetails = (chain, keysignPayload) => {
7458
+ let memo = keysignPayload.memo || void 0;
7459
+ if (chain !== Chain4.Ripple) {
7460
+ return { memo, destinationTag: void 0 };
7461
+ }
7462
+ const rippleSpecific = keysignPayload.blockchainSpecific;
7463
+ if (rippleSpecific.case !== "rippleSpecific") {
7464
+ throw new Error("Ripple send payload is missing Ripple-specific data");
7465
+ }
7466
+ const destinationTag = resolveDestinationTag({
7467
+ destinationTag: rippleSpecific.value.destinationTag,
7468
+ memo
7469
+ });
7470
+ const legacyMemoDestinationTag = getLegacyDestinationTag(memo);
7471
+ if (legacyMemoDestinationTag !== void 0 && legacyMemoDestinationTag === destinationTag) {
7472
+ memo = void 0;
7473
+ }
7474
+ return { memo, destinationTag };
7475
+ };
7416
7476
  async function executeSend(ctx2, params) {
7417
7477
  const vault = await ctx2.ensureActiveVault();
7418
7478
  if (!Object.values(Chain4).includes(params.chain)) {
@@ -7424,9 +7484,13 @@ async function executeSend(ctx2, params) {
7424
7484
  }
7425
7485
  return sendTransaction(vault, params);
7426
7486
  }
7427
- async function previewDryRun(vault, params, dryResult, to, destinationTag) {
7487
+ async function previewDryRun(vault, params, dryResult, to) {
7428
7488
  const balance = await vault.balance(params.chain, params.tokenId);
7429
7489
  const hasInsufficientBalance = parseFloat(dryResult.total) > parseFloat(balance.formattedAmount);
7490
+ const { memo: previewMemo, destinationTag: payloadDestinationTag } = getSendPreviewDetails(
7491
+ params.chain,
7492
+ dryResult.keysignPayload
7493
+ );
7430
7494
  const isTokenSend = balance.tokenId !== void 0;
7431
7495
  const feeBalance = isTokenSend ? await vault.balance(params.chain).catch(() => void 0) : void 0;
7432
7496
  const warnings = [];
@@ -7450,7 +7514,8 @@ async function previewDryRun(vault, params, dryResult, to, destinationTag) {
7450
7514
  feeSymbol: dryResult.feeSymbol,
7451
7515
  total: dryResult.total,
7452
7516
  balance: balance.formattedAmount,
7453
- destinationTag,
7517
+ destinationTag: payloadDestinationTag,
7518
+ ...previewMemo ? { memo: previewMemo } : {},
7454
7519
  ...warnings.length > 0 ? { warning: warnings.join(". ") } : {}
7455
7520
  };
7456
7521
  if (isJsonOutput()) {
@@ -7463,6 +7528,7 @@ Dry-run preview:`);
7463
7528
  info(` To: ${result.to}`);
7464
7529
  info(` Amount: ${result.amount} ${result.symbol}`);
7465
7530
  if (result.destinationTag !== void 0) info(` Destination tag: ${result.destinationTag}`);
7531
+ if (result.memo) info(` Memo: ${escapeTerminalControls(result.memo)}`);
7466
7532
  info(` Fee: ${result.fee} ${result.feeSymbol}`);
7467
7533
  info(` Total: ${result.total} ${result.symbol}`);
7468
7534
  info(` Balance: ${result.balance} ${result.symbol}`);
@@ -7497,7 +7563,7 @@ async function sendTransaction(vault, params) {
7497
7563
  prepareSpinner.succeed("Transaction prepared");
7498
7564
  if (!dryResult.dryRun) throw new Error("unreachable");
7499
7565
  if (params.dryRun) {
7500
- return previewDryRun(vault, params, dryResult, to, destinationTag);
7566
+ return previewDryRun(vault, params, dryResult, to);
7501
7567
  }
7502
7568
  let gas;
7503
7569
  try {
@@ -7508,14 +7574,15 @@ async function sendTransaction(vault, params) {
7508
7574
  const balance = await vault.balance(params.chain, params.tokenId);
7509
7575
  if (!isJsonOutput()) {
7510
7576
  const address = await vault.address(params.chain);
7577
+ const preview = getSendPreviewDetails(params.chain, dryResult.keysignPayload);
7511
7578
  displayTransactionPreview(
7512
7579
  address,
7513
7580
  to,
7514
7581
  dryResult.total,
7515
7582
  balance.symbol,
7516
7583
  params.chain,
7517
- params.memo,
7518
- destinationTag,
7584
+ preview.memo,
7585
+ preview.destinationTag,
7519
7586
  gas
7520
7587
  );
7521
7588
  }
@@ -11028,8 +11095,8 @@ var AgentExecutor = class {
11028
11095
  `vault_coin remove: missing contract_address for ${t.ticker || t.symbol || "coin"} on ${t.chain}`
11029
11096
  );
11030
11097
  }
11031
- await this.vault.removeToken(chain2, tokenId2);
11032
- results.push({ chain: chain2.toString(), tokenId: tokenId2 });
11098
+ const removed2 = await this.vault.removeToken(chain2, tokenId2);
11099
+ results.push({ chain: chain2.toString(), tokenId: tokenId2, removed: removed2 });
11033
11100
  }
11034
11101
  return { removed: results };
11035
11102
  }
@@ -11040,8 +11107,8 @@ var AgentExecutor = class {
11040
11107
  if (!tokenId) {
11041
11108
  throw new Error(`vault_coin remove: missing contract_address for coin on ${chainName}`);
11042
11109
  }
11043
- await this.vault.removeToken(chain, tokenId);
11044
- return { chain: chain.toString(), removed: true };
11110
+ const removed = await this.vault.removeToken(chain, tokenId);
11111
+ return { chain: chain.toString(), removed };
11045
11112
  }
11046
11113
  async removeChain(_toolCallId, input) {
11047
11114
  return this.runTool("remove_chain", () => this.removeChainImpl(input));
@@ -14883,6 +14950,13 @@ function summarizeData(data) {
14883
14950
  return `tx: ${data.tx_hash.slice(0, 12)}...`;
14884
14951
  }
14885
14952
  if (data.added) return "added";
14953
+ if (Array.isArray(data.removed)) {
14954
+ const entries = data.removed;
14955
+ const removed = entries.filter((entry) => entry.removed).length;
14956
+ if (removed === 0) return "not tracked";
14957
+ return removed === entries.length ? "removed" : `removed ${removed}/${entries.length}`;
14958
+ }
14959
+ if (data.removed === false) return "not tracked";
14886
14960
  if (data.removed) return "removed";
14887
14961
  if (data.message) return data.message;
14888
14962
  return "";
@@ -15271,7 +15345,7 @@ var cachedVersion = null;
15271
15345
  function getVersion() {
15272
15346
  if (cachedVersion) return cachedVersion;
15273
15347
  if (true) {
15274
- cachedVersion = "3.0.0";
15348
+ cachedVersion = "4.0.0";
15275
15349
  return cachedVersion;
15276
15350
  }
15277
15351
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vultisig/cli",
3
- "version": "3.0.0",
3
+ "version": "4.0.0",
4
4
  "description": "The self-custody MPC wallet CLI for AI coding agents (Claude Code, Cursor, OpenCode). Natural-language agent mode, 36+ chains, DKLS23 threshold signatures. Seedless.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -73,10 +73,10 @@
73
73
  "@cosmjs/stargate": "^0.39.0",
74
74
  "@napi-rs/keyring": "^1.3.0",
75
75
  "@noble/hashes": "^2.2.0",
76
- "@vultisig/client-shared": "^0.3.1",
77
- "@vultisig/core-chain": "^2.29.2",
78
- "@vultisig/rujira": "^57.0.0",
79
- "@vultisig/sdk": "^3.0.0",
76
+ "@vultisig/client-shared": "^0.3.2",
77
+ "@vultisig/core-chain": "^2.31.0",
78
+ "@vultisig/rujira": "^59.0.0",
79
+ "@vultisig/sdk": "^4.0.0",
80
80
  "chalk": "^5.6.2",
81
81
  "cli-table3": "^0.6.5",
82
82
  "commander": "^15.0.0",
@@ -94,6 +94,7 @@
94
94
  "@types/node": "^26.1.1",
95
95
  "@types/tabtab": "^3.0.4",
96
96
  "@types/ws": "^8.18.1",
97
+ "@vultisig/core-mpc": "^1.16.0",
97
98
  "esbuild": "^0.28.1",
98
99
  "ethers": "^6.17.0",
99
100
  "node-pty": "^1.1.0",