@vultisig/cli 6.3.0 → 7.2.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 +30 -0
  2. package/dist/index.js +318 -73
  3. package/package.json +6 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @vultisig/cli
2
2
 
3
+ ## 7.2.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#2321](https://github.com/vultisig/vultisig-sdk/pull/2321) [`f45627f`](https://github.com/vultisig/vultisig-sdk/commit/f45627f7a986a3491c4586363acbfbdaa5f6b9c3) Thanks [@rcoderdev](https://github.com/rcoderdev)! - Decode modern and legacy Cosmos governance votes into a lossless action-specific shared envelope.
8
+
9
+ - Updated dependencies [[`f45627f`](https://github.com/vultisig/vultisig-sdk/commit/f45627f7a986a3491c4586363acbfbdaa5f6b9c3), [`fd7507a`](https://github.com/vultisig/vultisig-sdk/commit/fd7507af5d9dc14938da80372bc3c1a8ec02a459), [`a5def09`](https://github.com/vultisig/vultisig-sdk/commit/a5def098e06cf7269174eb6840dc516d78ec55f9), [`bcca32c`](https://github.com/vultisig/vultisig-sdk/commit/bcca32c885066e2bf224aa37d6666c68a3684956), [`052f2e3`](https://github.com/vultisig/vultisig-sdk/commit/052f2e3b56509c1ddc2bcc14f21d0d1a5ee5d674), [`231070e`](https://github.com/vultisig/vultisig-sdk/commit/231070ea811a79a84b43a98fadd80c2811071c94)]:
10
+ - @vultisig/sdk@7.2.0
11
+ - @vultisig/core-chain@5.2.0
12
+
13
+ ## 7.0.0
14
+
15
+ ### Patch Changes
16
+
17
+ - [#2295](https://github.com/vultisig/vultisig-sdk/pull/2295) [`27dfd44`](https://github.com/vultisig/vultisig-sdk/commit/27dfd44fe6002d84c6f1e57ed0729ec22303a749) Thanks [@neavra](https://github.com/neavra)! - Refresh EIP-1559 priority fees immediately before CLI signing so zero-tip transactions use the live RPC suggestion, respect canonical per-chain minimums, and never exceed `maxFeePerGas`. Broadcast rejection messages now retain the actionable RPC reason without exposing the signed raw transaction.
18
+
19
+ - [#1996](https://github.com/vultisig/vultisig-sdk/pull/1996) [`a56c7cb`](https://github.com/vultisig/vultisig-sdk/commit/a56c7cb2b1bcfa74edcee0ae7e5552f830804734) Thanks [@gomesalexandre](https://github.com/gomesalexandre)! - Export the `ChainDiscoveryAggregate` type from the public SDK seedphrase and root entrypoints so consumers can import the aggregate result returned by seedphrase discovery without restating the contract locally.
20
+
21
+ - [#2307](https://github.com/vultisig/vultisig-sdk/pull/2307) [`42fd134`](https://github.com/vultisig/vultisig-sdk/commit/42fd1349b52fec44a5a0f47a898ad1537ac6ff60) Thanks [@rcoderdev](https://github.com/rcoderdev)! - Refresh stale raw EVM server nonces from pending RPC state immediately before signing.
22
+
23
+ - [#2294](https://github.com/vultisig/vultisig-sdk/pull/2294) [`a3e1415`](https://github.com/vultisig/vultisig-sdk/commit/a3e1415758077867343de920af5bec36f2f89b11) Thanks [@neavra](https://github.com/neavra)! - Route pipe-mode confirmation and password replies around the serialized message queue so signable agent turns can complete.
24
+
25
+ - [#2318](https://github.com/vultisig/vultisig-sdk/pull/2318) [`26a83ed`](https://github.com/vultisig/vultisig-sdk/commit/26a83ed417c9543f9c92d28cbb1bd35f0f914817) Thanks [@rcoderdev](https://github.com/rcoderdev)! - Fix TRON transaction lifecycle safety across fee preparation, status polling, and broadcast. Native TRX bandwidth estimates now measure the WalletCore-serialized signed transaction including memo bytes, default transaction times use the fetched block timestamp, expired raw transactions terminate polling, and successful broadcasts must return the deterministic local transaction hash.
26
+
27
+ - Updated dependencies [[`27dfd44`](https://github.com/vultisig/vultisig-sdk/commit/27dfd44fe6002d84c6f1e57ed0729ec22303a749), [`1b3fb49`](https://github.com/vultisig/vultisig-sdk/commit/1b3fb498ad3f78b84b92b9f09b41fe8956fc5e9a), [`a56c7cb`](https://github.com/vultisig/vultisig-sdk/commit/a56c7cb2b1bcfa74edcee0ae7e5552f830804734), [`bd15294`](https://github.com/vultisig/vultisig-sdk/commit/bd15294a739c7977b26704b7b6df38e83e6bda23), [`7d4c116`](https://github.com/vultisig/vultisig-sdk/commit/7d4c116cc7a940e96abf075790c4a387edd03987), [`7b65284`](https://github.com/vultisig/vultisig-sdk/commit/7b65284f7fe73f538283a201a416efdbbda0830c), [`1716a79`](https://github.com/vultisig/vultisig-sdk/commit/1716a7946bf617ff16f8bb15f33b279efbb4a87d), [`a711b6c`](https://github.com/vultisig/vultisig-sdk/commit/a711b6ca8a8a7ae39d0efbb1c919b23b6538a1b8), [`ef6a8bb`](https://github.com/vultisig/vultisig-sdk/commit/ef6a8bb0007c6576179e5a8cb10aa47820075b57), [`26a83ed`](https://github.com/vultisig/vultisig-sdk/commit/26a83ed417c9543f9c92d28cbb1bd35f0f914817), [`4978c02`](https://github.com/vultisig/vultisig-sdk/commit/4978c02f08521600fc70fa6b4753bae5a589371b), [`8a2c126`](https://github.com/vultisig/vultisig-sdk/commit/8a2c126333bb1010cf859eef6a4876e7a20da18f), [`b19c5d5`](https://github.com/vultisig/vultisig-sdk/commit/b19c5d56f4d00d92493f8da74ff80d92b1d87c68)]:
28
+ - @vultisig/sdk@7.0.0
29
+ - @vultisig/core-chain@5.0.0
30
+ - @vultisig/rujira@71.0.0
31
+ - @vultisig/client-shared@0.3.6
32
+
3
33
  ## 6.3.0
4
34
 
5
35
  ### Patch Changes
package/dist/index.js CHANGED
@@ -8130,7 +8130,7 @@ var require_dist = __commonJS({
8130
8130
  import "dotenv/config";
8131
8131
  import { promises as fs4 } from "node:fs";
8132
8132
  import { descriptions } from "@vultisig/client-shared";
8133
- import { Chain as Chain14, parseKeygenQR, Vultisig as Vultisig6 } from "@vultisig/sdk";
8133
+ import { Chain as Chain15, parseKeygenQR, Vultisig as Vultisig6 } from "@vultisig/sdk";
8134
8134
  import chalk16 from "chalk";
8135
8135
  import { InvalidArgumentError, program as program2 } from "commander";
8136
8136
 
@@ -11487,7 +11487,7 @@ function resolveTxStatusParams(params) {
11487
11487
  }
11488
11488
  var POLL_INTERVAL_MS = 5e3;
11489
11489
  var DEFAULT_TIMEOUT_SEC = 120;
11490
- var isTerminal = (status) => status === "success" || status === "error";
11490
+ var isTerminal = (status) => status === "success" || status === "error" || status === "expired";
11491
11491
  function resolveTimeoutMs(timeoutSec) {
11492
11492
  if (typeof timeoutSec !== "number" || !Number.isFinite(timeoutSec)) {
11493
11493
  return DEFAULT_TIMEOUT_SEC * 1e3;
@@ -11527,7 +11527,7 @@ async function executeTxStatus(ctx2, params, opts = {}) {
11527
11527
  }
11528
11528
  if (result.status === "success") {
11529
11529
  recordResolution(params.txHash, "confirmed");
11530
- } else if (result.status === "error") {
11530
+ } else if (result.status === "error" || result.status === "expired") {
11531
11531
  recordResolution(params.txHash, "failed");
11532
11532
  }
11533
11533
  spinner.succeed(`Transaction status: ${toCliStatus(result.status)}`);
@@ -14046,9 +14046,104 @@ import { randomUUID } from "node:crypto";
14046
14046
  // src/agent/toolOutputSigning.ts
14047
14047
  import { getChainKind as getChainKind5 } from "@vultisig/sdk";
14048
14048
 
14049
+ // ../../packages/core/chain/dist/Chain.js
14050
+ var EthereumL2Chain = {
14051
+ Arbitrum: "Arbitrum",
14052
+ Base: "Base",
14053
+ Blast: "Blast",
14054
+ Optimism: "Optimism",
14055
+ Zksync: "Zksync",
14056
+ Mantle: "Mantle",
14057
+ Robinhood: "Robinhood"
14058
+ };
14059
+ var EvmChain = {
14060
+ ...EthereumL2Chain,
14061
+ Avalanche: "Avalanche",
14062
+ CronosChain: "CronosChain",
14063
+ BSC: "BSC",
14064
+ Ethereum: "Ethereum",
14065
+ Polygon: "Polygon",
14066
+ Hyperliquid: "Hyperliquid",
14067
+ Sei: "Sei"
14068
+ };
14069
+ var UtxoChain;
14070
+ (function(UtxoChain2) {
14071
+ UtxoChain2["Bitcoin"] = "Bitcoin";
14072
+ UtxoChain2["BitcoinCash"] = "Bitcoin-Cash";
14073
+ UtxoChain2["Litecoin"] = "Litecoin";
14074
+ UtxoChain2["Dogecoin"] = "Dogecoin";
14075
+ UtxoChain2["Dash"] = "Dash";
14076
+ UtxoChain2["Zcash"] = "Zcash";
14077
+ })(UtxoChain || (UtxoChain = {}));
14078
+ var cosmosChainsByKind = {
14079
+ ibcEnabled: {
14080
+ Cosmos: "Cosmos",
14081
+ Osmosis: "Osmosis",
14082
+ Dydx: "Dydx",
14083
+ Terra: "Terra",
14084
+ TerraClassic: "TerraClassic",
14085
+ Noble: "Noble",
14086
+ Akash: "Akash"
14087
+ },
14088
+ vaultBased: {
14089
+ THORChain: "THORChain",
14090
+ MayaChain: "MayaChain"
14091
+ }
14092
+ };
14093
+ var IbcEnabledCosmosChain = cosmosChainsByKind.ibcEnabled;
14094
+ var VaultBasedCosmosChain = cosmosChainsByKind.vaultBased;
14095
+ var CosmosChain = {
14096
+ ...IbcEnabledCosmosChain,
14097
+ ...VaultBasedCosmosChain
14098
+ };
14099
+ var OtherChain;
14100
+ (function(OtherChain2) {
14101
+ OtherChain2["Sui"] = "Sui";
14102
+ OtherChain2["Solana"] = "Solana";
14103
+ OtherChain2["Polkadot"] = "Polkadot";
14104
+ OtherChain2["Bittensor"] = "Bittensor";
14105
+ OtherChain2["Ton"] = "Ton";
14106
+ OtherChain2["Ripple"] = "Ripple";
14107
+ OtherChain2["Tron"] = "Tron";
14108
+ OtherChain2["Cardano"] = "Cardano";
14109
+ OtherChain2["QBTC"] = "QBTC";
14110
+ })(OtherChain || (OtherChain = {}));
14111
+ var Chain10 = {
14112
+ ...EvmChain,
14113
+ ...UtxoChain,
14114
+ ...CosmosChain,
14115
+ ...OtherChain
14116
+ };
14117
+ var UtxoBasedChain = [...Object.values(UtxoChain), OtherChain.Cardano];
14118
+ var defaultChains = [Chain10.Bitcoin, Chain10.Ethereum, Chain10.THORChain, Chain10.Solana, Chain10.BSC];
14119
+
14120
+ // ../../packages/core/chain/dist/chains/evm/tx/fee/index.js
14121
+ var evmChainTxFeeFormat = {
14122
+ [EvmChain.Arbitrum]: "enveloped",
14123
+ [EvmChain.Base]: "enveloped",
14124
+ [EvmChain.Blast]: "enveloped",
14125
+ [EvmChain.Optimism]: "enveloped",
14126
+ [EvmChain.Zksync]: "enveloped",
14127
+ [EvmChain.Avalanche]: "enveloped",
14128
+ [EvmChain.CronosChain]: "enveloped",
14129
+ [EvmChain.BSC]: "legacy",
14130
+ [EvmChain.Ethereum]: "enveloped",
14131
+ [EvmChain.Polygon]: "enveloped",
14132
+ [EvmChain.Mantle]: "enveloped",
14133
+ [EvmChain.Hyperliquid]: "enveloped",
14134
+ // Sei must stay 'enveloped': iOS (EVMHelper.setGasParameters) and Android
14135
+ // (EthereumGasHelper.setGasParameters) sign every EVM chain except BSC as
14136
+ // EIP-1559. A different tx mode changes the pre-signing hash and therefore
14137
+ // the relay message_id, so extension<->mobile Sei co-signing deadlocks
14138
+ // (vultisig-windows#4369).
14139
+ [EvmChain.Sei]: "enveloped",
14140
+ [EvmChain.Robinhood]: "enveloped"
14141
+ };
14142
+
14049
14143
  // src/agent/executor.ts
14050
14144
  import {
14051
- Chain as Chain11,
14145
+ Chain as Chain12,
14146
+ clampEvmPriorityFee,
14052
14147
  computeEip712Hash,
14053
14148
  getChainKind as getChainKind4,
14054
14149
  getEvmRpcUrl,
@@ -14220,7 +14315,7 @@ function sleep2(ms) {
14220
14315
 
14221
14316
  // src/agent/hlOrder.ts
14222
14317
  var import_msgpack = __toESM(require_dist(), 1);
14223
- import { Chain as Chain10 } from "@vultisig/sdk";
14318
+ import { Chain as Chain11 } from "@vultisig/sdk";
14224
14319
  var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
14225
14320
  var HL_AGENT_TYPES = {
14226
14321
  Agent: [
@@ -14371,7 +14466,7 @@ async function validateRequestBinding(payload, expected, vault, now) {
14371
14466
  if (typeof payload.owner_public_key !== "string") throw new Error("HL_INVALID_OWNER_PUBLIC_KEY");
14372
14467
  if (payload.owner_public_key.toLowerCase() !== expected.publicKey.toLowerCase()) throw new Error("HL_OWNER_MISMATCH");
14373
14468
  if (typeof payload.vault_address !== "string") throw new Error("HL_INVALID_VAULT_ADDRESS");
14374
- const localAddress = await vault.address(Chain10.Ethereum);
14469
+ const localAddress = await vault.address(Chain11.Ethereum);
14375
14470
  if (payload.vault_address.toLowerCase() !== localAddress.toLowerCase()) throw new Error("HL_VAULT_ADDRESS_MISMATCH");
14376
14471
  if (typeof payload.expires_at !== "string") throw new Error("HL_INVALID_EXPIRY");
14377
14472
  const expiry = Date.parse(payload.expires_at);
@@ -14464,6 +14559,11 @@ var EVM_CHAINS = /* @__PURE__ */ new Set([
14464
14559
  ]);
14465
14560
  var ERC20_TRANSFER_SELECTOR = "0xa9059cbb";
14466
14561
  var ERC20_TRANSFER_ABI = parseAbi(["function transfer(address to, uint256 value)"]);
14562
+ var changedByMoreThanHalf = (before, after) => {
14563
+ if (before === after) return false;
14564
+ const difference = before > after ? before - after : after - before;
14565
+ return difference * 2n > before;
14566
+ };
14467
14567
  function decodeErc20Transfer(calldata) {
14468
14568
  if (calldata.slice(0, ERC20_TRANSFER_SELECTOR.length).toLowerCase() !== ERC20_TRANSFER_SELECTOR) return null;
14469
14569
  try {
@@ -14709,12 +14809,12 @@ var AgentExecutor = class {
14709
14809
  if (this.vault.isEncrypted && !this.vault.isUnlocked?.() && this.password) {
14710
14810
  await this.vault.unlock?.(this.password);
14711
14811
  }
14712
- const expectedAddress = await this.vault.address(Chain11.Ethereum);
14812
+ const expectedAddress = await this.vault.address(Chain12.Ethereum);
14713
14813
  const signatures = [];
14714
14814
  for (const step of payload.steps) {
14715
14815
  const signed = await this.vault.signBytes({
14716
14816
  data: step.digest,
14717
- chain: Chain11.Ethereum
14817
+ chain: Chain12.Ethereum
14718
14818
  });
14719
14819
  const canonical = toCanonicalEvmSignature(signed.signature, signed.recovery ?? 0);
14720
14820
  const v = canonical.recovery + 27;
@@ -14889,7 +14989,7 @@ var AgentExecutor = class {
14889
14989
  if (!nestedTx && txReadyData && typeof txReadyData === "object") {
14890
14990
  const txArgs = txReadyData.txArgs;
14891
14991
  if (txArgs && typeof txArgs === "object" && typeof txArgs.to === "string" && typeof txArgs.amount === "string") {
14892
- const chain2 = resolveChainFromTxReady(txReadyData) || Chain11.Ethereum;
14992
+ const chain2 = resolveChainFromTxReady(txReadyData) || Chain12.Ethereum;
14893
14993
  if (getChainKind4(chain2) !== "evm") {
14894
14994
  this.pendingPayloads.clear();
14895
14995
  this.pendingLegs = [];
@@ -14914,7 +15014,7 @@ var AgentExecutor = class {
14914
15014
  `);
14915
15015
  return false;
14916
15016
  }
14917
- const chain = resolveChainFromTxReady(txReadyData) || Chain11.Ethereum;
15017
+ const chain = resolveChainFromTxReady(txReadyData) || Chain12.Ethereum;
14918
15018
  this.pendingPayloads.clear();
14919
15019
  this.pendingLegs = [];
14920
15020
  this.pendingPayloads.set("latest", {
@@ -15572,8 +15672,9 @@ var AgentExecutor = class {
15572
15672
  nonce: swapTx.nonce
15573
15673
  }
15574
15674
  });
15575
- await this.patchEvmNonce(chain, keysignPayload);
15675
+ const nonceGraceAge = Date.now() - (this.evmLastBroadcast.get(chain.toString()) ?? 0);
15576
15676
  await this.patchEvmGas(chain, keysignPayload);
15677
+ await this.patchEvmNonce(chain, keysignPayload, nonceGraceAge);
15577
15678
  const messageHashes = await this.vault.extractMessageHashes(keysignPayload);
15578
15679
  const signature = await this.vault.sign(
15579
15680
  {
@@ -15825,33 +15926,45 @@ var AgentExecutor = class {
15825
15926
  }
15826
15927
  }
15827
15928
  /**
15828
- * Patch the EVM nonce in a keysign payload if our local state is ahead of on-chain.
15829
- * The payload's blockchainSpecific.ethereumSpecific.nonce was set from RPC during
15830
- * prepareSendTx(). If we have locally-tracked pending txs, we override with a higher value.
15929
+ * Refresh the EVM nonce from pending RPC state, then patch it if our local state
15930
+ * is further ahead. Raw server envelopes may carry a nonce that became stale
15931
+ * before signing, so the RPC refresh must not depend on local state existing.
15831
15932
  *
15832
15933
  * Also detects evicted txs: if local state claims a higher nonce but there are
15833
15934
  * no pending txs in the mempool (pending == latest), the intermediate txs were
15834
15935
  * dropped and local state is stale.
15835
15936
  */
15836
- async patchEvmNonce(chain, payload) {
15837
- if (!this.stateStore || !isEvmChain(chain)) return;
15937
+ async patchEvmNonce(chain, payload, capturedBroadcastAge) {
15938
+ if (!isEvmChain(chain)) return;
15838
15939
  const bs = payload.blockchainSpecific;
15839
15940
  if (!bs || bs.case !== "ethereumSpecific") return;
15840
- const rpcNonce = bs.value.nonce;
15941
+ const recentBroadcastAge = capturedBroadcastAge ?? Date.now() - (this.evmLastBroadcast.get(chain.toString()) ?? 0);
15942
+ const hasRecentBroadcast = recentBroadcastAge < 3e4;
15943
+ const payloadNonce = bs.value.nonce;
15944
+ const latestNonce = this.stateStore && !hasRecentBroadcast ? await this.fetchEvmLatestNonce(chain) : null;
15945
+ const pendingNonce = await this.fetchEvmPendingNonce(chain);
15946
+ const rpcNonce = [payloadNonce, latestNonce, pendingNonce].reduce(
15947
+ (highest, nonce) => nonce !== null && nonce > highest ? nonce : highest,
15948
+ payloadNonce
15949
+ );
15950
+ if (rpcNonce !== payloadNonce) {
15951
+ bs.value.nonce = rpcNonce;
15952
+ if (this.verbose) process.stderr.write(`[nonce] Refreshed ${chain} nonce: ${payloadNonce} \u2192 ${rpcNonce}
15953
+ `);
15954
+ }
15955
+ if (!this.stateStore) return;
15841
15956
  const nextNonce = this.stateStore.getNextEvmNonce(chain, rpcNonce);
15842
15957
  if (nextNonce !== rpcNonce) {
15843
- const lastBroadcast = this.evmLastBroadcast.get(chain.toString()) ?? 0;
15844
- if (Date.now() - lastBroadcast < 3e4) {
15958
+ if (hasRecentBroadcast) {
15845
15959
  if (this.verbose)
15846
15960
  process.stderr.write(
15847
- `[nonce] Keeping local nonce ${nextNonce} for ${chain} (broadcast ${Date.now() - lastBroadcast}ms ago)
15961
+ `[nonce] Keeping local nonce ${nextNonce} for ${chain} (broadcast ${recentBroadcastAge}ms ago)
15848
15962
  `
15849
15963
  );
15850
15964
  bs.value.nonce = nextNonce;
15851
15965
  return;
15852
15966
  }
15853
- const pendingNonce = await this.fetchEvmPendingNonce(chain);
15854
- if (pendingNonce !== null && pendingNonce === rpcNonce) {
15967
+ if (latestNonce !== null && pendingNonce === latestNonce) {
15855
15968
  if (this.verbose)
15856
15969
  process.stderr.write(
15857
15970
  `[nonce] Stale local state for ${chain}: local=${nextNonce}, on-chain=${rpcNonce}, no pending txs \u2014 using on-chain nonce
@@ -15861,7 +15974,8 @@ var AgentExecutor = class {
15861
15974
  return;
15862
15975
  }
15863
15976
  const nonceGap = nextNonce - rpcNonce;
15864
- if (pendingNonce === null && nonceGap > 3n) {
15977
+ const pendingStateVerified = pendingNonce !== null && latestNonce !== null && pendingNonce >= latestNonce;
15978
+ if (!pendingStateVerified && nonceGap > 3n) {
15865
15979
  process.stderr.write(
15866
15980
  `[nonce] Warning: pending nonce was not verified for ${chain}; signing will continue with on-chain nonce ${rpcNonce} because the local gap is ${nonceGap}
15867
15981
  `
@@ -15870,7 +15984,7 @@ var AgentExecutor = class {
15870
15984
  return;
15871
15985
  }
15872
15986
  bs.value.nonce = nextNonce;
15873
- if (pendingNonce === null) {
15987
+ if (!pendingStateVerified) {
15874
15988
  process.stderr.write(
15875
15989
  `[nonce] Warning: pending nonce was not verified for ${chain}; signing will continue with local nonce ${nextNonce}
15876
15990
  `
@@ -15889,7 +16003,51 @@ var AgentExecutor = class {
15889
16003
  if (!isEvmChain(chain)) return;
15890
16004
  const bs = payload.blockchainSpecific;
15891
16005
  if (!bs || bs.case !== "ethereumSpecific") return;
16006
+ const isEip1559 = evmChainTxFeeFormat[chain] === "enveloped";
16007
+ if (!isEip1559) return;
15892
16008
  const rpcUrl = getEvmRpcUrl(chain);
16009
+ const currentPriorityFee = BigInt(bs.value.priorityFee || "0");
16010
+ const currentMaxFee = BigInt(bs.value.maxFeePerGasWei || "0");
16011
+ const baseFee = await this.fetchEvmBaseFee(chain, rpcUrl);
16012
+ let priorityFee = currentPriorityFee;
16013
+ if (currentPriorityFee > 0n) {
16014
+ priorityFee = clampEvmPriorityFee(chain, currentPriorityFee);
16015
+ if (priorityFee < currentPriorityFee) {
16016
+ process.stderr.write(
16017
+ `[gas] Warning: ${chain} maxPriorityFeePerGas ${currentPriorityFee} exceeded the safety ceiling; clamped to ${priorityFee}
16018
+ `
16019
+ );
16020
+ }
16021
+ } else {
16022
+ const rpcPriorityFee = await this.fetchEvmPriorityFee(rpcUrl);
16023
+ const baseFeeFallback = baseFee === null ? 0n : baseFee / 10n;
16024
+ const fallbackPriorityFee = baseFeeFallback < 2000000000n ? baseFeeFallback : 2000000000n;
16025
+ priorityFee = clampEvmPriorityFee(chain, rpcPriorityFee > 0n ? rpcPriorityFee : fallbackPriorityFee);
16026
+ }
16027
+ const minMaxFee = baseFee === null ? priorityFee : baseFee * 25n / 10n + priorityFee;
16028
+ const patchedMaxFee = currentMaxFee < minMaxFee ? minMaxFee : currentMaxFee;
16029
+ if (priorityFee !== currentPriorityFee) {
16030
+ bs.value.priorityFee = priorityFee.toString();
16031
+ if (this.verbose)
16032
+ process.stderr.write(`[gas] Patched ${chain} maxPriorityFeePerGas: ${currentPriorityFee} \u2192 ${priorityFee}
16033
+ `);
16034
+ }
16035
+ if (currentMaxFee < patchedMaxFee) {
16036
+ bs.value.maxFeePerGasWei = patchedMaxFee.toString();
16037
+ if (this.verbose)
16038
+ process.stderr.write(
16039
+ `[gas] Bumped ${chain} maxFeePerGas: ${currentMaxFee} \u2192 ${patchedMaxFee} (baseFee=${baseFee ?? "unavailable"})
16040
+ `
16041
+ );
16042
+ }
16043
+ if (changedByMoreThanHalf(currentPriorityFee, priorityFee) || changedByMoreThanHalf(currentMaxFee, patchedMaxFee)) {
16044
+ process.stderr.write(
16045
+ `[gas] Notice: ${chain} fees adjusted after confirmation: maxPriorityFeePerGas ${currentPriorityFee} \u2192 ${priorityFee}, maxFeePerGas ${currentMaxFee} \u2192 ${patchedMaxFee}
16046
+ `
16047
+ );
16048
+ }
16049
+ }
16050
+ async fetchEvmBaseFee(chain, rpcUrl) {
15893
16051
  try {
15894
16052
  const res = await fetch(rpcUrl, {
15895
16053
  method: "POST",
@@ -15906,24 +16064,33 @@ var AgentExecutor = class {
15906
16064
  if (!res.ok || data?.error || data?.result?.baseFeePerGas === void 0 || data?.result?.baseFeePerGas === null) {
15907
16065
  throw new Error(`Failed to fetch current base fee for ${chain}`);
15908
16066
  }
15909
- const baseFee = BigInt(data.result.baseFeePerGas);
15910
- if (baseFee === 0n) return;
15911
- const currentPriorityFee = BigInt(bs.value.priorityFee || "0");
15912
- const currentMaxFee = BigInt(bs.value.maxFeePerGasWei || "0");
15913
- const minMaxFee = baseFee * 25n / 10n + currentPriorityFee;
15914
- if (currentMaxFee < minMaxFee) {
15915
- bs.value.maxFeePerGasWei = minMaxFee.toString();
15916
- if (this.verbose)
15917
- process.stderr.write(
15918
- `[gas] Bumped ${chain} maxFeePerGas: ${currentMaxFee} \u2192 ${minMaxFee} (baseFee=${baseFee})
15919
- `
15920
- );
15921
- }
16067
+ return BigInt(data.result.baseFeePerGas);
15922
16068
  } catch {
15923
16069
  process.stderr.write(
15924
16070
  `[gas] Warning: gas estimate was not refreshed for ${chain}; signing will continue with the original estimate
15925
16071
  `
15926
16072
  );
16073
+ return null;
16074
+ }
16075
+ }
16076
+ async fetchEvmPriorityFee(rpcUrl) {
16077
+ try {
16078
+ const res = await fetch(rpcUrl, {
16079
+ method: "POST",
16080
+ headers: { "Content-Type": "application/json" },
16081
+ body: JSON.stringify({
16082
+ jsonrpc: "2.0",
16083
+ method: "eth_maxPriorityFeePerGas",
16084
+ params: [],
16085
+ id: 2
16086
+ }),
16087
+ signal: AbortSignal.timeout(5e3)
16088
+ });
16089
+ const data = await res.json();
16090
+ if (!res.ok || data?.error || data?.result === void 0) return 0n;
16091
+ return BigInt(data.result);
16092
+ } catch {
16093
+ return 0n;
15927
16094
  }
15928
16095
  }
15929
16096
  /**
@@ -15931,6 +16098,16 @@ var AgentExecutor = class {
15931
16098
  * Returns null if the RPC call fails (non-fatal).
15932
16099
  */
15933
16100
  async fetchEvmPendingNonce(chain) {
16101
+ return this.fetchEvmNonce(chain, "pending");
16102
+ }
16103
+ /**
16104
+ * Fetch the confirmed nonce from RPC (eth_getTransactionCount with "latest" tag).
16105
+ * Returns null if the RPC call fails (non-fatal).
16106
+ */
16107
+ async fetchEvmLatestNonce(chain) {
16108
+ return this.fetchEvmNonce(chain, "latest");
16109
+ }
16110
+ async fetchEvmNonce(chain, blockTag) {
15934
16111
  if (!isEvmChain(chain)) return null;
15935
16112
  const rpcUrl = getEvmRpcUrl(chain);
15936
16113
  try {
@@ -15941,7 +16118,7 @@ var AgentExecutor = class {
15941
16118
  body: JSON.stringify({
15942
16119
  jsonrpc: "2.0",
15943
16120
  method: "eth_getTransactionCount",
15944
- params: [address, "pending"],
16121
+ params: [address, blockTag],
15945
16122
  id: 1
15946
16123
  }),
15947
16124
  signal: AbortSignal.timeout(5e3)
@@ -16041,11 +16218,11 @@ var AgentExecutor = class {
16041
16218
  `);
16042
16219
  const chainName = params.chain;
16043
16220
  const chainId = domain.chainId;
16044
- let chain = Chain11.Ethereum;
16221
+ let chain = Chain12.Ethereum;
16045
16222
  if (chainName) {
16046
- chain = resolveChain(chainName) || Chain11.Ethereum;
16223
+ chain = resolveChain(chainName) || Chain12.Ethereum;
16047
16224
  } else if (chainId) {
16048
- chain = resolveChainId(chainId) || Chain11.Ethereum;
16225
+ chain = resolveChainId(chainId) || Chain12.Ethereum;
16049
16226
  }
16050
16227
  const sigResult = await this.vault.signBytes({
16051
16228
  data: eip712Hash,
@@ -17379,7 +17556,9 @@ var PipeInterface = class {
17379
17556
  session;
17380
17557
  rl = null;
17381
17558
  stopped = false;
17559
+ inputClosed = false;
17382
17560
  pendingPasswordResolve = null;
17561
+ pendingPasswordReject = null;
17383
17562
  pendingConfirmResolve = null;
17384
17563
  constructor(session) {
17385
17564
  this.session = session;
@@ -17411,38 +17590,45 @@ var PipeInterface = class {
17411
17590
  }))
17412
17591
  });
17413
17592
  }
17414
- const lines = [];
17593
+ const commands = [];
17415
17594
  let inputDone = false;
17416
17595
  let processing = false;
17417
17596
  this.rl.on("line", async (line) => {
17418
17597
  const trimmed = line.trim();
17419
17598
  if (!trimmed) return;
17420
- lines.push(trimmed);
17599
+ let cmd;
17600
+ try {
17601
+ cmd = JSON.parse(trimmed);
17602
+ } catch {
17603
+ this.emitInvalidInput("Malformed JSON input");
17604
+ return;
17605
+ }
17606
+ if (cmd?.type === "confirm" || cmd?.type === "password") {
17607
+ this.handleControlCommand(cmd);
17608
+ return;
17609
+ }
17610
+ commands.push(cmd);
17421
17611
  if (!processing) {
17422
17612
  processing = true;
17423
- while (lines.length > 0) {
17424
- const nextLine = lines.shift();
17613
+ while (commands.length > 0) {
17614
+ const nextCommand = commands.shift();
17425
17615
  try {
17426
- const cmd = JSON.parse(nextLine);
17427
- await this.handleCommand(cmd);
17616
+ await this.handleCommand(nextCommand);
17428
17617
  } catch (err) {
17429
- const { message, code } = normalizeAgentError(err);
17430
- this.emit({
17431
- type: "error",
17432
- message: `Invalid input: ${message}`,
17433
- code: code === "UNKNOWN_ERROR" /* UNKNOWN_ERROR */ ? "INVALID_INPUT" /* INVALID_INPUT */ : code
17434
- });
17618
+ this.emitInvalidInput(err);
17435
17619
  }
17436
17620
  }
17437
17621
  processing = false;
17438
- if (inputDone && lines.length === 0) {
17622
+ if (inputDone && commands.length === 0) {
17439
17623
  this.stop();
17440
17624
  }
17441
17625
  }
17442
17626
  });
17443
17627
  this.rl.on("close", () => {
17628
+ this.inputClosed = true;
17444
17629
  inputDone = true;
17445
- if (!processing && lines.length === 0) {
17630
+ this.settlePendingPrompts("Password input closed before a reply was received");
17631
+ if (!processing && commands.length === 0) {
17446
17632
  this.stop();
17447
17633
  }
17448
17634
  });
@@ -17522,17 +17708,30 @@ var PipeInterface = class {
17522
17708
  onDone: () => {
17523
17709
  this.emit({ type: "done" });
17524
17710
  },
17525
- requestPassword: async () => {
17526
- return new Promise((resolve) => {
17711
+ requestPassword: () => {
17712
+ if (this.inputClosed) {
17713
+ const password2 = Promise.reject(
17714
+ Object.assign(new Error("Password input closed before a reply was received"), {
17715
+ code: "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */
17716
+ })
17717
+ );
17718
+ void password2.catch(() => void 0);
17719
+ return password2;
17720
+ }
17721
+ const password = new Promise((resolve, reject) => {
17527
17722
  this.pendingPasswordResolve = resolve;
17723
+ this.pendingPasswordReject = reject;
17528
17724
  this.emit({
17529
17725
  type: "error",
17530
17726
  message: "PASSWORD_REQUIRED",
17531
17727
  code: "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */
17532
17728
  });
17533
17729
  });
17730
+ void password.catch(() => void 0);
17731
+ return password;
17534
17732
  },
17535
17733
  requestConfirmation: async (message) => {
17734
+ if (this.inputClosed) return false;
17536
17735
  return new Promise((resolve) => {
17537
17736
  this.pendingConfirmResolve = resolve;
17538
17737
  this.emit({
@@ -17554,21 +17753,14 @@ var PipeInterface = class {
17554
17753
  const { message, code } = normalizeAgentError(err);
17555
17754
  this.emit({ type: "error", message, code });
17556
17755
  this.emit({ type: "done" });
17756
+ } finally {
17757
+ this.settlePendingPrompts("Password request cancelled because the turn ended");
17557
17758
  }
17558
17759
  break;
17559
17760
  }
17560
- case "password": {
17561
- if (this.pendingPasswordResolve) {
17562
- this.pendingPasswordResolve(cmd.password);
17563
- this.pendingPasswordResolve = null;
17564
- }
17565
- break;
17566
- }
17761
+ case "password":
17567
17762
  case "confirm": {
17568
- if (this.pendingConfirmResolve) {
17569
- this.pendingConfirmResolve(cmd.confirmed);
17570
- this.pendingConfirmResolve = null;
17571
- }
17763
+ this.handleControlCommand(cmd);
17572
17764
  break;
17573
17765
  }
17574
17766
  default:
@@ -17579,6 +17771,54 @@ var PipeInterface = class {
17579
17771
  });
17580
17772
  }
17581
17773
  }
17774
+ handleControlCommand(cmd) {
17775
+ if (cmd.type === "password") {
17776
+ if (!this.pendingPasswordResolve) {
17777
+ this.emitInvalidInput("No pending password request for this reply");
17778
+ return;
17779
+ }
17780
+ if (typeof cmd.password !== "string") {
17781
+ this.emitInvalidInput("Password reply must contain a string password");
17782
+ return;
17783
+ }
17784
+ const resolve2 = this.pendingPasswordResolve;
17785
+ this.pendingPasswordResolve = null;
17786
+ this.pendingPasswordReject = null;
17787
+ resolve2(cmd.password);
17788
+ return;
17789
+ }
17790
+ if (!this.pendingConfirmResolve) {
17791
+ this.emitInvalidInput("No pending confirmation for this reply");
17792
+ return;
17793
+ }
17794
+ if (cmd.confirmed !== true && cmd.confirmed !== false) {
17795
+ this.emitInvalidInput("Confirmation reply must contain a boolean confirmed value");
17796
+ return;
17797
+ }
17798
+ const resolve = this.pendingConfirmResolve;
17799
+ this.pendingConfirmResolve = null;
17800
+ resolve(cmd.confirmed === true);
17801
+ }
17802
+ settlePendingPrompts(passwordMessage) {
17803
+ const confirmResolve = this.pendingConfirmResolve;
17804
+ this.pendingConfirmResolve = null;
17805
+ confirmResolve?.(false);
17806
+ const passwordReject = this.pendingPasswordReject;
17807
+ this.pendingPasswordResolve = null;
17808
+ this.pendingPasswordReject = null;
17809
+ if (passwordReject) {
17810
+ const error2 = Object.assign(new Error(passwordMessage), { code: "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */ });
17811
+ passwordReject(error2);
17812
+ }
17813
+ }
17814
+ emitInvalidInput(err) {
17815
+ const { message, code } = normalizeAgentError(err);
17816
+ this.emit({
17817
+ type: "error",
17818
+ message: `Invalid input: ${message}`,
17819
+ code: code === "UNKNOWN_ERROR" /* UNKNOWN_ERROR */ ? "INVALID_INPUT" /* INVALID_INPUT */ : code
17820
+ });
17821
+ }
17582
17822
  emit(event) {
17583
17823
  process.stdout.write(JSON.stringify(event) + "\n");
17584
17824
  }
@@ -18492,6 +18732,11 @@ var AgentSession = class {
18492
18732
  recordResolution(txHash, "failed");
18493
18733
  return;
18494
18734
  }
18735
+ if (result.status === "expired") {
18736
+ ui.onTxStatus(txHash, chainName ?? "", "failed", explorerUrl);
18737
+ recordResolution(txHash, "failed");
18738
+ return;
18739
+ }
18495
18740
  } catch (err) {
18496
18741
  if (this.config.verbose) {
18497
18742
  process.stderr.write(`[session] tx confirm poll ${attempt + 1} failed: ${err?.message ?? err}
@@ -19628,7 +19873,7 @@ var cachedVersion = null;
19628
19873
  function getVersion() {
19629
19874
  if (cachedVersion) return cachedVersion;
19630
19875
  if (true) {
19631
- cachedVersion = "6.3.0";
19876
+ cachedVersion = "7.2.0";
19632
19877
  return cachedVersion;
19633
19878
  }
19634
19879
  try {
@@ -20125,7 +20370,7 @@ var EventBuffer = class {
20125
20370
  };
20126
20371
 
20127
20372
  // src/interactive/session.ts
20128
- import { Chain as Chain13, fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
20373
+ import { Chain as Chain14, fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
20129
20374
  import chalk14 from "chalk";
20130
20375
  import ora3 from "ora";
20131
20376
  import * as readline3 from "readline";
@@ -20976,7 +21221,7 @@ Error: ${error2.message}`));
20976
21221
  } else if (rest[i] === "--destination-tag") {
20977
21222
  const tag = rest[i + 1];
20978
21223
  const parsedTag = Number(tag);
20979
- if (chain !== Chain13.Ripple || !/^(0|[1-9]\d*)$/.test(tag ?? "") || !Number.isSafeInteger(parsedTag) || parsedTag > 4294967295) {
21224
+ if (chain !== Chain14.Ripple || !/^(0|[1-9]\d*)$/.test(tag ?? "") || !Number.isSafeInteger(parsedTag) || parsedTag > 4294967295) {
20980
21225
  throw new Error("Invalid XRP DestinationTag: expected an integer from 0 to 4294967295");
20981
21226
  }
20982
21227
  destinationTag = parsedTag;
@@ -21824,7 +22069,7 @@ See also: balance, tx-status`
21824
22069
  if (!amount && !options.max) throw new Error("Provide an amount or use --max");
21825
22070
  if (amount && options.max) throw new Error("Cannot specify both amount and --max");
21826
22071
  const chain = resolveChainOrThrow(chainStr);
21827
- if (options.destinationTag !== void 0 && chain !== Chain14.Ripple) {
22072
+ if (options.destinationTag !== void 0 && chain !== Chain15.Ripple) {
21828
22073
  throw new Error("--destination-tag is only supported for XRP");
21829
22074
  }
21830
22075
  const destinationTag = options.destinationTag === void 0 ? void 0 : Number(options.destinationTag);
@@ -21952,7 +22197,7 @@ program2.command("broadcast").description("Broadcast a pre-signed raw transactio
21952
22197
  program2.command("tx-status").description("Check transaction status (polls until terminal or timeout)").requiredOption("--chain <chain>", "Target blockchain").requiredOption("--tx-hash <hash>", "Transaction hash to check").option("--no-wait", "Return immediately without waiting for confirmation").option("--timeout <seconds>", "Max seconds to poll before giving up (default 120)").addHelpText(
21953
22198
  "after",
21954
22199
  `
21955
- Statuses: pending, not_found, confirmed, failed
22200
+ Statuses: pending, not_found, expired, confirmed, failed
21956
22201
  Malformed hashes fail with INVALID_HASH (exit 4).
21957
22202
 
21958
22203
  Examples:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vultisig/cli",
3
- "version": "6.3.0",
3
+ "version": "7.2.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
  "@msgpack/msgpack": "^3.1.2",
74
74
  "@napi-rs/keyring": "^1.3.0",
75
75
  "@noble/hashes": "^2.2.0",
76
- "@vultisig/client-shared": "^0.3.5",
77
- "@vultisig/core-chain": "^4.1.1",
78
- "@vultisig/rujira": "^70.0.0",
79
- "@vultisig/sdk": "^6.3.0",
76
+ "@vultisig/client-shared": "^0.3.6",
77
+ "@vultisig/core-chain": "^5.2.0",
78
+ "@vultisig/rujira": "^71.0.0",
79
+ "@vultisig/sdk": "^7.2.0",
80
80
  "chalk": "^5.6.2",
81
81
  "cli-table3": "^0.6.5",
82
82
  "commander": "^15.0.0",
@@ -94,7 +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": "^2.1.1",
97
+ "@vultisig/core-mpc": "^3.1.0",
98
98
  "esbuild": "^0.28.1",
99
99
  "ethers": "^6.17.0",
100
100
  "node-pty": "^1.1.0",