@vultisig/cli 0.14.2 → 0.15.2

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 +43 -0
  2. package/dist/index.js +218 -59
  3. package/package.json +7 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,48 @@
1
1
  # @vultisig/cli
2
2
 
3
+ ## 0.15.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#263](https://github.com/vultisig/vultisig-sdk/pull/263) [`6585c38`](https://github.com/vultisig/vultisig-sdk/commit/6585c38431db063f600e133d1a23f84b7c19e934) Thanks [@rcoderdev](https://github.com/rcoderdev)! - fix(cli): align agent executor with backend payloads and harden action handling
8
+ - model `tx_ready` / non-streaming transaction payloads with `TxReadyPayload`
9
+ - optional `vultisig` on agent config for shared SDK state (e.g. address book)
10
+ - executor improvements (chain locks, calldata resolution, EVM gas refresh) and unit tests
11
+
12
+ - Updated dependencies [[`6585c38`](https://github.com/vultisig/vultisig-sdk/commit/6585c38431db063f600e133d1a23f84b7c19e934)]:
13
+ - @vultisig/sdk@0.15.2
14
+ - @vultisig/rujira@10.0.0
15
+
16
+ ## 0.15.0
17
+
18
+ ### Patch Changes
19
+
20
+ - [#234](https://github.com/vultisig/vultisig-sdk/pull/234) [`9f71a0e`](https://github.com/vultisig/vultisig-sdk/commit/9f71a0e430aadcb96707448c5e5e077aa0b561e0) Thanks [@rcoderdev](https://github.com/rcoderdev)! - Add Vitest for the CLI package and run CLI tests from the root `yarn test` script. Unimplemented agent actions now return `success: false` with an error message instead of `success: true` with a `data.message` field.
21
+
22
+ - [#235](https://github.com/vultisig/vultisig-sdk/pull/235) [`aea1c28`](https://github.com/vultisig/vultisig-sdk/commit/aea1c28051345ddef9c952108b203caa8b7fa032) Thanks [@rcoderdev](https://github.com/rcoderdev)! - ### Swap amounts (backward compatible)
23
+ - `SwapQuoteParams.amount` and `SwapTxParams.amount` now accept **`string | number`**. Call sites that already pass a **number** require no code changes.
24
+ - Human-readable swap amounts can be passed as **decimal strings** end-to-end (compound `vault.swap()`, `getSwapQuote`, `prepareSwapTx`, CLI agent), avoiding precision loss from `Number()` / `parseFloat()` on extreme magnitudes or fractional digits.
25
+ - `toChainAmount` accepts **`string | number`**; whitespace-only / empty strings throw instead of being treated as zero.
26
+
27
+ ### Send preparation (stricter validation)
28
+ - `prepareSendTx` and `estimateSendFee` reject **zero or negative** `amount` in base units. This aligns with real transfers; payloads with `toAmount: "0"` are no longer built for native/token sends.
29
+ - **Zero-value EVM contract calls** are unchanged: use `prepareContractCallTx` (or `vault.contractCall()`), which still builds via the internal path that allows `value: 0n`.
30
+
31
+ ### Other
32
+ - Swap approval sizing uses `toChainAmount` instead of float scaling for required allowance.
33
+ - `@vultisig/rujira` (source): `VultisigSignature.format` includes **`MLDSA`** to match SDK `Signature` — type-only widening, no runtime change; Rujira will pick up a **patch** version via normal dependency releases when published next.
34
+ - CLI: direct **`viem`** dependency; Solana local swap human amount via `formatUnits`; agent SSE `Transaction` typing includes optional `swap_tx` / `send_tx` / `tx`.
35
+
36
+ **Semver:** **Minor** for `@vultisig/core-chain`, `@vultisig/core-mpc`, and `@vultisig/sdk` (additive types + intentional validation tightening). **`@vultisig/cli` is linked to the SDK** in Changesets config, so it receives the same minor bump. This is **not** a SemVer **major** for integration purposes: swap inputs are only widened; `prepareSendTx({ amount: 0n })` was never a valid broadcast path.
37
+
38
+ **Release tooling note:** `yarn changeset status` may still propose a **major** version for `@vultisig/rujira` when the SDK minors, even though the only Rujira change is adding `'MLDSA'` to a string-literal union (fully backward compatible). Review the Version Packages PR and **downgrade Rujira to patch** if your policy is to reserve majors for real breaking API changes.
39
+
40
+ **`@vultisig/sdk` is 0.x:** per [SemVer](https://semver.org/#spec-item-4), minor releases on `0.y.z` may include behavior changes; consumers pinning `^0.14.0` should still accept `0.15.0` but should read changelog for validation tightening.
41
+
42
+ - Updated dependencies [[`9f71a0e`](https://github.com/vultisig/vultisig-sdk/commit/9f71a0e430aadcb96707448c5e5e077aa0b561e0), [`aea1c28`](https://github.com/vultisig/vultisig-sdk/commit/aea1c28051345ddef9c952108b203caa8b7fa032)]:
43
+ - @vultisig/sdk@0.15.0
44
+ - @vultisig/rujira@10.0.0
45
+
3
46
  ## 0.14.2
4
47
 
5
48
  ### Patch Changes
package/dist/index.js CHANGED
@@ -1430,10 +1430,29 @@ var init_sha3 = __esm({
1430
1430
  }
1431
1431
  });
1432
1432
 
1433
+ // node_modules/viem/_esm/utils/unit/formatUnits.js
1434
+ function formatUnits(value, decimals) {
1435
+ let display = value.toString();
1436
+ const negative = display.startsWith("-");
1437
+ if (negative)
1438
+ display = display.slice(1);
1439
+ display = display.padStart(decimals, "0");
1440
+ let [integer, fraction] = [
1441
+ display.slice(0, display.length - decimals),
1442
+ display.slice(display.length - decimals)
1443
+ ];
1444
+ fraction = fraction.replace(/(0+)$/, "");
1445
+ return `${negative ? "-" : ""}${integer || "0"}${fraction ? `.${fraction}` : ""}`;
1446
+ }
1447
+ var init_formatUnits = __esm({
1448
+ "node_modules/viem/_esm/utils/unit/formatUnits.js"() {
1449
+ }
1450
+ });
1451
+
1433
1452
  // src/index.ts
1434
1453
  import "dotenv/config";
1435
1454
  import { promises as fs4 } from "node:fs";
1436
- import { parseKeygenQR, Vultisig as Vultisig7 } from "@vultisig/sdk";
1455
+ import { parseKeygenQR, Vultisig as Vultisig6 } from "@vultisig/sdk";
1437
1456
  import chalk15 from "chalk";
1438
1457
  import { program } from "commander";
1439
1458
  import inquirer8 from "inquirer";
@@ -3915,7 +3934,12 @@ Address Book${options.chain ? ` (${options.chain})` : ""}:
3915
3934
  }
3916
3935
 
3917
3936
  // src/commands/rujira.ts
3918
- import { getRoutesSummary, listEasyRoutes, RujiraClient, VultisigRujiraProvider } from "@vultisig/rujira";
3937
+ import {
3938
+ getRoutesSummary,
3939
+ listEasyRoutes,
3940
+ RujiraClient,
3941
+ VultisigRujiraProvider
3942
+ } from "@vultisig/rujira";
3919
3943
  async function createRujiraClient(ctx2, options = {}) {
3920
3944
  const vault = await ctx2.ensureActiveVault();
3921
3945
  const provider = new VultisigRujiraProvider(vault);
@@ -4547,8 +4571,11 @@ var AgentClient = class {
4547
4571
  case "tx_ready":
4548
4572
  if (this.verbose) process.stderr.write(`[SSE:tx_ready] raw: ${data.slice(0, 2e3)}
4549
4573
  `);
4550
- result.transactions.push(parsed);
4551
- callbacks.onTxReady?.(parsed);
4574
+ {
4575
+ const txReady = parsed;
4576
+ result.transactions.push(txReady);
4577
+ callbacks.onTxReady?.(txReady);
4578
+ }
4552
4579
  break;
4553
4580
  case "message":
4554
4581
  result.message = parsed.message || parsed;
@@ -4763,7 +4790,10 @@ function getNativeTokenDecimals(chain) {
4763
4790
  }
4764
4791
 
4765
4792
  // src/agent/executor.ts
4766
- import { Chain as Chain9, Vultisig as Vultisig6 } from "@vultisig/sdk";
4793
+ import { Chain as Chain9, evmCall, fiatCurrencies as fiatCurrencies3, Vultisig as VultisigSdk } from "@vultisig/sdk";
4794
+
4795
+ // node_modules/viem/_esm/index.js
4796
+ init_formatUnits();
4767
4797
 
4768
4798
  // src/core/VaultStateStore.ts
4769
4799
  import * as fs2 from "node:fs";
@@ -4977,6 +5007,8 @@ var EVM_GAS_RPC = {
4977
5007
  };
4978
5008
  var AgentExecutor = class {
4979
5009
  vault;
5010
+ /** Owning SDK (optional); used for address book backed by app storage */
5011
+ vultisig;
4980
5012
  pendingPayloads = /* @__PURE__ */ new Map();
4981
5013
  password = null;
4982
5014
  verbose;
@@ -4985,9 +5017,10 @@ var AgentExecutor = class {
4985
5017
  chainLockReleases = /* @__PURE__ */ new Map();
4986
5018
  /** Backend client for resolving calldata_id references. */
4987
5019
  backendClient = null;
4988
- constructor(vault, verbose = false, vaultId) {
5020
+ constructor(vault, verbose = false, vaultId, vultisig) {
4989
5021
  this.vault = vault;
4990
5022
  this.verbose = verbose;
5023
+ this.vultisig = vultisig;
4991
5024
  if (vaultId) {
4992
5025
  this.stateStore = new VaultStateStore(vaultId);
4993
5026
  }
@@ -5001,6 +5034,8 @@ var AgentExecutor = class {
5001
5034
  /**
5002
5035
  * Store a server-built transaction (from tx_ready SSE event).
5003
5036
  * This allows sign_tx to find and sign it when the backend requests signing.
5037
+ *
5038
+ * @returns true when a signable payload was stored; false for MCP errors or missing tx body
5004
5039
  */
5005
5040
  storeServerTransaction(txReadyData) {
5006
5041
  if (this.verbose)
@@ -5008,11 +5043,17 @@ var AgentExecutor = class {
5008
5043
  `[executor] storeServerTransaction called, keys: ${Object.keys(txReadyData || {}).join(",")}
5009
5044
  `
5010
5045
  );
5011
- const swapTx = txReadyData.swap_tx || txReadyData.send_tx || txReadyData.tx;
5012
- if (!swapTx) {
5046
+ const nestedTx = txReadyData?.swap_tx || txReadyData?.send_tx || txReadyData?.tx;
5047
+ if (nestedTx?.status === "error" || nestedTx?.error) {
5048
+ if (this.verbose)
5049
+ process.stderr.write(`[executor] skipping error tx_ready: ${nestedTx.error || "unknown error"}
5050
+ `);
5051
+ return false;
5052
+ }
5053
+ if (!nestedTx) {
5013
5054
  if (this.verbose) process.stderr.write(`[executor] storeServerTransaction: no swap_tx/send_tx/tx found in data
5014
5055
  `);
5015
- return;
5056
+ return false;
5016
5057
  }
5017
5058
  const chain = resolveChainFromTxReady(txReadyData) || Chain9.Ethereum;
5018
5059
  this.pendingPayloads.clear();
@@ -5027,6 +5068,7 @@ var AgentExecutor = class {
5027
5068
  `[executor] Stored server tx for chain ${chain}, pendingPayloads size=${this.pendingPayloads.size}
5028
5069
  `
5029
5070
  );
5071
+ return true;
5030
5072
  }
5031
5073
  hasPendingTransaction() {
5032
5074
  return this.pendingPayloads.has("latest");
@@ -5085,7 +5127,7 @@ var AgentExecutor = class {
5085
5127
  case "sign_tx":
5086
5128
  return this.signTx(params);
5087
5129
  case "get_address_book":
5088
- return this.getAddressBook();
5130
+ return this.getAddressBook(params);
5089
5131
  case "address_book_add":
5090
5132
  return this.addAddressBookEntry(params);
5091
5133
  case "address_book_remove":
@@ -5131,15 +5173,34 @@ var AgentExecutor = class {
5131
5173
  }
5132
5174
  return { balances: entries };
5133
5175
  }
5134
- async getPortfolio(_params) {
5135
- const balanceRecord = await this.vault.balances();
5136
- const entries = Object.entries(balanceRecord).map(([key, b]) => ({
5137
- chain: b.chainId || key.split(":")[0] || "",
5176
+ async getPortfolio(params) {
5177
+ const currencyRaw = String(params.currency ?? "USD").trim().toLowerCase();
5178
+ const fiatCurrency = fiatCurrencies3.includes(currencyRaw) ? currencyRaw : "usd";
5179
+ const portfolio = await this.vault.portfolio(fiatCurrency);
5180
+ const chainFilter = params.chain;
5181
+ const tickerFilter = params.ticker;
5182
+ let rows = portfolio.balances.map((b) => ({
5183
+ chain: b.chainId || "",
5138
5184
  symbol: b.symbol || "",
5139
5185
  amount: b.formattedAmount || b.amount?.toString() || "0",
5140
- decimals: b.decimals || 18
5186
+ decimals: b.decimals ?? 18,
5187
+ raw_amount: b.amount,
5188
+ fiatValue: b.fiatValue,
5189
+ fiatCurrency: b.fiatCurrency ?? portfolio.currency
5141
5190
  }));
5142
- return { balances: entries };
5191
+ if (chainFilter) {
5192
+ const chain = resolveChain(chainFilter);
5193
+ if (!chain) throw new Error(`Unknown chain: ${chainFilter}`);
5194
+ rows = rows.filter((r) => r.chain.toLowerCase() === chain.toLowerCase());
5195
+ }
5196
+ if (tickerFilter) {
5197
+ rows = rows.filter((r) => r.symbol.toLowerCase() === String(tickerFilter).toLowerCase());
5198
+ }
5199
+ return {
5200
+ balances: rows,
5201
+ totalValue: portfolio.totalValue,
5202
+ currency: portfolio.currency
5203
+ };
5143
5204
  }
5144
5205
  // ============================================================================
5145
5206
  // Chain & Token Management
@@ -5239,13 +5300,28 @@ var AgentExecutor = class {
5239
5300
  };
5240
5301
  const amount = parseAmount(amountStr, balance.decimals);
5241
5302
  const memo = params.memo;
5242
- const payload = await this.vault.prepareSendTx({ coin, receiver: toAddress, amount, memo });
5303
+ const payload = await this.vault.prepareSendTx({
5304
+ coin,
5305
+ receiver: toAddress,
5306
+ amount,
5307
+ memo
5308
+ });
5243
5309
  await this.patchEvmNonce(chain, payload);
5244
5310
  const messageHashes = await this.vault.extractMessageHashes(payload);
5245
5311
  this.pendingPayloads.clear();
5246
5312
  const payloadId = `tx_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
5247
- this.pendingPayloads.set(payloadId, { payload, coin, chain, timestamp: Date.now() });
5248
- this.pendingPayloads.set("latest", { payload, coin, chain, timestamp: Date.now() });
5313
+ this.pendingPayloads.set(payloadId, {
5314
+ payload,
5315
+ coin,
5316
+ chain,
5317
+ timestamp: Date.now()
5318
+ });
5319
+ this.pendingPayloads.set("latest", {
5320
+ payload,
5321
+ coin,
5322
+ chain,
5323
+ timestamp: Date.now()
5324
+ });
5249
5325
  return {
5250
5326
  keysign_payload: payloadId,
5251
5327
  from_chain: chain.toString(),
@@ -5285,16 +5361,19 @@ var AgentExecutor = class {
5285
5361
  const fromToken = params.from_contract || params.from_token_id;
5286
5362
  const toToken = params.to_contract || params.to_token_id;
5287
5363
  const fromCoin = { chain: fromChain, token: fromToken || void 0 };
5288
- const toCoin = { chain: toChain || fromChain, token: toToken || void 0 };
5364
+ const toCoin = {
5365
+ chain: toChain || fromChain,
5366
+ token: toToken || void 0
5367
+ };
5289
5368
  const quote = await this.vault.getSwapQuote({
5290
5369
  fromCoin,
5291
5370
  toCoin,
5292
- amount: parseFloat(amountStr)
5371
+ amount: amountStr
5293
5372
  });
5294
5373
  const swapResult = await this.vault.prepareSwapTx({
5295
5374
  fromCoin,
5296
5375
  toCoin,
5297
- amount: parseFloat(amountStr),
5376
+ amount: amountStr,
5298
5377
  swapQuote: quote,
5299
5378
  autoApprove: true
5300
5379
  });
@@ -5355,11 +5434,14 @@ var AgentExecutor = class {
5355
5434
  chain: params.chain,
5356
5435
  chain_id: params.chain_id
5357
5436
  };
5358
- this.storeServerTransaction({
5437
+ const stored = this.storeServerTransaction({
5359
5438
  tx: txData,
5360
5439
  chain: params.chain,
5361
5440
  from_chain: params.chain
5362
5441
  });
5442
+ if (!stored) {
5443
+ throw new Error("Could not stage calldata transaction for signing (invalid or empty tx payload)");
5444
+ }
5363
5445
  const chain = resolveChain(params.chain) || Chain9.Ethereum;
5364
5446
  const address = await this.vault.address(chain);
5365
5447
  return {
@@ -5432,9 +5514,10 @@ var AgentExecutor = class {
5432
5514
  }
5433
5515
  const { payload, chain } = stored;
5434
5516
  if (payload.__serverTx) {
5517
+ let result;
5435
5518
  if (chain === "Solana" && (payload.swap_tx || payload.provider)) {
5436
5519
  try {
5437
- return await this.buildAndSignSolanaSwapLocally(payload);
5520
+ result = await this.buildAndSignSolanaSwapLocally(payload);
5438
5521
  } catch (e) {
5439
5522
  if (e._phase === "prepare") {
5440
5523
  if (this.verbose)
@@ -5445,7 +5528,9 @@ var AgentExecutor = class {
5445
5528
  }
5446
5529
  }
5447
5530
  }
5448
- return this.signServerTx(payload, chain, params);
5531
+ if (!result) result = await this.signServerTx(payload, chain, params);
5532
+ if (payload.sequence_id) result.sequence_id = payload.sequence_id;
5533
+ return result;
5449
5534
  }
5450
5535
  return this.signSdkTx(payload, chain, payloadId);
5451
5536
  }
@@ -5481,7 +5566,7 @@ var AgentExecutor = class {
5481
5566
  }
5482
5567
  await this.releaseEvmLock(chain);
5483
5568
  this.pendingPayloads.clear();
5484
- const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
5569
+ const explorerUrl = VultisigSdk.getTxExplorerUrl(chain, txHash);
5485
5570
  return {
5486
5571
  tx_hash: txHash,
5487
5572
  chain: chain.toString(),
@@ -5578,7 +5663,7 @@ var AgentExecutor = class {
5578
5663
  }
5579
5664
  await this.releaseEvmLock(chain);
5580
5665
  this.pendingPayloads.clear();
5581
- const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
5666
+ const explorerUrl = VultisigSdk.getTxExplorerUrl(chain, txHash);
5582
5667
  return {
5583
5668
  tx_hash: txHash,
5584
5669
  chain: chain.toString(),
@@ -5595,12 +5680,23 @@ var AgentExecutor = class {
5595
5680
  * Uses swap params from the tx_ready event to call vault.getSwapQuote → prepareSwapTx.
5596
5681
  */
5597
5682
  async buildAndSignSolanaSwapLocally(serverTxData) {
5683
+ if (serverTxData._phase === "prepare") {
5684
+ throw Object.assign(new Error("tx_ready prepare phase: deferring to server sign path"), {
5685
+ _phase: "prepare"
5686
+ });
5687
+ }
5598
5688
  const fromChainName = serverTxData.from_chain || serverTxData.chain || "Solana";
5599
5689
  const toChainName = serverTxData.to_chain;
5600
5690
  const fromChain = resolveChain(fromChainName);
5601
- if (!fromChain) throw Object.assign(new Error(`Unknown from_chain: ${fromChainName}`), { _phase: "prepare" });
5691
+ if (!fromChain)
5692
+ throw Object.assign(new Error(`Unknown from_chain: ${fromChainName}`), {
5693
+ _phase: "prepare"
5694
+ });
5602
5695
  const toChain = toChainName ? resolveChain(toChainName) : fromChain;
5603
- if (!toChain) throw Object.assign(new Error(`Unknown to_chain: ${toChainName}`), { _phase: "prepare" });
5696
+ if (!toChain)
5697
+ throw Object.assign(new Error(`Unknown to_chain: ${toChainName}`), {
5698
+ _phase: "prepare"
5699
+ });
5604
5700
  const amountStr = serverTxData.amount;
5605
5701
  if (!amountStr)
5606
5702
  throw Object.assign(new Error("Missing amount in tx_ready data for local Solana swap build"), {
@@ -5615,10 +5711,17 @@ var AgentExecutor = class {
5615
5711
  });
5616
5712
  const fromCoin = { chain: fromChain, token: fromToken || void 0 };
5617
5713
  const toCoin = { chain: toChain, token: toToken || void 0 };
5618
- const humanAmount = Number(amountStr) / Math.pow(10, fromDecimals);
5714
+ let humanAmount;
5715
+ try {
5716
+ humanAmount = formatUnits(BigInt(amountStr), fromDecimals);
5717
+ } catch {
5718
+ throw Object.assign(new Error(`Invalid amount in tx_ready data for local Solana swap build: ${amountStr}`), {
5719
+ _phase: "prepare"
5720
+ });
5721
+ }
5619
5722
  if (this.verbose)
5620
5723
  process.stderr.write(
5621
- `[solana_local_swap] from=${fromChainName} to=${toChainName || fromChainName} amount=${amountStr}
5724
+ `[solana_local_swap] from=${fromChainName} to=${toChainName || fromChainName} amount=${amountStr} human=${humanAmount}
5622
5725
  `
5623
5726
  );
5624
5727
  if (this.vault.isEncrypted && !this.vault.isUnlocked?.()) {
@@ -5660,7 +5763,7 @@ var AgentExecutor = class {
5660
5763
  signature
5661
5764
  });
5662
5765
  this.pendingPayloads.clear();
5663
- const explorerUrl = Vultisig6.getTxExplorerUrl(chain, txHash);
5766
+ const explorerUrl = VultisigSdk.getTxExplorerUrl(chain, txHash);
5664
5767
  return {
5665
5768
  tx_hash: txHash,
5666
5769
  chain: chain.toString(),
@@ -5908,8 +6011,18 @@ var AgentExecutor = class {
5908
6011
  // ============================================================================
5909
6012
  // Address Book
5910
6013
  // ============================================================================
5911
- async getAddressBook() {
5912
- throw new Error("get_address_book is not yet implemented locally. The backend may handle this action server-side.");
6014
+ async getAddressBook(params) {
6015
+ if (!this.vultisig) {
6016
+ throw new Error(
6017
+ "get_address_book requires the CLI SDK instance. Ensure AgentConfig.vultisig is set when creating the session."
6018
+ );
6019
+ }
6020
+ const chainName = params.chain || params.chain_name;
6021
+ const chain = chainName ? resolveChain(chainName) : void 0;
6022
+ if (chainName && !chain) {
6023
+ throw new Error(`Unknown chain: ${chainName}`);
6024
+ }
6025
+ return await this.vultisig.getAddressBook(chain);
5913
6026
  }
5914
6027
  async addAddressBookEntry(_params) {
5915
6028
  throw new Error("address_book_add is not yet implemented locally. The backend may handle this action server-side.");
@@ -5922,8 +6035,34 @@ var AgentExecutor = class {
5922
6035
  // ============================================================================
5923
6036
  // Token Search & Other
5924
6037
  // ============================================================================
5925
- async searchToken(_params) {
5926
- throw new Error("search_token is not yet implemented locally. The backend may handle this action server-side.");
6038
+ async searchToken(params) {
6039
+ const query = String(params.query ?? params.q ?? "").trim().toLowerCase();
6040
+ if (!query) {
6041
+ return { tokens: [] };
6042
+ }
6043
+ const limit = 20;
6044
+ const chainName = params.chain;
6045
+ const tokenMatchesQuery = (t) => {
6046
+ const tick = t.ticker.toLowerCase();
6047
+ const addr = (t.contractAddress ?? "").toLowerCase();
6048
+ const pid = (t.priceProviderId ?? "").toLowerCase();
6049
+ return tick.includes(query) || addr.includes(query) || pid.includes(query);
6050
+ };
6051
+ if (chainName) {
6052
+ const chain = resolveChain(chainName);
6053
+ if (!chain) throw new Error(`Unknown chain: ${chainName}`);
6054
+ const tokens = VultisigSdk.getKnownTokens(chain).filter(tokenMatchesQuery).slice(0, limit);
6055
+ return { tokens };
6056
+ }
6057
+ const out = [];
6058
+ for (const c of Object.values(Chain9)) {
6059
+ for (const t of VultisigSdk.getKnownTokens(c)) {
6060
+ if (!tokenMatchesQuery(t)) continue;
6061
+ out.push(t);
6062
+ if (out.length >= limit) return { tokens: out };
6063
+ }
6064
+ }
6065
+ return { tokens: out };
5927
6066
  }
5928
6067
  async listVaults() {
5929
6068
  return {
@@ -5940,8 +6079,25 @@ var AgentExecutor = class {
5940
6079
  async scanTx(_params) {
5941
6080
  throw new Error("scan_tx is not yet implemented locally. The backend may handle this action server-side.");
5942
6081
  }
5943
- async readEvmContract(_params) {
5944
- throw new Error("read_evm_contract is not yet implemented locally. The backend may handle this action server-side.");
6082
+ async readEvmContract(params) {
6083
+ const chainName = params.chain;
6084
+ if (!chainName) throw new Error("read_evm_contract requires chain");
6085
+ const contractRaw = params.contract_address || params.contractAddress;
6086
+ if (!contractRaw) throw new Error("read_evm_contract requires contract_address");
6087
+ const functionName = params.function_name || params.functionName;
6088
+ if (!functionName) throw new Error("read_evm_contract requires function_name");
6089
+ const chain = resolveChain(chainName);
6090
+ if (!chain) throw new Error(`Unknown chain: ${chainName}`);
6091
+ if (!EVM_CHAINS.has(chain)) {
6092
+ throw new Error(`read_evm_contract only supports EVM chains (got ${chain})`);
6093
+ }
6094
+ const callParams = params.params ?? [];
6095
+ const data = await encodeContractCall(functionName, callParams);
6096
+ const addr = contractRaw.startsWith("0x") ? contractRaw : `0x${contractRaw}`;
6097
+ const to = addr;
6098
+ const from = params.from;
6099
+ const result = await evmCall(chain, { to, data, from });
6100
+ return { result };
5945
6101
  }
5946
6102
  };
5947
6103
  async function encodeContractCall(functionName, params) {
@@ -6215,7 +6371,10 @@ function parseDERSignature(sigHex) {
6215
6371
  }
6216
6372
  let offset = 0;
6217
6373
  if (raw.slice(offset, offset + 2) !== "30") {
6218
- return { r: raw.slice(0, 64).padStart(64, "0"), s: raw.slice(64).padStart(64, "0") };
6374
+ return {
6375
+ r: raw.slice(0, 64).padStart(64, "0"),
6376
+ s: raw.slice(64).padStart(64, "0")
6377
+ };
6219
6378
  }
6220
6379
  offset += 2;
6221
6380
  offset += 2;
@@ -6425,7 +6584,7 @@ var AgentSession = class {
6425
6584
  this.config = config;
6426
6585
  this.client = new AgentClient(config.backendUrl);
6427
6586
  this.client.verbose = !!config.verbose;
6428
- this.executor = new AgentExecutor(vault, !!config.verbose, vault.publicKeys.ecdsa);
6587
+ this.executor = new AgentExecutor(vault, !!config.verbose, vault.publicKeys.ecdsa, config.vultisig);
6429
6588
  this.publicKey = vault.publicKeys.ecdsa;
6430
6589
  if (config.password) {
6431
6590
  this.executor.setPassword(config.password);
@@ -6582,6 +6741,7 @@ var AgentSession = class {
6582
6741
  error: result.error || ""
6583
6742
  };
6584
6743
  }
6744
+ let serverTxStoredFromStream = 0;
6585
6745
  const streamResult = await this.client.sendMessageStream(
6586
6746
  this.conversationId,
6587
6747
  request,
@@ -6602,16 +6762,11 @@ var AgentSession = class {
6602
6762
  ui.onSuggestions(suggestions);
6603
6763
  },
6604
6764
  onTxReady: (tx) => {
6605
- const txData = tx?.swap_tx || tx?.send_tx || tx?.tx;
6606
- if (txData?.status === "error" || txData?.error) {
6607
- if (this.config.verbose)
6608
- process.stderr.write(`[session] skipping error tx_ready: ${txData.error || "unknown error"}
6609
- `);
6610
- return;
6611
- }
6612
- this.executor.storeServerTransaction(tx);
6613
- if (this.config.password) {
6614
- this.executor.setPassword(this.config.password);
6765
+ if (this.executor.storeServerTransaction(tx)) {
6766
+ serverTxStoredFromStream++;
6767
+ if (this.config.password) {
6768
+ this.executor.setPassword(this.config.password);
6769
+ }
6615
6770
  }
6616
6771
  },
6617
6772
  onMessage: (_msg) => {
@@ -6662,10 +6817,12 @@ var AgentSession = class {
6662
6817
  return;
6663
6818
  }
6664
6819
  }
6665
- if (streamResult.transactions.length > 0 && this.executor.hasPendingTransaction()) {
6820
+ if (serverTxStoredFromStream > 0) {
6666
6821
  if (this.config.verbose)
6667
- process.stderr.write(`[session] ${streamResult.transactions.length} tx_ready events, signing client-side
6668
- `);
6822
+ process.stderr.write(
6823
+ `[session] ${serverTxStoredFromStream} stored server tx from tx_ready, signing client-side
6824
+ `
6825
+ );
6669
6826
  const signAction = {
6670
6827
  id: `tx_sign_${Date.now()}`,
6671
6828
  type: "sign_tx",
@@ -7180,6 +7337,7 @@ async function executeAgent(ctx2, options) {
7180
7337
  const config = {
7181
7338
  backendUrl: options.backendUrl || process.env.VULTISIG_AGENT_URL || "https://abe.vultisig.com",
7182
7339
  vaultName: vault.name,
7340
+ vultisig: ctx2.sdk,
7183
7341
  password: options.password,
7184
7342
  viaAgent: options.viaAgent,
7185
7343
  sessionId: options.sessionId,
@@ -7221,6 +7379,7 @@ async function executeAgentAsk(ctx2, message, options) {
7221
7379
  const config = {
7222
7380
  backendUrl: options.backendUrl || process.env.VULTISIG_AGENT_URL || "https://abe.vultisig.com",
7223
7381
  vaultName: vault.name,
7382
+ vultisig: ctx2.sdk,
7224
7383
  password: options.password,
7225
7384
  sessionId: options.session,
7226
7385
  verbose: options.verbose,
@@ -7725,7 +7884,7 @@ var EventBuffer = class {
7725
7884
  };
7726
7885
 
7727
7886
  // src/interactive/session.ts
7728
- import { fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
7887
+ import { fiatCurrencies as fiatCurrencies4 } from "@vultisig/sdk";
7729
7888
  import chalk12 from "chalk";
7730
7889
  import ora3 from "ora";
7731
7890
  import * as readline3 from "readline";
@@ -8543,9 +8702,9 @@ Error: ${error2.message}`));
8543
8702
  i++;
8544
8703
  }
8545
8704
  }
8546
- if (!fiatCurrencies3.includes(currency)) {
8705
+ if (!fiatCurrencies4.includes(currency)) {
8547
8706
  console.log(chalk12.red(`Invalid currency: ${currency}`));
8548
- console.log(chalk12.yellow(`Supported currencies: ${fiatCurrencies3.join(", ")}`));
8707
+ console.log(chalk12.yellow(`Supported currencies: ${fiatCurrencies4.join(", ")}`));
8549
8708
  return;
8550
8709
  }
8551
8710
  const raw = args.includes("--raw");
@@ -8794,7 +8953,7 @@ var cachedVersion = null;
8794
8953
  function getVersion() {
8795
8954
  if (cachedVersion) return cachedVersion;
8796
8955
  if (true) {
8797
- cachedVersion = "0.14.2";
8956
+ cachedVersion = "0.15.2";
8798
8957
  return cachedVersion;
8799
8958
  }
8800
8959
  try {
@@ -9289,7 +9448,7 @@ async function init(vaultOverride, unlockPassword, passwordTTL) {
9289
9448
  }
9290
9449
  const globalOptions = program.opts();
9291
9450
  const serverEndpoints = resolveServerEndpoints(globalOptions);
9292
- const sdk = new Vultisig7({
9451
+ const sdk = new Vultisig6({
9293
9452
  onPasswordRequired: createPasswordCallback(),
9294
9453
  ...serverEndpoints ? { serverEndpoints } : {},
9295
9454
  ...passwordTTL !== void 0 ? { passwordCache: { defaultTTL: passwordTTL } } : {}
@@ -9906,7 +10065,7 @@ program.command("update").description("Check for updates and show update command
9906
10065
  setupCompletionCommand(program);
9907
10066
  async function startInteractiveMode() {
9908
10067
  const serverEndpoints = resolveServerEndpoints(parseServerEndpointOverridesFromArgv(process.argv.slice(2)));
9909
- const sdk = new Vultisig7({
10068
+ const sdk = new Vultisig6({
9910
10069
  onPasswordRequired: createPasswordCallback(),
9911
10070
  ...serverEndpoints ? { serverEndpoints } : {}
9912
10071
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vultisig/cli",
3
- "version": "0.14.2",
3
+ "version": "0.15.2",
4
4
  "description": "Command-line wallet for Vultisig - multi-chain MPC wallet management",
5
5
  "type": "module",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "cli:interactive": "npx tsx src/index.ts --interactive",
22
22
  "repl": "npx tsx src/index.ts --interactive",
23
23
  "typecheck": "tsc --noEmit",
24
+ "test": "vitest run",
24
25
  "prepublishOnly": "npm run build"
25
26
  },
26
27
  "keywords": [
@@ -53,8 +54,8 @@
53
54
  "@cosmjs/proto-signing": "^0.38.1",
54
55
  "@cosmjs/stargate": "^0.38.1",
55
56
  "@noble/hashes": "^2.0.1",
56
- "@vultisig/rujira": "^9.0.0",
57
- "@vultisig/sdk": "^0.14.1",
57
+ "@vultisig/rujira": "^10.0.0",
58
+ "@vultisig/sdk": "^0.15.3",
58
59
  "chalk": "^5.6.2",
59
60
  "cli-table3": "^0.6.5",
60
61
  "commander": "^14.0.3",
@@ -62,6 +63,7 @@
62
63
  "ora": "^9.3.0",
63
64
  "qrcode-terminal": "^0.12.0",
64
65
  "tabtab": "^3.0.2",
66
+ "viem": "^2.45.1",
65
67
  "ws": "^8.19.0"
66
68
  },
67
69
  "devDependencies": {
@@ -71,7 +73,8 @@
71
73
  "@types/ws": "^8.18.1",
72
74
  "esbuild": "^0.27.4",
73
75
  "tsx": "^4.21.0",
74
- "typescript": "^5.9.3"
76
+ "typescript": "^5.9.3",
77
+ "vitest": "^3.2.4"
75
78
  },
76
79
  "engines": {
77
80
  "node": ">=20.0.0"