@vultisig/cli 6.2.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/index.js +502 -109
  3. package/package.json +6 -6
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @vultisig/cli
2
2
 
3
+ ## 7.0.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#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.
8
+
9
+ - [#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.
10
+
11
+ - [#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.
12
+
13
+ - [#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.
14
+
15
+ - [#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.
16
+
17
+ - 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)]:
18
+ - @vultisig/sdk@7.0.0
19
+ - @vultisig/core-chain@5.0.0
20
+ - @vultisig/rujira@71.0.0
21
+ - @vultisig/client-shared@0.3.6
22
+
23
+ ## 6.3.0
24
+
25
+ ### Patch Changes
26
+
27
+ - [#2202](https://github.com/vultisig/vultisig-sdk/pull/2202) [`2d90f80`](https://github.com/vultisig/vultisig-sdk/commit/2d90f803055e7c1325ab1dc1966e17431740cb1f) Thanks [@neavra](https://github.com/neavra)! - Derive swap sell-token contracts from signed approval payloads and mark unresolved contract disclosures explicitly.
28
+
29
+ - Updated dependencies [[`c0eafeb`](https://github.com/vultisig/vultisig-sdk/commit/c0eafeb12df42cffa70ef6951c081a74383e5fa2), [`b2f63b7`](https://github.com/vultisig/vultisig-sdk/commit/b2f63b70c4dac0001f0f40521efa4412f1d9e1cb), [`536c320`](https://github.com/vultisig/vultisig-sdk/commit/536c32028bf1f66897bf018c19f36c298545841a), [`78be14e`](https://github.com/vultisig/vultisig-sdk/commit/78be14ea9f1f011ded932ce6a9be5c19cf5694d0), [`76be65f`](https://github.com/vultisig/vultisig-sdk/commit/76be65fc297a43de6313b9dce3b096e6f764831a), [`e23780f`](https://github.com/vultisig/vultisig-sdk/commit/e23780f0b292cd76e80c72c5ccdcdafebd73a735), [`30499f8`](https://github.com/vultisig/vultisig-sdk/commit/30499f83f9909d8eb9bc1d310b25fdadb5c240e7), [`534154b`](https://github.com/vultisig/vultisig-sdk/commit/534154b6e9daaffad5d2e4958af3ead5fa940f21), [`3d40017`](https://github.com/vultisig/vultisig-sdk/commit/3d400171facf1107472e827c4ef48d7dd99843f1), [`c0c08f0`](https://github.com/vultisig/vultisig-sdk/commit/c0c08f0bd2337147d6b92ca7f391115edd7db62b), [`2869ae6`](https://github.com/vultisig/vultisig-sdk/commit/2869ae60bc157e2020a541e31a50afabef4e9edc), [`ef252e4`](https://github.com/vultisig/vultisig-sdk/commit/ef252e40b590a6cad9f303f7aba743c617442f90), [`4e23e92`](https://github.com/vultisig/vultisig-sdk/commit/4e23e9205310e46564bc1782b57499b2a39d7a5f), [`4561c65`](https://github.com/vultisig/vultisig-sdk/commit/4561c6584f62fa78d17546bcc119e31b6ea665cb), [`fc876a7`](https://github.com/vultisig/vultisig-sdk/commit/fc876a7fb561041539cb257a814d078304de6857), [`d08babd`](https://github.com/vultisig/vultisig-sdk/commit/d08babd96c36b193f8788605ed59871da1483ff3), [`4292e31`](https://github.com/vultisig/vultisig-sdk/commit/4292e3127e56bc2c92f2ac7b0a2886fcb0445043), [`d267ff6`](https://github.com/vultisig/vultisig-sdk/commit/d267ff66b9cf6d0b5898d40ec1f37cda6decb707), [`5f7dd00`](https://github.com/vultisig/vultisig-sdk/commit/5f7dd007cb5026d7c3ebe414fa5952f3419df570), [`bfe0292`](https://github.com/vultisig/vultisig-sdk/commit/bfe0292e3876fb7acc7044bac1b544c27aba8919), [`acd0b2e`](https://github.com/vultisig/vultisig-sdk/commit/acd0b2eaa882669a399fb378e51244ed4199ee47), [`f86320b`](https://github.com/vultisig/vultisig-sdk/commit/f86320bcfbee02b2f27473ac41a042db307596f6)]:
30
+ - @vultisig/core-chain@4.1.1
31
+ - @vultisig/sdk@6.3.0
32
+
3
33
  ## 6.2.0
4
34
 
5
35
  ### Minor 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
 
@@ -10610,32 +10610,6 @@ Use --add <contractAddress> to add or --remove <tokenId> to remove`));
10610
10610
  }
10611
10611
  }
10612
10612
 
10613
- // ../../packages/core/chain/dist/chains/ripple/address.js
10614
- import { classicAddressToXAddress, isValidXAddress, xAddressToClassicAddress } from "ripple-address-codec";
10615
- var assertSupportedTag = (tag) => {
10616
- if (tag === false)
10617
- return void 0;
10618
- return tag;
10619
- };
10620
- var decodeRippleXAddress = (address) => {
10621
- const value = address.trim();
10622
- if (!isValidXAddress(value))
10623
- throw new Error("Invalid XRP X-address");
10624
- const decoded = xAddressToClassicAddress(value);
10625
- if (decoded.test)
10626
- throw new Error("XRP testnet X-addresses are not supported");
10627
- return {
10628
- address: decoded.classicAddress,
10629
- destinationTag: assertSupportedTag(decoded.tag)
10630
- };
10631
- };
10632
- var normalizeRippleDestination = (address) => {
10633
- const value = address.trim();
10634
- if (value.startsWith("X") || value.startsWith("T"))
10635
- return decodeRippleXAddress(value);
10636
- return { address: value };
10637
- };
10638
-
10639
10613
  // ../../packages/core/mpc/dist/keysign/error.js
10640
10614
  var BuildKeysignPayloadError = class extends Error {
10641
10615
  type;
@@ -10667,7 +10641,7 @@ var resolveDestinationTag = ({ destinationTag, memo }) => {
10667
10641
  };
10668
10642
 
10669
10643
  // src/commands/transaction.ts
10670
- import { Chain as Chain3, Vultisig as Vultisig2 } from "@vultisig/sdk";
10644
+ import { Chain as Chain3, normalizeRippleDestination, Vultisig as Vultisig2 } from "@vultisig/sdk";
10671
10645
  var getSendPreviewDetails = (chain, keysignPayload) => {
10672
10646
  let memo = keysignPayload.memo || void 0;
10673
10647
  if (chain !== Chain3.Ripple) {
@@ -11513,7 +11487,7 @@ function resolveTxStatusParams(params) {
11513
11487
  }
11514
11488
  var POLL_INTERVAL_MS = 5e3;
11515
11489
  var DEFAULT_TIMEOUT_SEC = 120;
11516
- var isTerminal = (status) => status === "success" || status === "error";
11490
+ var isTerminal = (status) => status === "success" || status === "error" || status === "expired";
11517
11491
  function resolveTimeoutMs(timeoutSec) {
11518
11492
  if (typeof timeoutSec !== "number" || !Number.isFinite(timeoutSec)) {
11519
11493
  return DEFAULT_TIMEOUT_SEC * 1e3;
@@ -11553,7 +11527,7 @@ async function executeTxStatus(ctx2, params, opts = {}) {
11553
11527
  }
11554
11528
  if (result.status === "success") {
11555
11529
  recordResolution(params.txHash, "confirmed");
11556
- } else if (result.status === "error") {
11530
+ } else if (result.status === "error" || result.status === "expired") {
11557
11531
  recordResolution(params.txHash, "failed");
11558
11532
  }
11559
11533
  spinner.succeed(`Transaction status: ${toCliStatus(result.status)}`);
@@ -14072,9 +14046,104 @@ import { randomUUID } from "node:crypto";
14072
14046
  // src/agent/toolOutputSigning.ts
14073
14047
  import { getChainKind as getChainKind5 } from "@vultisig/sdk";
14074
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
+
14075
14143
  // src/agent/executor.ts
14076
14144
  import {
14077
- Chain as Chain11,
14145
+ Chain as Chain12,
14146
+ clampEvmPriorityFee,
14078
14147
  computeEip712Hash,
14079
14148
  getChainKind as getChainKind4,
14080
14149
  getEvmRpcUrl,
@@ -14246,7 +14315,7 @@ function sleep2(ms) {
14246
14315
 
14247
14316
  // src/agent/hlOrder.ts
14248
14317
  var import_msgpack = __toESM(require_dist(), 1);
14249
- import { Chain as Chain10 } from "@vultisig/sdk";
14318
+ import { Chain as Chain11 } from "@vultisig/sdk";
14250
14319
  var ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
14251
14320
  var HL_AGENT_TYPES = {
14252
14321
  Agent: [
@@ -14397,7 +14466,7 @@ async function validateRequestBinding(payload, expected, vault, now) {
14397
14466
  if (typeof payload.owner_public_key !== "string") throw new Error("HL_INVALID_OWNER_PUBLIC_KEY");
14398
14467
  if (payload.owner_public_key.toLowerCase() !== expected.publicKey.toLowerCase()) throw new Error("HL_OWNER_MISMATCH");
14399
14468
  if (typeof payload.vault_address !== "string") throw new Error("HL_INVALID_VAULT_ADDRESS");
14400
- const localAddress = await vault.address(Chain10.Ethereum);
14469
+ const localAddress = await vault.address(Chain11.Ethereum);
14401
14470
  if (payload.vault_address.toLowerCase() !== localAddress.toLowerCase()) throw new Error("HL_VAULT_ADDRESS_MISMATCH");
14402
14471
  if (typeof payload.expires_at !== "string") throw new Error("HL_INVALID_EXPIRY");
14403
14472
  const expiry = Date.parse(payload.expires_at);
@@ -14490,16 +14559,185 @@ var EVM_CHAINS = /* @__PURE__ */ new Set([
14490
14559
  ]);
14491
14560
  var ERC20_TRANSFER_SELECTOR = "0xa9059cbb";
14492
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
+ };
14493
14567
  function decodeErc20Transfer(calldata) {
14494
14568
  if (calldata.slice(0, ERC20_TRANSFER_SELECTOR.length).toLowerCase() !== ERC20_TRANSFER_SELECTOR) return null;
14495
14569
  try {
14496
- const decoded = decodeFunctionData({ abi: ERC20_TRANSFER_ABI, data: calldata });
14570
+ const decoded = decodeFunctionData({
14571
+ abi: ERC20_TRANSFER_ABI,
14572
+ data: calldata
14573
+ });
14497
14574
  const [recipient, amount] = decoded.args;
14498
14575
  return { recipient, amount };
14499
14576
  } catch {
14500
14577
  throw new Error("Invalid ERC-20 transfer calldata \u2014 refusing to sign");
14501
14578
  }
14502
14579
  }
14580
+ var tokenLabel = (value) => typeof value === "string" ? value : "";
14581
+ var MAX_TOKEN_SYMBOL_LENGTH = 32;
14582
+ var MAX_TOKEN_LABEL_LENGTH = 512;
14583
+ function safeTokenSymbol(symbol) {
14584
+ const defaultDisplaySymbol = symbol.match(/^[\p{L}\p{N}][\p{L}\p{N}._+-]{0,31}/u)?.[0] ?? "token";
14585
+ if (symbol.length > MAX_TOKEN_SYMBOL_LENGTH) {
14586
+ return { displaySymbol: defaultDisplaySymbol, suspicious: true };
14587
+ }
14588
+ const suspiciousAt = symbol.search(/0x[0-9a-f]{40}|\([^)]*\bon\b[^)]*\)/iu);
14589
+ const candidate = (suspiciousAt < 0 ? symbol : symbol.slice(0, suspiciousAt)).trim();
14590
+ const displaySymbol = candidate.match(/^[\p{L}\p{N}][\p{L}\p{N}._+-]*/u)?.[0] ?? "token";
14591
+ if (suspiciousAt < 0 && candidate === displaySymbol) {
14592
+ return { displaySymbol, suspicious: false };
14593
+ }
14594
+ return { displaySymbol, suspicious: true };
14595
+ }
14596
+ function unresolvedTokenIdentity(label) {
14597
+ if (typeof label !== "string" || label.length > MAX_TOKEN_LABEL_LENGTH) {
14598
+ return { rawSymbol: "", displaySymbol: "", kind: "unresolved" };
14599
+ }
14600
+ const suffixAt = label.lastIndexOf(" (");
14601
+ const rawSymbol = (suffixAt > 0 ? label.slice(0, suffixAt) : label).trim();
14602
+ return {
14603
+ rawSymbol,
14604
+ displaySymbol: safeTokenSymbol(rawSymbol).displaySymbol,
14605
+ kind: "unresolved"
14606
+ };
14607
+ }
14608
+ function untrustedSymbolIdentity(symbol) {
14609
+ const rawSymbol = tokenLabel(symbol);
14610
+ return {
14611
+ rawSymbol,
14612
+ displaySymbol: safeTokenSymbol(rawSymbol).displaySymbol,
14613
+ kind: "unresolved"
14614
+ };
14615
+ }
14616
+ function resolvedTokenIdentity(label, expectedChain) {
14617
+ const unresolved = unresolvedTokenIdentity(label);
14618
+ if (typeof label !== "string" || label.length > MAX_TOKEN_LABEL_LENGTH) return unresolved;
14619
+ const match = label.match(/^([\s\S]*) \(([\s\S]*?) on ([^,]+), \d+ dec, source: [^)]+\)$/u);
14620
+ if (!match) return unresolved;
14621
+ const [, rawSymbol, rawAssetId, descriptorChain] = match;
14622
+ const { displaySymbol, suspicious } = safeTokenSymbol(rawSymbol);
14623
+ const parsedChain = resolveChainReference(descriptorChain);
14624
+ const routedChain = typeof expectedChain === "string" || typeof expectedChain === "number" ? resolveChainReference(expectedChain) : void 0;
14625
+ if (suspicious || !parsedChain || !routedChain || parsedChain !== routedChain) {
14626
+ return { rawSymbol, displaySymbol, kind: "unresolved" };
14627
+ }
14628
+ const assetId = rawAssetId.trim();
14629
+ if (!assetId) return { rawSymbol, displaySymbol, kind: "unresolved" };
14630
+ if (assetId.toLowerCase() === "native") return { rawSymbol, displaySymbol, kind: "native" };
14631
+ if (getChainKind4(routedChain) === "evm") {
14632
+ if (!/^0x[0-9a-f]{40}$/iu.test(assetId)) return { rawSymbol, displaySymbol, kind: "unresolved" };
14633
+ } else if (!/^[\p{L}\p{N}:._/-]{1,128}$/u.test(assetId)) {
14634
+ return { rawSymbol, displaySymbol, kind: "unresolved" };
14635
+ }
14636
+ return { rawSymbol, displaySymbol, kind: "token", contract: assetId };
14637
+ }
14638
+ function replaceUnsafeSymbol(text, identity) {
14639
+ if (!identity.rawSymbol || identity.rawSymbol === identity.displaySymbol) return text;
14640
+ return text.split(identity.rawSymbol).join(identity.displaySymbol);
14641
+ }
14642
+ function anchorEnd(text, label) {
14643
+ if (!label) return -1;
14644
+ const tokenChar = /[\p{L}\p{N}._+-]/u;
14645
+ const trimmedLength = text.trimEnd().length;
14646
+ const index = trimmedLength - label.length;
14647
+ if (index < 0 || !text.startsWith(label, index)) return -1;
14648
+ const before = text[index - 1];
14649
+ if (before && tokenChar.test(before)) return -1;
14650
+ return trimmedLength;
14651
+ }
14652
+ function hasAnchor(text, anchors) {
14653
+ return anchors.some((anchor) => anchorEnd(text, anchor) >= 0);
14654
+ }
14655
+ function hasSingleSwapDelimiter(summary, labels) {
14656
+ const identities = [
14657
+ unresolvedTokenIdentity(labels.from_token),
14658
+ untrustedSymbolIdentity(labels.from_token_symbol),
14659
+ unresolvedTokenIdentity(labels.to_token),
14660
+ untrustedSymbolIdentity(labels.to_token_symbol)
14661
+ ];
14662
+ const structuralSummary = identities.reduce((text, identity) => replaceUnsafeSymbol(text, identity), summary);
14663
+ const firstArrow = structuralSummary.indexOf("\u2192");
14664
+ return firstArrow >= 0 && structuralSummary.indexOf("\u2192", firstArrow + 1) < 0;
14665
+ }
14666
+ function splitSwapHead(head, sellAnchors, buyAnchors) {
14667
+ let best;
14668
+ let arrowAt = head.indexOf("\u2192");
14669
+ while (arrowAt >= 0) {
14670
+ const left = head.slice(0, arrowAt);
14671
+ const right = head.slice(arrowAt + 1);
14672
+ const viaAt = right.indexOf(" via ");
14673
+ const buy = viaAt >= 0 ? right.slice(0, viaAt) : right;
14674
+ const provider = viaAt >= 0 ? right.slice(viaAt) : "";
14675
+ const score = Number(hasAnchor(left, sellAnchors)) + Number(hasAnchor(buy, buyAnchors));
14676
+ if (!best || score > best.score) {
14677
+ best = { left, buy, provider, score };
14678
+ }
14679
+ arrowAt = head.indexOf("\u2192", arrowAt + 1);
14680
+ }
14681
+ return best;
14682
+ }
14683
+ function terminalAnchorSymbol(anchor) {
14684
+ return anchor.trim().match(/[\p{L}\p{N}][\p{L}\p{N}._+-]*$/u)?.[0] ?? "";
14685
+ }
14686
+ function discloseSwapSide(side, anchors, suffix, expectedSymbol) {
14687
+ if (!expectedSymbol) {
14688
+ return `${side.trimEnd()} (contract unavailable)${side.slice(side.trimEnd().length)}`;
14689
+ }
14690
+ for (const anchor of anchors) {
14691
+ const end = anchorEnd(side, anchor);
14692
+ if (end < 0) continue;
14693
+ const visibleSymbol = terminalAnchorSymbol(anchor);
14694
+ if (expectedSymbol && visibleSymbol && visibleSymbol !== expectedSymbol) break;
14695
+ if (!suffix) return side;
14696
+ return `${side.slice(0, end)}${suffix}${side.slice(end)}`;
14697
+ }
14698
+ return `${side.trimEnd()} (contract unavailable)${side.slice(side.trimEnd().length)}`;
14699
+ }
14700
+ function discloseSwapTokenContracts(head, labels, payload, sourceChain) {
14701
+ const destinationChain = Object.hasOwn(labels, "to_chain") ? labels.to_chain ?? null : typeof payload?.to_chain === "string" || typeof payload?.to_chain === "number" ? payload.to_chain : void 0;
14702
+ const fromToken = resolvedTokenIdentity(labels.from_token, sourceChain);
14703
+ const toToken = resolvedTokenIdentity(labels.to_token, destinationChain);
14704
+ const fromSymbol = untrustedSymbolIdentity(labels.from_token_symbol);
14705
+ const toSymbol = untrustedSymbolIdentity(labels.to_token_symbol);
14706
+ const approvalArgs = payload?.approvalTxArgs;
14707
+ const rawApprovalTarget = tokenLabel(
14708
+ approvalArgs && typeof approvalArgs === "object" ? approvalArgs.tx?.to : void 0
14709
+ );
14710
+ const approvalTarget = /^0x[0-9a-f]{40}$/iu.test(rawApprovalTarget) ? rawApprovalTarget : "";
14711
+ const symbolIdentities = [fromToken, fromSymbol, toToken, toSymbol];
14712
+ const sanitizeText = (value) => symbolIdentities.reduce((text, identity) => replaceUnsafeSymbol(text, identity), tokenLabel(value));
14713
+ const sanitizedHead = sanitizeText(head);
14714
+ const sellAnchors = [sanitizeText(labels.amount_in), fromSymbol.displaySymbol, fromToken.displaySymbol].filter(
14715
+ Boolean
14716
+ );
14717
+ const buyAnchors = [sanitizeText(labels.expected_output), toSymbol.displaySymbol, toToken.displaySymbol].filter(
14718
+ Boolean
14719
+ );
14720
+ const approvalMatchesResolvedSellToken = fromToken.kind === "token" && !!fromToken.contract && !!approvalTarget && fromToken.contract.toLowerCase() === approvalTarget.toLowerCase();
14721
+ const sellSuffix = payload?.approvalTxArgs ? approvalMatchesResolvedSellToken ? ` (${approvalTarget})` : " (contract unavailable)" : fromToken.kind === "native" ? "" : fromToken.kind === "token" && fromToken.contract ? ` (${fromToken.contract})` : " (contract unavailable)";
14722
+ const buySuffix = toToken.kind === "native" ? "" : toToken.kind === "token" && toToken.contract ? ` (${toToken.contract})` : " (contract unavailable)";
14723
+ const halves = splitSwapHead(sanitizedHead, sellAnchors, buyAnchors);
14724
+ if (!halves) return `${sanitizedHead} (contract unavailable)`;
14725
+ const sellResolved = fromToken.kind !== "unresolved";
14726
+ const buyResolved = toToken.kind !== "unresolved";
14727
+ const sell = discloseSwapSide(
14728
+ halves.left,
14729
+ sellAnchors,
14730
+ sellResolved ? sellSuffix : "",
14731
+ sellResolved ? fromToken.displaySymbol : void 0
14732
+ );
14733
+ const buy = discloseSwapSide(
14734
+ halves.buy,
14735
+ buyAnchors,
14736
+ buyResolved ? buySuffix : "",
14737
+ buyResolved ? toToken.displaySymbol : void 0
14738
+ );
14739
+ return `${sell}\u2192${buy}${halves.provider}`;
14740
+ }
14503
14741
  var isEvmChain = (chain) => EVM_CHAINS.has(chain);
14504
14742
  function stripEmbeddedPayloadContract(value, disclosedContract) {
14505
14743
  if (!disclosedContract) return value;
@@ -14571,12 +14809,12 @@ var AgentExecutor = class {
14571
14809
  if (this.vault.isEncrypted && !this.vault.isUnlocked?.() && this.password) {
14572
14810
  await this.vault.unlock?.(this.password);
14573
14811
  }
14574
- const expectedAddress = await this.vault.address(Chain11.Ethereum);
14812
+ const expectedAddress = await this.vault.address(Chain12.Ethereum);
14575
14813
  const signatures = [];
14576
14814
  for (const step of payload.steps) {
14577
14815
  const signed = await this.vault.signBytes({
14578
14816
  data: step.digest,
14579
- chain: Chain11.Ethereum
14817
+ chain: Chain12.Ethereum
14580
14818
  });
14581
14819
  const canonical = toCanonicalEvmSignature(signed.signature, signed.recovery ?? 0);
14582
14820
  const v = canonical.recovery + 27;
@@ -14751,7 +14989,7 @@ var AgentExecutor = class {
14751
14989
  if (!nestedTx && txReadyData && typeof txReadyData === "object") {
14752
14990
  const txArgs = txReadyData.txArgs;
14753
14991
  if (txArgs && typeof txArgs === "object" && typeof txArgs.to === "string" && typeof txArgs.amount === "string") {
14754
- const chain2 = resolveChainFromTxReady(txReadyData) || Chain11.Ethereum;
14992
+ const chain2 = resolveChainFromTxReady(txReadyData) || Chain12.Ethereum;
14755
14993
  if (getChainKind4(chain2) !== "evm") {
14756
14994
  this.pendingPayloads.clear();
14757
14995
  this.pendingLegs = [];
@@ -14776,7 +15014,7 @@ var AgentExecutor = class {
14776
15014
  `);
14777
15015
  return false;
14778
15016
  }
14779
- const chain = resolveChainFromTxReady(txReadyData) || Chain11.Ethereum;
15017
+ const chain = resolveChainFromTxReady(txReadyData) || Chain12.Ethereum;
14780
15018
  this.pendingPayloads.clear();
14781
15019
  this.pendingLegs = [];
14782
15020
  this.pendingPayloads.set("latest", {
@@ -14885,12 +15123,22 @@ var AgentExecutor = class {
14885
15123
  }
14886
15124
  const isSwap = !!(p?.approvalTxArgs || p?.swap_tx || labels.quote_summary || labels.to_token_symbol);
14887
15125
  if (isSwap) {
14888
- const usedQuoteSummary = !!labels.quote_summary;
14889
- const head = labels.quote_summary || `swap ${labels.amount_in ?? p?.txArgs?.amount ?? "?"} ${labels.from_token_symbol ?? ""} \u2192 ${labels.to_token_symbol ?? ""}`.trim();
15126
+ const rawQuoteSummary = tokenLabel(labels.quote_summary);
15127
+ const quoteSummary = hasSingleSwapDelimiter(rawQuoteSummary, labels) ? rawQuoteSummary : "";
15128
+ const usedQuoteSummary = !!quoteSummary;
15129
+ const amountIn = tokenLabel(labels.amount_in) || tokenLabel(p?.txArgs?.amount) || "?";
15130
+ const fromSymbol = tokenLabel(labels.from_token_symbol);
15131
+ const sellHead = fromSymbol && !amountIn.endsWith(` ${fromSymbol}`) ? `${amountIn} ${fromSymbol}` : amountIn;
15132
+ const head = discloseSwapTokenContracts(
15133
+ quoteSummary || `swap ${sellHead} \u2192 ${tokenLabel(labels.to_token_symbol) || "?"}`,
15134
+ labels,
15135
+ p,
15136
+ stored.chain
15137
+ );
14890
15138
  const parts = [head, `on ${stored.chain}`];
14891
- if (!usedQuoteSummary && labels.provider) parts.push(`via ${labels.provider}`);
15139
+ if (!usedQuoteSummary && tokenLabel(labels.provider)) parts.push(`via ${tokenLabel(labels.provider)}`);
14892
15140
  if (p?.__multiLeg) parts.push("(+ token approval \u2014 2 transactions)");
14893
- if (labels.estimated_fee) parts.push(`est. fee ${labels.estimated_fee}`);
15141
+ if (tokenLabel(labels.estimated_fee)) parts.push(`est. fee ${tokenLabel(labels.estimated_fee)}`);
14894
15142
  return parts.join(" ");
14895
15143
  }
14896
15144
  const signedTx = extractNestedTx(p);
@@ -14914,18 +15162,18 @@ var AgentExecutor = class {
14914
15162
  */
14915
15163
  renderLabelSendSummary(p, labels, chain, to, disclosedContract) {
14916
15164
  const amount = stripEmbeddedPayloadContract(labels.resolved_amount ?? p?.txArgs?.amount ?? "?", disclosedContract);
14917
- const tokenLabel = stripEmbeddedPayloadContract(
15165
+ const tokenLabel2 = stripEmbeddedPayloadContract(
14918
15166
  (labels.token_resolved || labels.token_symbol || "").trim(),
14919
15167
  disclosedContract
14920
15168
  );
14921
- const symbol = tokenLabel.split(/\s+/, 1)[0];
15169
+ const symbol = tokenLabel2.split(/\s+/, 1)[0];
14922
15170
  const escapedChain = String(chain).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14923
15171
  const routedChainPattern = new RegExp(`(?:^|\\s)(?:not\\s+)?on ${escapedChain}(?=\\s|$)`);
14924
15172
  const positiveRoutedChainPattern = new RegExp(`(?:^|\\s)(?<!\\bnot\\s+)on ${escapedChain}(?=\\s|$)`);
14925
- const tokenContext = tokenLabel.slice(symbol.length);
15173
+ const tokenContext = tokenLabel2.slice(symbol.length);
14926
15174
  const labelCarriesRoutedChain = positiveRoutedChainPattern.test(tokenContext);
14927
15175
  const tokenDetail = tokenContext.replace(routedChainPattern, "").trim();
14928
- const amountEmbedsTokenLabel = tokenLabel.length > 0 && amount.endsWith(` ${tokenLabel}`);
15176
+ const amountEmbedsTokenLabel = tokenLabel2.length > 0 && amount.endsWith(` ${tokenLabel2}`);
14929
15177
  const amountWithSymbol = symbol && !amount.endsWith(` ${symbol}`) && !amountEmbedsTokenLabel ? `${amount} ${symbol}` : amount;
14930
15178
  const tokenDetailSuffix = amountEmbedsTokenLabel || !tokenDetail ? "" : ` ${tokenDetail}`;
14931
15179
  const location = amountEmbedsTokenLabel && labelCarriesRoutedChain ? "" : `on ${chain}${tokenDetailSuffix}`;
@@ -15424,8 +15672,9 @@ var AgentExecutor = class {
15424
15672
  nonce: swapTx.nonce
15425
15673
  }
15426
15674
  });
15427
- await this.patchEvmNonce(chain, keysignPayload);
15675
+ const nonceGraceAge = Date.now() - (this.evmLastBroadcast.get(chain.toString()) ?? 0);
15428
15676
  await this.patchEvmGas(chain, keysignPayload);
15677
+ await this.patchEvmNonce(chain, keysignPayload, nonceGraceAge);
15429
15678
  const messageHashes = await this.vault.extractMessageHashes(keysignPayload);
15430
15679
  const signature = await this.vault.sign(
15431
15680
  {
@@ -15677,33 +15926,45 @@ var AgentExecutor = class {
15677
15926
  }
15678
15927
  }
15679
15928
  /**
15680
- * Patch the EVM nonce in a keysign payload if our local state is ahead of on-chain.
15681
- * The payload's blockchainSpecific.ethereumSpecific.nonce was set from RPC during
15682
- * 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.
15683
15932
  *
15684
15933
  * Also detects evicted txs: if local state claims a higher nonce but there are
15685
15934
  * no pending txs in the mempool (pending == latest), the intermediate txs were
15686
15935
  * dropped and local state is stale.
15687
15936
  */
15688
- async patchEvmNonce(chain, payload) {
15689
- if (!this.stateStore || !isEvmChain(chain)) return;
15937
+ async patchEvmNonce(chain, payload, capturedBroadcastAge) {
15938
+ if (!isEvmChain(chain)) return;
15690
15939
  const bs = payload.blockchainSpecific;
15691
15940
  if (!bs || bs.case !== "ethereumSpecific") return;
15692
- 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;
15693
15956
  const nextNonce = this.stateStore.getNextEvmNonce(chain, rpcNonce);
15694
15957
  if (nextNonce !== rpcNonce) {
15695
- const lastBroadcast = this.evmLastBroadcast.get(chain.toString()) ?? 0;
15696
- if (Date.now() - lastBroadcast < 3e4) {
15958
+ if (hasRecentBroadcast) {
15697
15959
  if (this.verbose)
15698
15960
  process.stderr.write(
15699
- `[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)
15700
15962
  `
15701
15963
  );
15702
15964
  bs.value.nonce = nextNonce;
15703
15965
  return;
15704
15966
  }
15705
- const pendingNonce = await this.fetchEvmPendingNonce(chain);
15706
- if (pendingNonce !== null && pendingNonce === rpcNonce) {
15967
+ if (latestNonce !== null && pendingNonce === latestNonce) {
15707
15968
  if (this.verbose)
15708
15969
  process.stderr.write(
15709
15970
  `[nonce] Stale local state for ${chain}: local=${nextNonce}, on-chain=${rpcNonce}, no pending txs \u2014 using on-chain nonce
@@ -15713,7 +15974,8 @@ var AgentExecutor = class {
15713
15974
  return;
15714
15975
  }
15715
15976
  const nonceGap = nextNonce - rpcNonce;
15716
- if (pendingNonce === null && nonceGap > 3n) {
15977
+ const pendingStateVerified = pendingNonce !== null && latestNonce !== null && pendingNonce >= latestNonce;
15978
+ if (!pendingStateVerified && nonceGap > 3n) {
15717
15979
  process.stderr.write(
15718
15980
  `[nonce] Warning: pending nonce was not verified for ${chain}; signing will continue with on-chain nonce ${rpcNonce} because the local gap is ${nonceGap}
15719
15981
  `
@@ -15722,7 +15984,7 @@ var AgentExecutor = class {
15722
15984
  return;
15723
15985
  }
15724
15986
  bs.value.nonce = nextNonce;
15725
- if (pendingNonce === null) {
15987
+ if (!pendingStateVerified) {
15726
15988
  process.stderr.write(
15727
15989
  `[nonce] Warning: pending nonce was not verified for ${chain}; signing will continue with local nonce ${nextNonce}
15728
15990
  `
@@ -15741,7 +16003,51 @@ var AgentExecutor = class {
15741
16003
  if (!isEvmChain(chain)) return;
15742
16004
  const bs = payload.blockchainSpecific;
15743
16005
  if (!bs || bs.case !== "ethereumSpecific") return;
16006
+ const isEip1559 = evmChainTxFeeFormat[chain] === "enveloped";
16007
+ if (!isEip1559) return;
15744
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) {
15745
16051
  try {
15746
16052
  const res = await fetch(rpcUrl, {
15747
16053
  method: "POST",
@@ -15758,24 +16064,33 @@ var AgentExecutor = class {
15758
16064
  if (!res.ok || data?.error || data?.result?.baseFeePerGas === void 0 || data?.result?.baseFeePerGas === null) {
15759
16065
  throw new Error(`Failed to fetch current base fee for ${chain}`);
15760
16066
  }
15761
- const baseFee = BigInt(data.result.baseFeePerGas);
15762
- if (baseFee === 0n) return;
15763
- const currentPriorityFee = BigInt(bs.value.priorityFee || "0");
15764
- const currentMaxFee = BigInt(bs.value.maxFeePerGasWei || "0");
15765
- const minMaxFee = baseFee * 25n / 10n + currentPriorityFee;
15766
- if (currentMaxFee < minMaxFee) {
15767
- bs.value.maxFeePerGasWei = minMaxFee.toString();
15768
- if (this.verbose)
15769
- process.stderr.write(
15770
- `[gas] Bumped ${chain} maxFeePerGas: ${currentMaxFee} \u2192 ${minMaxFee} (baseFee=${baseFee})
15771
- `
15772
- );
15773
- }
16067
+ return BigInt(data.result.baseFeePerGas);
15774
16068
  } catch {
15775
16069
  process.stderr.write(
15776
16070
  `[gas] Warning: gas estimate was not refreshed for ${chain}; signing will continue with the original estimate
15777
16071
  `
15778
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;
15779
16094
  }
15780
16095
  }
15781
16096
  /**
@@ -15783,6 +16098,16 @@ var AgentExecutor = class {
15783
16098
  * Returns null if the RPC call fails (non-fatal).
15784
16099
  */
15785
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) {
15786
16111
  if (!isEvmChain(chain)) return null;
15787
16112
  const rpcUrl = getEvmRpcUrl(chain);
15788
16113
  try {
@@ -15793,7 +16118,7 @@ var AgentExecutor = class {
15793
16118
  body: JSON.stringify({
15794
16119
  jsonrpc: "2.0",
15795
16120
  method: "eth_getTransactionCount",
15796
- params: [address, "pending"],
16121
+ params: [address, blockTag],
15797
16122
  id: 1
15798
16123
  }),
15799
16124
  signal: AbortSignal.timeout(5e3)
@@ -15893,11 +16218,11 @@ var AgentExecutor = class {
15893
16218
  `);
15894
16219
  const chainName = params.chain;
15895
16220
  const chainId = domain.chainId;
15896
- let chain = Chain11.Ethereum;
16221
+ let chain = Chain12.Ethereum;
15897
16222
  if (chainName) {
15898
- chain = resolveChain(chainName) || Chain11.Ethereum;
16223
+ chain = resolveChain(chainName) || Chain12.Ethereum;
15899
16224
  } else if (chainId) {
15900
- chain = resolveChainId(chainId) || Chain11.Ethereum;
16225
+ chain = resolveChainId(chainId) || Chain12.Ethereum;
15901
16226
  }
15902
16227
  const sigResult = await this.vault.signBytes({
15903
16228
  data: eip712Hash,
@@ -17231,7 +17556,9 @@ var PipeInterface = class {
17231
17556
  session;
17232
17557
  rl = null;
17233
17558
  stopped = false;
17559
+ inputClosed = false;
17234
17560
  pendingPasswordResolve = null;
17561
+ pendingPasswordReject = null;
17235
17562
  pendingConfirmResolve = null;
17236
17563
  constructor(session) {
17237
17564
  this.session = session;
@@ -17263,38 +17590,45 @@ var PipeInterface = class {
17263
17590
  }))
17264
17591
  });
17265
17592
  }
17266
- const lines = [];
17593
+ const commands = [];
17267
17594
  let inputDone = false;
17268
17595
  let processing = false;
17269
17596
  this.rl.on("line", async (line) => {
17270
17597
  const trimmed = line.trim();
17271
17598
  if (!trimmed) return;
17272
- 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);
17273
17611
  if (!processing) {
17274
17612
  processing = true;
17275
- while (lines.length > 0) {
17276
- const nextLine = lines.shift();
17613
+ while (commands.length > 0) {
17614
+ const nextCommand = commands.shift();
17277
17615
  try {
17278
- const cmd = JSON.parse(nextLine);
17279
- await this.handleCommand(cmd);
17616
+ await this.handleCommand(nextCommand);
17280
17617
  } catch (err) {
17281
- const { message, code } = normalizeAgentError(err);
17282
- this.emit({
17283
- type: "error",
17284
- message: `Invalid input: ${message}`,
17285
- code: code === "UNKNOWN_ERROR" /* UNKNOWN_ERROR */ ? "INVALID_INPUT" /* INVALID_INPUT */ : code
17286
- });
17618
+ this.emitInvalidInput(err);
17287
17619
  }
17288
17620
  }
17289
17621
  processing = false;
17290
- if (inputDone && lines.length === 0) {
17622
+ if (inputDone && commands.length === 0) {
17291
17623
  this.stop();
17292
17624
  }
17293
17625
  }
17294
17626
  });
17295
17627
  this.rl.on("close", () => {
17628
+ this.inputClosed = true;
17296
17629
  inputDone = true;
17297
- if (!processing && lines.length === 0) {
17630
+ this.settlePendingPrompts("Password input closed before a reply was received");
17631
+ if (!processing && commands.length === 0) {
17298
17632
  this.stop();
17299
17633
  }
17300
17634
  });
@@ -17374,17 +17708,30 @@ var PipeInterface = class {
17374
17708
  onDone: () => {
17375
17709
  this.emit({ type: "done" });
17376
17710
  },
17377
- requestPassword: async () => {
17378
- 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) => {
17379
17722
  this.pendingPasswordResolve = resolve;
17723
+ this.pendingPasswordReject = reject;
17380
17724
  this.emit({
17381
17725
  type: "error",
17382
17726
  message: "PASSWORD_REQUIRED",
17383
17727
  code: "PASSWORD_REQUIRED" /* PASSWORD_REQUIRED */
17384
17728
  });
17385
17729
  });
17730
+ void password.catch(() => void 0);
17731
+ return password;
17386
17732
  },
17387
17733
  requestConfirmation: async (message) => {
17734
+ if (this.inputClosed) return false;
17388
17735
  return new Promise((resolve) => {
17389
17736
  this.pendingConfirmResolve = resolve;
17390
17737
  this.emit({
@@ -17406,21 +17753,14 @@ var PipeInterface = class {
17406
17753
  const { message, code } = normalizeAgentError(err);
17407
17754
  this.emit({ type: "error", message, code });
17408
17755
  this.emit({ type: "done" });
17756
+ } finally {
17757
+ this.settlePendingPrompts("Password request cancelled because the turn ended");
17409
17758
  }
17410
17759
  break;
17411
17760
  }
17412
- case "password": {
17413
- if (this.pendingPasswordResolve) {
17414
- this.pendingPasswordResolve(cmd.password);
17415
- this.pendingPasswordResolve = null;
17416
- }
17417
- break;
17418
- }
17761
+ case "password":
17419
17762
  case "confirm": {
17420
- if (this.pendingConfirmResolve) {
17421
- this.pendingConfirmResolve(cmd.confirmed);
17422
- this.pendingConfirmResolve = null;
17423
- }
17763
+ this.handleControlCommand(cmd);
17424
17764
  break;
17425
17765
  }
17426
17766
  default:
@@ -17431,6 +17771,54 @@ var PipeInterface = class {
17431
17771
  });
17432
17772
  }
17433
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
+ }
17434
17822
  emit(event) {
17435
17823
  process.stdout.write(JSON.stringify(event) + "\n");
17436
17824
  }
@@ -18344,6 +18732,11 @@ var AgentSession = class {
18344
18732
  recordResolution(txHash, "failed");
18345
18733
  return;
18346
18734
  }
18735
+ if (result.status === "expired") {
18736
+ ui.onTxStatus(txHash, chainName ?? "", "failed", explorerUrl);
18737
+ recordResolution(txHash, "failed");
18738
+ return;
18739
+ }
18347
18740
  } catch (err) {
18348
18741
  if (this.config.verbose) {
18349
18742
  process.stderr.write(`[session] tx confirm poll ${attempt + 1} failed: ${err?.message ?? err}
@@ -19480,7 +19873,7 @@ var cachedVersion = null;
19480
19873
  function getVersion() {
19481
19874
  if (cachedVersion) return cachedVersion;
19482
19875
  if (true) {
19483
- cachedVersion = "6.2.0";
19876
+ cachedVersion = "7.0.0";
19484
19877
  return cachedVersion;
19485
19878
  }
19486
19879
  try {
@@ -19977,7 +20370,7 @@ var EventBuffer = class {
19977
20370
  };
19978
20371
 
19979
20372
  // src/interactive/session.ts
19980
- import { Chain as Chain13, fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
20373
+ import { Chain as Chain14, fiatCurrencies as fiatCurrencies3 } from "@vultisig/sdk";
19981
20374
  import chalk14 from "chalk";
19982
20375
  import ora3 from "ora";
19983
20376
  import * as readline3 from "readline";
@@ -20828,7 +21221,7 @@ Error: ${error2.message}`));
20828
21221
  } else if (rest[i] === "--destination-tag") {
20829
21222
  const tag = rest[i + 1];
20830
21223
  const parsedTag = Number(tag);
20831
- 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) {
20832
21225
  throw new Error("Invalid XRP DestinationTag: expected an integer from 0 to 4294967295");
20833
21226
  }
20834
21227
  destinationTag = parsedTag;
@@ -21676,7 +22069,7 @@ See also: balance, tx-status`
21676
22069
  if (!amount && !options.max) throw new Error("Provide an amount or use --max");
21677
22070
  if (amount && options.max) throw new Error("Cannot specify both amount and --max");
21678
22071
  const chain = resolveChainOrThrow(chainStr);
21679
- if (options.destinationTag !== void 0 && chain !== Chain14.Ripple) {
22072
+ if (options.destinationTag !== void 0 && chain !== Chain15.Ripple) {
21680
22073
  throw new Error("--destination-tag is only supported for XRP");
21681
22074
  }
21682
22075
  const destinationTag = options.destinationTag === void 0 ? void 0 : Number(options.destinationTag);
@@ -21804,7 +22197,7 @@ program2.command("broadcast").description("Broadcast a pre-signed raw transactio
21804
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(
21805
22198
  "after",
21806
22199
  `
21807
- Statuses: pending, not_found, confirmed, failed
22200
+ Statuses: pending, not_found, expired, confirmed, failed
21808
22201
  Malformed hashes fail with INVALID_HASH (exit 4).
21809
22202
 
21810
22203
  Examples:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vultisig/cli",
3
- "version": "6.2.0",
3
+ "version": "7.0.0",
4
4
  "description": "The self-custody MPC wallet CLI for AI coding agents (Claude Code, Cursor, OpenCode). Natural-language agent mode, 36+ chains, DKLS23 threshold signatures. Seedless.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -73,10 +73,10 @@
73
73
  "@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.0",
78
- "@vultisig/rujira": "^70.0.0",
79
- "@vultisig/sdk": "^6.2.0",
76
+ "@vultisig/client-shared": "^0.3.6",
77
+ "@vultisig/core-chain": "^5.0.0",
78
+ "@vultisig/rujira": "^71.0.0",
79
+ "@vultisig/sdk": "^7.0.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.0",
97
+ "@vultisig/core-mpc": "^3.0.0",
98
98
  "esbuild": "^0.28.1",
99
99
  "ethers": "^6.17.0",
100
100
  "node-pty": "^1.1.0",