@vultisig/cli 3.1.0 → 4.0.1

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 +33 -0
  2. package/dist/index.js +333 -14
  3. package/package.json +6 -5
package/CHANGELOG.md CHANGED
@@ -1,5 +1,38 @@
1
1
  # @vultisig/cli
2
2
 
3
+ ## 4.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#1561](https://github.com/vultisig/vultisig-sdk/pull/1561) [`38be8ef`](https://github.com/vultisig/vultisig-sdk/commit/38be8eff97b061d241c7ac1c1616b48115fbb974) Thanks [@NeOMakinG](https://github.com/NeOMakinG)! - Render `yield_opportunities` and `polymarket_markets` agent surfaces as prose instead of leaking raw `{"surface":...}` JSON into the terminal. The CLI only advertised `balance_summary`/`turn_outcome` in `supported_surfaces`, so any other surface fell back to the backend's legacy verbatim-echo path. Adds parsers, prose renderers, and a legacy-echo fallback for both surfaces, mirroring the existing `balance_summary` handling, and wires them through the TUI, pipe mode, and `agent ask`.
8
+
9
+ - Updated dependencies [[`3767033`](https://github.com/vultisig/vultisig-sdk/commit/3767033f51804f3fff5088098c767280577bd4a4), [`68df301`](https://github.com/vultisig/vultisig-sdk/commit/68df301134b8000187a11cf92b9427e8200d4623)]:
10
+ - @vultisig/sdk@4.0.1
11
+
12
+ ## 4.0.0
13
+
14
+ ### Minor Changes
15
+
16
+ - [#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.
17
+
18
+ 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.
19
+
20
+ **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.
21
+
22
+ **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 }`.
23
+
24
+ **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.
25
+
26
+ ### Patch Changes
27
+
28
+ - [#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.
29
+
30
+ - 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)]:
31
+ - @vultisig/core-chain@2.31.0
32
+ - @vultisig/sdk@4.0.0
33
+ - @vultisig/client-shared@0.3.2
34
+ - @vultisig/rujira@59.0.0
35
+
3
36
  ## 3.1.0
4
37
 
5
38
  ### Patch 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
  }
@@ -10062,6 +10129,8 @@ var AskInterface = class {
10062
10129
  toolCalls = [];
10063
10130
  transactions = [];
10064
10131
  cards = [];
10132
+ yieldCards = [];
10133
+ polymarketCards = [];
10065
10134
  warnings = [];
10066
10135
  outcome;
10067
10136
  error;
@@ -10114,6 +10183,12 @@ var AskInterface = class {
10114
10183
  onBalanceSummary: (card) => {
10115
10184
  this.cards.push(card);
10116
10185
  },
10186
+ onYieldOpportunities: (card) => {
10187
+ this.yieldCards.push(card);
10188
+ },
10189
+ onPolymarketMarkets: (card) => {
10190
+ this.polymarketCards.push(card);
10191
+ },
10117
10192
  onTurnOutcome: (outcome) => {
10118
10193
  this.outcome = outcome;
10119
10194
  },
@@ -10172,6 +10247,8 @@ var AskInterface = class {
10172
10247
  this.toolCalls = [];
10173
10248
  this.transactions = [];
10174
10249
  this.cards = [];
10250
+ this.yieldCards = [];
10251
+ this.polymarketCards = [];
10175
10252
  this.warnings = [];
10176
10253
  this.outcome = void 0;
10177
10254
  this.error = void 0;
@@ -10195,6 +10272,8 @@ var AskInterface = class {
10195
10272
  toolCalls: this.toolCalls,
10196
10273
  transactions: this.transactions,
10197
10274
  cards: this.cards,
10275
+ yieldCards: this.yieldCards,
10276
+ polymarketCards: this.polymarketCards,
10198
10277
  warnings: this.warnings,
10199
10278
  error: this.error,
10200
10279
  ...this.outcome ? { outcome: this.outcome } : {}
@@ -10259,7 +10338,12 @@ async function authenticateVault(client, vault, password, maxAttempts = 3) {
10259
10338
 
10260
10339
  // src/agent/cards.ts
10261
10340
  import chalk8 from "chalk";
10262
- var CLI_SUPPORTED_SURFACES = ["balance_summary", "turn_outcome"];
10341
+ var CLI_SUPPORTED_SURFACES = [
10342
+ "balance_summary",
10343
+ "turn_outcome",
10344
+ "yield_opportunities",
10345
+ "polymarket_markets"
10346
+ ];
10263
10347
  function parseTurnOutcome(raw) {
10264
10348
  if (!raw || typeof raw !== "object") return null;
10265
10349
  const kind = raw.kind;
@@ -10407,6 +10491,142 @@ function renderBalanceSummaryCard(card) {
10407
10491
  }
10408
10492
  return lines.join("\n");
10409
10493
  }
10494
+ function parseYieldOpportunity(v) {
10495
+ if (!v || typeof v !== "object") return null;
10496
+ const o = v;
10497
+ const id = asString(o.id);
10498
+ const symbol = asString(o.token) || asString(o.symbol);
10499
+ const chain = asString(o.network) || asString(o.chain);
10500
+ if (!symbol) return null;
10501
+ const opportunity = { id, chain, symbol };
10502
+ const apy = o.apy;
10503
+ if (typeof apy === "number" && Number.isFinite(apy)) {
10504
+ opportunity.apy = `${apy.toFixed(2)}%`;
10505
+ } else if (typeof apy === "string" && apy) {
10506
+ opportunity.apy = stripControlChars(apy);
10507
+ }
10508
+ const provider = asString(o.provider);
10509
+ if (provider) opportunity.provider = provider;
10510
+ const type = asString(o.type);
10511
+ if (type) opportunity.type = type;
10512
+ return opportunity;
10513
+ }
10514
+ function parseYieldOpportunitiesEnvelope(value) {
10515
+ if (!value || typeof value !== "object") return null;
10516
+ const o = value;
10517
+ if (o.surface !== "yield_opportunities") return null;
10518
+ const raw = Array.isArray(o.opportunities) ? o.opportunities : Array.isArray(o.data) ? o.data : [];
10519
+ const opportunities = raw.map(parseYieldOpportunity).filter((y) => y !== null);
10520
+ if (opportunities.length === 0) return null;
10521
+ const card = { surface: "yield_opportunities", opportunities };
10522
+ const title = asString(o.title);
10523
+ if (title) card.title = title;
10524
+ return card;
10525
+ }
10526
+ function renderYieldOpportunitiesCard(card) {
10527
+ const lines = [chalk8.bold(` ${card.title || "Yield opportunities"}`)];
10528
+ for (const opp of card.opportunities) {
10529
+ const label = opp.symbol || opp.id || "opportunity";
10530
+ const apyCol = opp.apy ? chalk8.green(` ${opp.apy} APY`) : "";
10531
+ const chainCol = opp.chain ? chalk8.gray(` (${opp.chain})`) : "";
10532
+ const providerCol = opp.provider ? chalk8.gray(` via ${opp.provider}`) : "";
10533
+ lines.push(` ${chalk8.bold(label)}${apyCol}${chainCol}${providerCol}`);
10534
+ }
10535
+ return lines.join("\n");
10536
+ }
10537
+ function extractYieldOpportunitiesFromText(content) {
10538
+ return extractSurfaceFromText(content, "yield_opportunities", parseYieldOpportunitiesEnvelope);
10539
+ }
10540
+ function formatProbability(v) {
10541
+ if (typeof v !== "number" || !Number.isFinite(v)) return void 0;
10542
+ if (v <= 0 || v >= 1) return void 0;
10543
+ return `${Math.round(v * 100)}%`;
10544
+ }
10545
+ function buildPolymarketOutcomes(o) {
10546
+ const yesPrice = formatProbability(o.yesPrice);
10547
+ const noPrice = formatProbability(o.noPrice);
10548
+ const topOutcome = asString(o.topOutcome);
10549
+ if (topOutcome) {
10550
+ const outcome = { name: topOutcome };
10551
+ if (yesPrice) outcome.price = yesPrice;
10552
+ return [outcome];
10553
+ }
10554
+ const outcomes = [];
10555
+ if (yesPrice) outcomes.push({ name: "YES", price: yesPrice });
10556
+ if (noPrice) outcomes.push({ name: "NO", price: noPrice });
10557
+ return outcomes;
10558
+ }
10559
+ function parsePolymarketMarket(v) {
10560
+ if (!v || typeof v !== "object") return null;
10561
+ const o = v;
10562
+ const question = asString(o.question) || asString(o.title);
10563
+ if (!question) return null;
10564
+ const id = asString(o.id) || asString(o.slug);
10565
+ const outcomes = buildPolymarketOutcomes(o);
10566
+ const market = { id, question, outcomes };
10567
+ const volume24h = o.volume24h;
10568
+ if (typeof volume24h === "number" && Number.isFinite(volume24h)) {
10569
+ market.volume = formatUsd(volume24h);
10570
+ } else if (typeof volume24h === "string" && volume24h) {
10571
+ market.volume = stripControlChars(volume24h);
10572
+ }
10573
+ const endDate = asString(o.endDate) || asString(o.end_date);
10574
+ if (endDate) market.endDate = endDate;
10575
+ return market;
10576
+ }
10577
+ function parsePolymarketMarketsEnvelope(value) {
10578
+ if (!value || typeof value !== "object") return null;
10579
+ const o = value;
10580
+ if (o.surface !== "polymarket_markets") return null;
10581
+ const raw = Array.isArray(o.markets) ? o.markets : Array.isArray(o.data) ? o.data : [];
10582
+ const markets = raw.map(parsePolymarketMarket).filter((m) => m !== null);
10583
+ if (markets.length === 0) return null;
10584
+ const card = { surface: "polymarket_markets", markets };
10585
+ const title = asString(o.title);
10586
+ if (title) card.title = title;
10587
+ const subtitle = asString(o.subtitle);
10588
+ if (subtitle) card.subtitle = subtitle;
10589
+ return card;
10590
+ }
10591
+ function renderPolymarketMarketsCard(card) {
10592
+ const lines = [chalk8.bold(` ${card.title || "Polymarket markets"}`)];
10593
+ if (card.subtitle) lines.push(chalk8.gray(` ${card.subtitle}`));
10594
+ for (const market of card.markets) {
10595
+ const metaBits = [market.volume ? `${market.volume} vol` : "", market.endDate ? `ends ${market.endDate}` : ""].filter(Boolean).join(", ");
10596
+ lines.push(` ${chalk8.bold(market.question)}${metaBits ? chalk8.gray(` (${metaBits})`) : ""}`);
10597
+ for (const outcome of market.outcomes) {
10598
+ const priceCol = outcome.price ? chalk8.gray(` ${outcome.price}`) : "";
10599
+ lines.push(` ${outcome.name}${priceCol}`);
10600
+ }
10601
+ }
10602
+ return lines.join("\n");
10603
+ }
10604
+ function extractPolymarketMarketsFromText(content) {
10605
+ return extractSurfaceFromText(content, "polymarket_markets", parsePolymarketMarketsEnvelope);
10606
+ }
10607
+ function extractSurfaceFromText(content, surfaceKey, parser) {
10608
+ if (!content || !content.includes(surfaceKey)) return null;
10609
+ if (content.length > 2e5) return null;
10610
+ for (let i = content.indexOf("{"); i !== -1; i = content.indexOf("{", i + 1)) {
10611
+ const end = matchBrace(content, i);
10612
+ if (end === -1) break;
10613
+ const blob = content.slice(i, end + 1);
10614
+ if (!blob.includes(surfaceKey)) continue;
10615
+ let parsed;
10616
+ try {
10617
+ parsed = JSON.parse(blob);
10618
+ } catch {
10619
+ continue;
10620
+ }
10621
+ const card = parser(parsed);
10622
+ if (!card) continue;
10623
+ const before = content.slice(0, i).replace(/```(?:json)?\s*$/i, "");
10624
+ const after = content.slice(end + 1).replace(/^\s*```/, "");
10625
+ const remainingText = (before + after).trim();
10626
+ return { card, remainingText };
10627
+ }
10628
+ return null;
10629
+ }
10410
10630
 
10411
10631
  // src/agent/client.ts
10412
10632
  import { randomUUID } from "node:crypto";
@@ -11028,8 +11248,8 @@ var AgentExecutor = class {
11028
11248
  `vault_coin remove: missing contract_address for ${t.ticker || t.symbol || "coin"} on ${t.chain}`
11029
11249
  );
11030
11250
  }
11031
- await this.vault.removeToken(chain2, tokenId2);
11032
- results.push({ chain: chain2.toString(), tokenId: tokenId2 });
11251
+ const removed2 = await this.vault.removeToken(chain2, tokenId2);
11252
+ results.push({ chain: chain2.toString(), tokenId: tokenId2, removed: removed2 });
11033
11253
  }
11034
11254
  return { removed: results };
11035
11255
  }
@@ -11040,8 +11260,8 @@ var AgentExecutor = class {
11040
11260
  if (!tokenId) {
11041
11261
  throw new Error(`vault_coin remove: missing contract_address for coin on ${chainName}`);
11042
11262
  }
11043
- await this.vault.removeToken(chain, tokenId);
11044
- return { chain: chain.toString(), removed: true };
11263
+ const removed = await this.vault.removeToken(chain, tokenId);
11264
+ return { chain: chain.toString(), removed };
11045
11265
  }
11046
11266
  async removeChain(_toolCallId, input) {
11047
11267
  return this.runTool("remove_chain", () => this.removeChainImpl(input));
@@ -12766,6 +12986,16 @@ var AgentClient = class {
12766
12986
  callbacks.onBalanceSummary?.(card);
12767
12987
  break;
12768
12988
  }
12989
+ case "yield_opportunities": {
12990
+ const card = v1Data ?? parsed.data ?? parsed;
12991
+ callbacks.onYieldOpportunities?.(card);
12992
+ break;
12993
+ }
12994
+ case "polymarket_markets": {
12995
+ const card = v1Data ?? parsed.data ?? parsed;
12996
+ callbacks.onPolymarketMarkets?.(card);
12997
+ break;
12998
+ }
12769
12999
  case "turn_outcome": {
12770
13000
  const outcome = parseTurnOutcome(v1Data ?? parsed.data ?? parsed);
12771
13001
  if (outcome) callbacks.onTurnOutcome?.(outcome);
@@ -12962,6 +13192,10 @@ var AgentClient = class {
12962
13192
  return "suggestions";
12963
13193
  case "data-balance_summary":
12964
13194
  return "balance_summary";
13195
+ case "data-yield_opportunities":
13196
+ return "yield_opportunities";
13197
+ case "data-polymarket_markets":
13198
+ return "polymarket_markets";
12965
13199
  case "data-turn_outcome":
12966
13200
  return "turn_outcome";
12967
13201
  case "data-message":
@@ -13384,6 +13618,12 @@ var PipeInterface = class {
13384
13618
  onBalanceSummary: (card) => {
13385
13619
  this.emit({ type: "balance_summary", card });
13386
13620
  },
13621
+ onYieldOpportunities: (card) => {
13622
+ this.emit({ type: "yield_opportunities", card });
13623
+ },
13624
+ onPolymarketMarkets: (card) => {
13625
+ this.emit({ type: "polymarket_markets", card });
13626
+ },
13387
13627
  onSuggestions: (suggestions) => {
13388
13628
  this.emit({ type: "suggestions", suggestions });
13389
13629
  },
@@ -14000,6 +14240,8 @@ var AgentSession = class {
14000
14240
  }
14001
14241
  let toolOutputCandidate = null;
14002
14242
  let balanceCardRendered = false;
14243
+ let yieldCardRendered = false;
14244
+ let polymarketCardRendered = false;
14003
14245
  const pendingDispatches = [];
14004
14246
  let dispatchChain = Promise.resolve();
14005
14247
  const callbacks = {
@@ -14037,6 +14279,20 @@ var AgentSession = class {
14037
14279
  ui.onBalanceSummary?.(card);
14038
14280
  }
14039
14281
  },
14282
+ onYieldOpportunities: (raw) => {
14283
+ const card = parseYieldOpportunitiesEnvelope(raw);
14284
+ if (card) {
14285
+ yieldCardRendered = true;
14286
+ ui.onYieldOpportunities?.(card);
14287
+ }
14288
+ },
14289
+ onPolymarketMarkets: (raw) => {
14290
+ const card = parsePolymarketMarketsEnvelope(raw);
14291
+ if (card) {
14292
+ polymarketCardRendered = true;
14293
+ ui.onPolymarketMarkets?.(card);
14294
+ }
14295
+ },
14040
14296
  onTurnOutcome: (outcome) => {
14041
14297
  ui.onTurnOutcome?.(outcome);
14042
14298
  },
@@ -14078,6 +14334,8 @@ var AgentSession = class {
14078
14334
  let displayText = stripLeakedToolCallTags(responseText);
14079
14335
  if (displayText) {
14080
14336
  displayText = this.renderEchoedBalanceCard(displayText, balanceCardRendered, ui);
14337
+ displayText = this.renderEchoedYieldOpportunitiesCard(displayText, yieldCardRendered, ui);
14338
+ displayText = this.renderEchoedPolymarketMarketsCard(displayText, polymarketCardRendered, ui);
14081
14339
  }
14082
14340
  if (displayText) {
14083
14341
  ui.onAssistantMessage(displayText);
@@ -14343,6 +14601,32 @@ var AgentSession = class {
14343
14601
  }
14344
14602
  return extracted.remainingText;
14345
14603
  }
14604
+ /**
14605
+ * Legacy-path fallback for echoed yield_opportunities cards (rj3p). Mirrors
14606
+ * {@link renderEchoedBalanceCard} — strips a verbatim-echoed envelope from
14607
+ * the displayed text and renders it, unless the typed SSE part already fired.
14608
+ */
14609
+ renderEchoedYieldOpportunitiesCard(displayText, alreadyRendered, ui) {
14610
+ const extracted = extractYieldOpportunitiesFromText(displayText);
14611
+ if (!extracted) return displayText;
14612
+ if (!alreadyRendered) {
14613
+ ui.onYieldOpportunities?.(extracted.card);
14614
+ }
14615
+ return extracted.remainingText;
14616
+ }
14617
+ /**
14618
+ * Legacy-path fallback for echoed polymarket_markets cards (rj3p). Mirrors
14619
+ * {@link renderEchoedBalanceCard} — strips a verbatim-echoed envelope from
14620
+ * the displayed text and renders it, unless the typed SSE part already fired.
14621
+ */
14622
+ renderEchoedPolymarketMarketsCard(displayText, alreadyRendered, ui) {
14623
+ const extracted = extractPolymarketMarketsFromText(displayText);
14624
+ if (!extracted) return displayText;
14625
+ if (!alreadyRendered) {
14626
+ ui.onPolymarketMarkets?.(extracted.card);
14627
+ }
14628
+ return extracted.remainingText;
14629
+ }
14346
14630
  /**
14347
14631
  * Wrap a per-tool dispatch with the password-prompt gate (for tools in
14348
14632
  * {@link PASSWORD_REQUIRED_TOOLS}) and `ui.onToolCall` /
@@ -14673,6 +14957,22 @@ var ChatTUI = class {
14673
14957
  this.currentStreamText = "";
14674
14958
  console.log(renderBalanceSummaryCard(card));
14675
14959
  },
14960
+ onYieldOpportunities: (card) => {
14961
+ if (this.isStreaming) {
14962
+ process.stdout.write("\n");
14963
+ this.isStreaming = false;
14964
+ }
14965
+ this.currentStreamText = "";
14966
+ console.log(renderYieldOpportunitiesCard(card));
14967
+ },
14968
+ onPolymarketMarkets: (card) => {
14969
+ if (this.isStreaming) {
14970
+ process.stdout.write("\n");
14971
+ this.isStreaming = false;
14972
+ }
14973
+ this.currentStreamText = "";
14974
+ console.log(renderPolymarketMarketsCard(card));
14975
+ },
14676
14976
  onSuggestions: (suggestions) => {
14677
14977
  if (suggestions.length > 0) {
14678
14978
  console.log(chalk9.gray(" Suggestions:"));
@@ -14883,6 +15183,13 @@ function summarizeData(data) {
14883
15183
  return `tx: ${data.tx_hash.slice(0, 12)}...`;
14884
15184
  }
14885
15185
  if (data.added) return "added";
15186
+ if (Array.isArray(data.removed)) {
15187
+ const entries = data.removed;
15188
+ const removed = entries.filter((entry) => entry.removed).length;
15189
+ if (removed === 0) return "not tracked";
15190
+ return removed === entries.length ? "removed" : `removed ${removed}/${entries.length}`;
15191
+ }
15192
+ if (data.removed === false) return "not tracked";
14886
15193
  if (data.removed) return "removed";
14887
15194
  if (data.message) return data.message;
14888
15195
  return "";
@@ -15037,6 +15344,16 @@ function outputAskHuman(result, confirmationRequired, proposed) {
15037
15344
  for (const card of result.cards) {
15038
15345
  process.stdout.write(`
15039
15346
  ${renderBalanceSummaryCard(card)}
15347
+ `);
15348
+ }
15349
+ for (const card of result.yieldCards) {
15350
+ process.stdout.write(`
15351
+ ${renderYieldOpportunitiesCard(card)}
15352
+ `);
15353
+ }
15354
+ for (const card of result.polymarketCards) {
15355
+ process.stdout.write(`
15356
+ ${renderPolymarketMarketsCard(card)}
15040
15357
  `);
15041
15358
  }
15042
15359
  if (result.response) {
@@ -15068,6 +15385,8 @@ function outputAskSuccess(wantsJson, result, conversationId) {
15068
15385
  tool_calls: result.toolCalls,
15069
15386
  transactions: result.transactions,
15070
15387
  ...result.cards.length > 0 ? { cards: result.cards } : {},
15388
+ ...result.yieldCards.length > 0 ? { yield_cards: result.yieldCards } : {},
15389
+ ...result.polymarketCards.length > 0 ? { polymarket_cards: result.polymarketCards } : {},
15071
15390
  ...result.warnings.length > 0 ? { warnings: result.warnings } : {},
15072
15391
  // a2a-02: the typed turn ending (success | blocked | refusal | error) at
15073
15392
  // `data.outcome` — the same relative slot as on the error envelope. Present
@@ -15271,7 +15590,7 @@ var cachedVersion = null;
15271
15590
  function getVersion() {
15272
15591
  if (cachedVersion) return cachedVersion;
15273
15592
  if (true) {
15274
- cachedVersion = "3.1.0";
15593
+ cachedVersion = "4.0.1";
15275
15594
  return cachedVersion;
15276
15595
  }
15277
15596
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vultisig/cli",
3
- "version": "3.1.0",
3
+ "version": "4.0.1",
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.30.0",
78
- "@vultisig/rujira": "^58.0.0",
79
- "@vultisig/sdk": "^3.1.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.1",
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",