@vultisig/cli 6.2.0 → 6.3.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 +10 -0
  2. package/dist/index.js +185 -37
  3. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # @vultisig/cli
2
2
 
3
+ ## 6.3.0
4
+
5
+ ### Patch Changes
6
+
7
+ - [#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.
8
+
9
+ - 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)]:
10
+ - @vultisig/core-chain@4.1.1
11
+ - @vultisig/sdk@6.3.0
12
+
3
13
  ## 6.2.0
4
14
 
5
15
  ### Minor Changes
package/dist/index.js CHANGED
@@ -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) {
@@ -14493,13 +14467,177 @@ var ERC20_TRANSFER_ABI = parseAbi(["function transfer(address to, uint256 value)
14493
14467
  function decodeErc20Transfer(calldata) {
14494
14468
  if (calldata.slice(0, ERC20_TRANSFER_SELECTOR.length).toLowerCase() !== ERC20_TRANSFER_SELECTOR) return null;
14495
14469
  try {
14496
- const decoded = decodeFunctionData({ abi: ERC20_TRANSFER_ABI, data: calldata });
14470
+ const decoded = decodeFunctionData({
14471
+ abi: ERC20_TRANSFER_ABI,
14472
+ data: calldata
14473
+ });
14497
14474
  const [recipient, amount] = decoded.args;
14498
14475
  return { recipient, amount };
14499
14476
  } catch {
14500
14477
  throw new Error("Invalid ERC-20 transfer calldata \u2014 refusing to sign");
14501
14478
  }
14502
14479
  }
14480
+ var tokenLabel = (value) => typeof value === "string" ? value : "";
14481
+ var MAX_TOKEN_SYMBOL_LENGTH = 32;
14482
+ var MAX_TOKEN_LABEL_LENGTH = 512;
14483
+ function safeTokenSymbol(symbol) {
14484
+ const defaultDisplaySymbol = symbol.match(/^[\p{L}\p{N}][\p{L}\p{N}._+-]{0,31}/u)?.[0] ?? "token";
14485
+ if (symbol.length > MAX_TOKEN_SYMBOL_LENGTH) {
14486
+ return { displaySymbol: defaultDisplaySymbol, suspicious: true };
14487
+ }
14488
+ const suspiciousAt = symbol.search(/0x[0-9a-f]{40}|\([^)]*\bon\b[^)]*\)/iu);
14489
+ const candidate = (suspiciousAt < 0 ? symbol : symbol.slice(0, suspiciousAt)).trim();
14490
+ const displaySymbol = candidate.match(/^[\p{L}\p{N}][\p{L}\p{N}._+-]*/u)?.[0] ?? "token";
14491
+ if (suspiciousAt < 0 && candidate === displaySymbol) {
14492
+ return { displaySymbol, suspicious: false };
14493
+ }
14494
+ return { displaySymbol, suspicious: true };
14495
+ }
14496
+ function unresolvedTokenIdentity(label) {
14497
+ if (typeof label !== "string" || label.length > MAX_TOKEN_LABEL_LENGTH) {
14498
+ return { rawSymbol: "", displaySymbol: "", kind: "unresolved" };
14499
+ }
14500
+ const suffixAt = label.lastIndexOf(" (");
14501
+ const rawSymbol = (suffixAt > 0 ? label.slice(0, suffixAt) : label).trim();
14502
+ return {
14503
+ rawSymbol,
14504
+ displaySymbol: safeTokenSymbol(rawSymbol).displaySymbol,
14505
+ kind: "unresolved"
14506
+ };
14507
+ }
14508
+ function untrustedSymbolIdentity(symbol) {
14509
+ const rawSymbol = tokenLabel(symbol);
14510
+ return {
14511
+ rawSymbol,
14512
+ displaySymbol: safeTokenSymbol(rawSymbol).displaySymbol,
14513
+ kind: "unresolved"
14514
+ };
14515
+ }
14516
+ function resolvedTokenIdentity(label, expectedChain) {
14517
+ const unresolved = unresolvedTokenIdentity(label);
14518
+ if (typeof label !== "string" || label.length > MAX_TOKEN_LABEL_LENGTH) return unresolved;
14519
+ const match = label.match(/^([\s\S]*) \(([\s\S]*?) on ([^,]+), \d+ dec, source: [^)]+\)$/u);
14520
+ if (!match) return unresolved;
14521
+ const [, rawSymbol, rawAssetId, descriptorChain] = match;
14522
+ const { displaySymbol, suspicious } = safeTokenSymbol(rawSymbol);
14523
+ const parsedChain = resolveChainReference(descriptorChain);
14524
+ const routedChain = typeof expectedChain === "string" || typeof expectedChain === "number" ? resolveChainReference(expectedChain) : void 0;
14525
+ if (suspicious || !parsedChain || !routedChain || parsedChain !== routedChain) {
14526
+ return { rawSymbol, displaySymbol, kind: "unresolved" };
14527
+ }
14528
+ const assetId = rawAssetId.trim();
14529
+ if (!assetId) return { rawSymbol, displaySymbol, kind: "unresolved" };
14530
+ if (assetId.toLowerCase() === "native") return { rawSymbol, displaySymbol, kind: "native" };
14531
+ if (getChainKind4(routedChain) === "evm") {
14532
+ if (!/^0x[0-9a-f]{40}$/iu.test(assetId)) return { rawSymbol, displaySymbol, kind: "unresolved" };
14533
+ } else if (!/^[\p{L}\p{N}:._/-]{1,128}$/u.test(assetId)) {
14534
+ return { rawSymbol, displaySymbol, kind: "unresolved" };
14535
+ }
14536
+ return { rawSymbol, displaySymbol, kind: "token", contract: assetId };
14537
+ }
14538
+ function replaceUnsafeSymbol(text, identity) {
14539
+ if (!identity.rawSymbol || identity.rawSymbol === identity.displaySymbol) return text;
14540
+ return text.split(identity.rawSymbol).join(identity.displaySymbol);
14541
+ }
14542
+ function anchorEnd(text, label) {
14543
+ if (!label) return -1;
14544
+ const tokenChar = /[\p{L}\p{N}._+-]/u;
14545
+ const trimmedLength = text.trimEnd().length;
14546
+ const index = trimmedLength - label.length;
14547
+ if (index < 0 || !text.startsWith(label, index)) return -1;
14548
+ const before = text[index - 1];
14549
+ if (before && tokenChar.test(before)) return -1;
14550
+ return trimmedLength;
14551
+ }
14552
+ function hasAnchor(text, anchors) {
14553
+ return anchors.some((anchor) => anchorEnd(text, anchor) >= 0);
14554
+ }
14555
+ function hasSingleSwapDelimiter(summary, labels) {
14556
+ const identities = [
14557
+ unresolvedTokenIdentity(labels.from_token),
14558
+ untrustedSymbolIdentity(labels.from_token_symbol),
14559
+ unresolvedTokenIdentity(labels.to_token),
14560
+ untrustedSymbolIdentity(labels.to_token_symbol)
14561
+ ];
14562
+ const structuralSummary = identities.reduce((text, identity) => replaceUnsafeSymbol(text, identity), summary);
14563
+ const firstArrow = structuralSummary.indexOf("\u2192");
14564
+ return firstArrow >= 0 && structuralSummary.indexOf("\u2192", firstArrow + 1) < 0;
14565
+ }
14566
+ function splitSwapHead(head, sellAnchors, buyAnchors) {
14567
+ let best;
14568
+ let arrowAt = head.indexOf("\u2192");
14569
+ while (arrowAt >= 0) {
14570
+ const left = head.slice(0, arrowAt);
14571
+ const right = head.slice(arrowAt + 1);
14572
+ const viaAt = right.indexOf(" via ");
14573
+ const buy = viaAt >= 0 ? right.slice(0, viaAt) : right;
14574
+ const provider = viaAt >= 0 ? right.slice(viaAt) : "";
14575
+ const score = Number(hasAnchor(left, sellAnchors)) + Number(hasAnchor(buy, buyAnchors));
14576
+ if (!best || score > best.score) {
14577
+ best = { left, buy, provider, score };
14578
+ }
14579
+ arrowAt = head.indexOf("\u2192", arrowAt + 1);
14580
+ }
14581
+ return best;
14582
+ }
14583
+ function terminalAnchorSymbol(anchor) {
14584
+ return anchor.trim().match(/[\p{L}\p{N}][\p{L}\p{N}._+-]*$/u)?.[0] ?? "";
14585
+ }
14586
+ function discloseSwapSide(side, anchors, suffix, expectedSymbol) {
14587
+ if (!expectedSymbol) {
14588
+ return `${side.trimEnd()} (contract unavailable)${side.slice(side.trimEnd().length)}`;
14589
+ }
14590
+ for (const anchor of anchors) {
14591
+ const end = anchorEnd(side, anchor);
14592
+ if (end < 0) continue;
14593
+ const visibleSymbol = terminalAnchorSymbol(anchor);
14594
+ if (expectedSymbol && visibleSymbol && visibleSymbol !== expectedSymbol) break;
14595
+ if (!suffix) return side;
14596
+ return `${side.slice(0, end)}${suffix}${side.slice(end)}`;
14597
+ }
14598
+ return `${side.trimEnd()} (contract unavailable)${side.slice(side.trimEnd().length)}`;
14599
+ }
14600
+ function discloseSwapTokenContracts(head, labels, payload, sourceChain) {
14601
+ 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;
14602
+ const fromToken = resolvedTokenIdentity(labels.from_token, sourceChain);
14603
+ const toToken = resolvedTokenIdentity(labels.to_token, destinationChain);
14604
+ const fromSymbol = untrustedSymbolIdentity(labels.from_token_symbol);
14605
+ const toSymbol = untrustedSymbolIdentity(labels.to_token_symbol);
14606
+ const approvalArgs = payload?.approvalTxArgs;
14607
+ const rawApprovalTarget = tokenLabel(
14608
+ approvalArgs && typeof approvalArgs === "object" ? approvalArgs.tx?.to : void 0
14609
+ );
14610
+ const approvalTarget = /^0x[0-9a-f]{40}$/iu.test(rawApprovalTarget) ? rawApprovalTarget : "";
14611
+ const symbolIdentities = [fromToken, fromSymbol, toToken, toSymbol];
14612
+ const sanitizeText = (value) => symbolIdentities.reduce((text, identity) => replaceUnsafeSymbol(text, identity), tokenLabel(value));
14613
+ const sanitizedHead = sanitizeText(head);
14614
+ const sellAnchors = [sanitizeText(labels.amount_in), fromSymbol.displaySymbol, fromToken.displaySymbol].filter(
14615
+ Boolean
14616
+ );
14617
+ const buyAnchors = [sanitizeText(labels.expected_output), toSymbol.displaySymbol, toToken.displaySymbol].filter(
14618
+ Boolean
14619
+ );
14620
+ const approvalMatchesResolvedSellToken = fromToken.kind === "token" && !!fromToken.contract && !!approvalTarget && fromToken.contract.toLowerCase() === approvalTarget.toLowerCase();
14621
+ const sellSuffix = payload?.approvalTxArgs ? approvalMatchesResolvedSellToken ? ` (${approvalTarget})` : " (contract unavailable)" : fromToken.kind === "native" ? "" : fromToken.kind === "token" && fromToken.contract ? ` (${fromToken.contract})` : " (contract unavailable)";
14622
+ const buySuffix = toToken.kind === "native" ? "" : toToken.kind === "token" && toToken.contract ? ` (${toToken.contract})` : " (contract unavailable)";
14623
+ const halves = splitSwapHead(sanitizedHead, sellAnchors, buyAnchors);
14624
+ if (!halves) return `${sanitizedHead} (contract unavailable)`;
14625
+ const sellResolved = fromToken.kind !== "unresolved";
14626
+ const buyResolved = toToken.kind !== "unresolved";
14627
+ const sell = discloseSwapSide(
14628
+ halves.left,
14629
+ sellAnchors,
14630
+ sellResolved ? sellSuffix : "",
14631
+ sellResolved ? fromToken.displaySymbol : void 0
14632
+ );
14633
+ const buy = discloseSwapSide(
14634
+ halves.buy,
14635
+ buyAnchors,
14636
+ buyResolved ? buySuffix : "",
14637
+ buyResolved ? toToken.displaySymbol : void 0
14638
+ );
14639
+ return `${sell}\u2192${buy}${halves.provider}`;
14640
+ }
14503
14641
  var isEvmChain = (chain) => EVM_CHAINS.has(chain);
14504
14642
  function stripEmbeddedPayloadContract(value, disclosedContract) {
14505
14643
  if (!disclosedContract) return value;
@@ -14885,12 +15023,22 @@ var AgentExecutor = class {
14885
15023
  }
14886
15024
  const isSwap = !!(p?.approvalTxArgs || p?.swap_tx || labels.quote_summary || labels.to_token_symbol);
14887
15025
  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();
15026
+ const rawQuoteSummary = tokenLabel(labels.quote_summary);
15027
+ const quoteSummary = hasSingleSwapDelimiter(rawQuoteSummary, labels) ? rawQuoteSummary : "";
15028
+ const usedQuoteSummary = !!quoteSummary;
15029
+ const amountIn = tokenLabel(labels.amount_in) || tokenLabel(p?.txArgs?.amount) || "?";
15030
+ const fromSymbol = tokenLabel(labels.from_token_symbol);
15031
+ const sellHead = fromSymbol && !amountIn.endsWith(` ${fromSymbol}`) ? `${amountIn} ${fromSymbol}` : amountIn;
15032
+ const head = discloseSwapTokenContracts(
15033
+ quoteSummary || `swap ${sellHead} \u2192 ${tokenLabel(labels.to_token_symbol) || "?"}`,
15034
+ labels,
15035
+ p,
15036
+ stored.chain
15037
+ );
14890
15038
  const parts = [head, `on ${stored.chain}`];
14891
- if (!usedQuoteSummary && labels.provider) parts.push(`via ${labels.provider}`);
15039
+ if (!usedQuoteSummary && tokenLabel(labels.provider)) parts.push(`via ${tokenLabel(labels.provider)}`);
14892
15040
  if (p?.__multiLeg) parts.push("(+ token approval \u2014 2 transactions)");
14893
- if (labels.estimated_fee) parts.push(`est. fee ${labels.estimated_fee}`);
15041
+ if (tokenLabel(labels.estimated_fee)) parts.push(`est. fee ${tokenLabel(labels.estimated_fee)}`);
14894
15042
  return parts.join(" ");
14895
15043
  }
14896
15044
  const signedTx = extractNestedTx(p);
@@ -14914,18 +15062,18 @@ var AgentExecutor = class {
14914
15062
  */
14915
15063
  renderLabelSendSummary(p, labels, chain, to, disclosedContract) {
14916
15064
  const amount = stripEmbeddedPayloadContract(labels.resolved_amount ?? p?.txArgs?.amount ?? "?", disclosedContract);
14917
- const tokenLabel = stripEmbeddedPayloadContract(
15065
+ const tokenLabel2 = stripEmbeddedPayloadContract(
14918
15066
  (labels.token_resolved || labels.token_symbol || "").trim(),
14919
15067
  disclosedContract
14920
15068
  );
14921
- const symbol = tokenLabel.split(/\s+/, 1)[0];
15069
+ const symbol = tokenLabel2.split(/\s+/, 1)[0];
14922
15070
  const escapedChain = String(chain).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
14923
15071
  const routedChainPattern = new RegExp(`(?:^|\\s)(?:not\\s+)?on ${escapedChain}(?=\\s|$)`);
14924
15072
  const positiveRoutedChainPattern = new RegExp(`(?:^|\\s)(?<!\\bnot\\s+)on ${escapedChain}(?=\\s|$)`);
14925
- const tokenContext = tokenLabel.slice(symbol.length);
15073
+ const tokenContext = tokenLabel2.slice(symbol.length);
14926
15074
  const labelCarriesRoutedChain = positiveRoutedChainPattern.test(tokenContext);
14927
15075
  const tokenDetail = tokenContext.replace(routedChainPattern, "").trim();
14928
- const amountEmbedsTokenLabel = tokenLabel.length > 0 && amount.endsWith(` ${tokenLabel}`);
15076
+ const amountEmbedsTokenLabel = tokenLabel2.length > 0 && amount.endsWith(` ${tokenLabel2}`);
14929
15077
  const amountWithSymbol = symbol && !amount.endsWith(` ${symbol}`) && !amountEmbedsTokenLabel ? `${amount} ${symbol}` : amount;
14930
15078
  const tokenDetailSuffix = amountEmbedsTokenLabel || !tokenDetail ? "" : ` ${tokenDetail}`;
14931
15079
  const location = amountEmbedsTokenLabel && labelCarriesRoutedChain ? "" : `on ${chain}${tokenDetailSuffix}`;
@@ -19480,7 +19628,7 @@ var cachedVersion = null;
19480
19628
  function getVersion() {
19481
19629
  if (cachedVersion) return cachedVersion;
19482
19630
  if (true) {
19483
- cachedVersion = "6.2.0";
19631
+ cachedVersion = "6.3.0";
19484
19632
  return cachedVersion;
19485
19633
  }
19486
19634
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vultisig/cli",
3
- "version": "6.2.0",
3
+ "version": "6.3.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": {
@@ -74,9 +74,9 @@
74
74
  "@napi-rs/keyring": "^1.3.0",
75
75
  "@noble/hashes": "^2.2.0",
76
76
  "@vultisig/client-shared": "^0.3.5",
77
- "@vultisig/core-chain": "^4.1.0",
77
+ "@vultisig/core-chain": "^4.1.1",
78
78
  "@vultisig/rujira": "^70.0.0",
79
- "@vultisig/sdk": "^6.2.0",
79
+ "@vultisig/sdk": "^6.3.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": "^2.1.1",
98
98
  "esbuild": "^0.28.1",
99
99
  "ethers": "^6.17.0",
100
100
  "node-pty": "^1.1.0",