@trustwallet/cli 0.20.0 → 0.22.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.
package/dist/index.js CHANGED
@@ -1046,8 +1046,8 @@ var require_command = __commonJS({
1046
1046
  "use strict";
1047
1047
  var EventEmitter = require("events").EventEmitter;
1048
1048
  var childProcess = require("child_process");
1049
- var path5 = require("path");
1050
- var fs7 = require("fs");
1049
+ var path7 = require("path");
1050
+ var fs10 = require("fs");
1051
1051
  var process3 = require("process");
1052
1052
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1053
1053
  var { CommanderError: CommanderError2 } = require_error();
@@ -1979,11 +1979,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
1979
1979
  let launchWithNode = false;
1980
1980
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
1981
1981
  function findFile(baseDir, baseName) {
1982
- const localBin = path5.resolve(baseDir, baseName);
1983
- if (fs7.existsSync(localBin)) return localBin;
1984
- if (sourceExt.includes(path5.extname(baseName))) return void 0;
1982
+ const localBin = path7.resolve(baseDir, baseName);
1983
+ if (fs10.existsSync(localBin)) return localBin;
1984
+ if (sourceExt.includes(path7.extname(baseName))) return void 0;
1985
1985
  const foundExt = sourceExt.find(
1986
- (ext) => fs7.existsSync(`${localBin}${ext}`)
1986
+ (ext) => fs10.existsSync(`${localBin}${ext}`)
1987
1987
  );
1988
1988
  if (foundExt) return `${localBin}${foundExt}`;
1989
1989
  return void 0;
@@ -1995,21 +1995,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
1995
1995
  if (this._scriptPath) {
1996
1996
  let resolvedScriptPath;
1997
1997
  try {
1998
- resolvedScriptPath = fs7.realpathSync(this._scriptPath);
1998
+ resolvedScriptPath = fs10.realpathSync(this._scriptPath);
1999
1999
  } catch (err) {
2000
2000
  resolvedScriptPath = this._scriptPath;
2001
2001
  }
2002
- executableDir = path5.resolve(
2003
- path5.dirname(resolvedScriptPath),
2002
+ executableDir = path7.resolve(
2003
+ path7.dirname(resolvedScriptPath),
2004
2004
  executableDir
2005
2005
  );
2006
2006
  }
2007
2007
  if (executableDir) {
2008
2008
  let localFile = findFile(executableDir, executableFile);
2009
2009
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2010
- const legacyName = path5.basename(
2010
+ const legacyName = path7.basename(
2011
2011
  this._scriptPath,
2012
- path5.extname(this._scriptPath)
2012
+ path7.extname(this._scriptPath)
2013
2013
  );
2014
2014
  if (legacyName !== this._name) {
2015
2015
  localFile = findFile(
@@ -2020,7 +2020,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2020
2020
  }
2021
2021
  executableFile = localFile || executableFile;
2022
2022
  }
2023
- launchWithNode = sourceExt.includes(path5.extname(executableFile));
2023
+ launchWithNode = sourceExt.includes(path7.extname(executableFile));
2024
2024
  let proc;
2025
2025
  if (process3.platform !== "win32") {
2026
2026
  if (launchWithNode) {
@@ -2860,7 +2860,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2860
2860
  * @return {Command}
2861
2861
  */
2862
2862
  nameFromFilename(filename) {
2863
- this._name = path5.basename(filename, path5.extname(filename));
2863
+ this._name = path7.basename(filename, path7.extname(filename));
2864
2864
  return this;
2865
2865
  }
2866
2866
  /**
@@ -2874,9 +2874,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2874
2874
  * @param {string} [path]
2875
2875
  * @return {(string|null|Command)}
2876
2876
  */
2877
- executableDir(path6) {
2878
- if (path6 === void 0) return this._executableDir;
2879
- this._executableDir = path6;
2877
+ executableDir(path8) {
2878
+ if (path8 === void 0) return this._executableDir;
2879
+ this._executableDir = path8;
2880
2880
  return this;
2881
2881
  }
2882
2882
  /**
@@ -3158,6 +3158,7 @@ __export(dist_exports, {
3158
3158
  coinTypeToChainKey: () => coinTypeToChainKey,
3159
3159
  createTwakClient: () => createTwakClient,
3160
3160
  encodeErc20Transfer: () => encodeErc20Transfer,
3161
+ errorMessage: () => errorMessage,
3161
3162
  fetchBalance: () => fetchBalance,
3162
3163
  fetchBalances: () => fetchBalances,
3163
3164
  formatAmount: () => formatAmount,
@@ -3315,6 +3316,20 @@ function getSupportedCoinTypes() {
3315
3316
  function isCoinTypeSupported(coinType) {
3316
3317
  return Object.values(CHAINS).some((chain2) => chain2.coinId === coinType);
3317
3318
  }
3319
+ function errorMessage(err) {
3320
+ if (err instanceof Error) return err.message;
3321
+ if (typeof err === "string") return err;
3322
+ if (err && typeof err === "object") {
3323
+ const { message } = err;
3324
+ if (typeof message === "string") return message;
3325
+ try {
3326
+ const json = JSON.stringify(err);
3327
+ if (typeof json === "string") return json;
3328
+ } catch {
3329
+ }
3330
+ }
3331
+ return String(err);
3332
+ }
3318
3333
  function classifyApiError(err) {
3319
3334
  if (err instanceof TwakApiError) {
3320
3335
  const status = err.statusCode;
@@ -3357,7 +3372,7 @@ function classifyStorageError(err) {
3357
3372
  syscall
3358
3373
  );
3359
3374
  if (isFsByCode || isFsBySyscall) {
3360
- const msg = err instanceof Error ? err.message : String(err);
3375
+ const msg = errorMessage(err);
3361
3376
  return { code: "STORAGE_ERROR", message: msg };
3362
3377
  }
3363
3378
  }
@@ -3367,7 +3382,7 @@ function classifyStorageError(err) {
3367
3382
  return null;
3368
3383
  }
3369
3384
  function classifyWalletLoadError(err) {
3370
- const msg = err instanceof Error ? err.message : String(err);
3385
+ const msg = errorMessage(err);
3371
3386
  if (/no wallet password/i.test(msg)) {
3372
3387
  return { code: "PASSWORD_MISSING", message: msg };
3373
3388
  }
@@ -3678,11 +3693,11 @@ function sortQueryRaw(query) {
3678
3693
  });
3679
3694
  return parts.join("&");
3680
3695
  }
3681
- function signRequest(method, path5, query, accessId, hmacSecret) {
3696
+ function signRequest(method, path7, query, accessId, hmacSecret) {
3682
3697
  const date = (/* @__PURE__ */ new Date()).toUTCString();
3683
3698
  const nonce = (0, import_crypto.randomUUID)();
3684
3699
  const sortedQuery = sortQueryRaw(query);
3685
- const plaintext = [method.toUpperCase(), path5, sortedQuery, accessId, nonce, date].join(";");
3700
+ const plaintext = [method.toUpperCase(), path7, sortedQuery, accessId, nonce, date].join(";");
3686
3701
  const signature = (0, import_crypto.createHmac)("sha256", hmacSecret).update(plaintext).digest("base64");
3687
3702
  return {
3688
3703
  "X-TW-CREDENTIAL": accessId,
@@ -4329,7 +4344,13 @@ var init_dist = __esm({
4329
4344
  // Raised by signing-path action providers when no wallet mode has been
4330
4345
  // selected yet for this server lifetime. Actionable: agent calls
4331
4346
  // switch_wallet_mode (local or walletconnect) and retries.
4332
- WALLET_NOT_BOUND: "WALLET_NOT_BOUND"
4347
+ WALLET_NOT_BOUND: "WALLET_NOT_BOUND",
4348
+ // The user declined a signing request in their wallet (WalletConnect).
4349
+ // Not retryable: the agent should ask the user instead of re-sending.
4350
+ USER_REJECTED: "USER_REJECTED",
4351
+ // The connected wallet answered a WalletConnect request with an error other
4352
+ // than a user rejection (e.g. unsupported method, invalid params).
4353
+ WC_REQUEST_FAILED: "WC_REQUEST_FAILED"
4333
4354
  };
4334
4355
  WalletError = class extends CommandError {
4335
4356
  constructor(code2, message) {
@@ -4441,12 +4462,12 @@ var init_dist = __esm({
4441
4462
  ...config
4442
4463
  };
4443
4464
  }
4444
- async gateway(method, path5, body, query) {
4445
- const url = new URL(path5, this.config.gatewayBaseUrl);
4465
+ async gateway(method, path7, body, query) {
4466
+ const url = new URL(path7, this.config.gatewayBaseUrl);
4446
4467
  if (query) url.search = query;
4447
4468
  const headers = signRequest(
4448
4469
  method,
4449
- path5,
4470
+ path7,
4450
4471
  query || "",
4451
4472
  this.config.accessId,
4452
4473
  this.config.hmacSecret
@@ -4461,14 +4482,14 @@ var init_dist = __esm({
4461
4482
  body: body ? JSON.stringify(body) : void 0
4462
4483
  });
4463
4484
  }
4464
- async amber(method, path5, body) {
4465
- if (!path5.startsWith("/amber-api/")) {
4485
+ async amber(method, path7, body) {
4486
+ if (!path7.startsWith("/amber-api/")) {
4466
4487
  throw new Error("Amber API path must start with '/amber-api/'.");
4467
4488
  }
4468
- return this.gateway(method, path5, body);
4489
+ return this.gateway(method, path7, body);
4469
4490
  }
4470
- async market(method, path5, body) {
4471
- const url = new URL(path5, this.config.marketBaseUrl);
4491
+ async market(method, path7, body) {
4492
+ const url = new URL(path7, this.config.marketBaseUrl);
4472
4493
  const raw = await this.fetch(url.toString(), {
4473
4494
  method,
4474
4495
  headers: { "Content-Type": "application/json" },
@@ -5803,15 +5824,15 @@ var require_route = __commonJS({
5803
5824
  };
5804
5825
  }
5805
5826
  function wrapConversion(toModel, graph) {
5806
- const path5 = [graph[toModel].parent, toModel];
5827
+ const path7 = [graph[toModel].parent, toModel];
5807
5828
  let fn2 = conversions[graph[toModel].parent][toModel];
5808
5829
  let cur = graph[toModel].parent;
5809
5830
  while (graph[cur].parent) {
5810
- path5.unshift(graph[cur].parent);
5831
+ path7.unshift(graph[cur].parent);
5811
5832
  fn2 = link(conversions[graph[cur].parent][cur], fn2);
5812
5833
  cur = graph[cur].parent;
5813
5834
  }
5814
- fn2.conversion = path5;
5835
+ fn2.conversion = path7;
5815
5836
  return fn2;
5816
5837
  }
5817
5838
  module2.exports = function(fromModel) {
@@ -6976,8 +6997,10 @@ async function initTrustSDK(config) {
6976
6997
  new import_trust_sdk.Node(cfg.rpcUrl)
6977
6998
  );
6978
6999
  } catch (err) {
6979
- process.stderr.write(`twak: could not register testnet node for ${cfg.id}: ${String(err)}
6980
- `);
7000
+ process.stderr.write(
7001
+ `twak: could not register testnet node for ${cfg.id}: ${errorMessage(err)}
7002
+ `
7003
+ );
6981
7004
  }
6982
7005
  }
6983
7006
  initialized = true;
@@ -7269,7 +7292,7 @@ async function verifyCredentials(config) {
7269
7292
  try {
7270
7293
  response = await gatewayFetch("PUT", `/v2/devices/${deviceId}`, config, DEVICE_INFO_BODY);
7271
7294
  } catch (err) {
7272
- const message = err instanceof Error ? err.message : String(err);
7295
+ const message = errorMessage(err);
7273
7296
  throw new VerifyCredentialsError(
7274
7297
  "NETWORK_ERROR",
7275
7298
  `Could not reach Trust Wallet gateway: ${message}`
@@ -8092,10 +8115,11 @@ var init_dist3 = __esm({
8092
8115
  signal: AbortSignal.timeout(timeoutMs)
8093
8116
  });
8094
8117
  } catch (err) {
8095
- throw new TwakNetworkError(
8096
- `RPC fetch failed on ${method}: ${err instanceof Error ? err.message : String(err)}`,
8097
- { method, rpcUrl, timeoutMs }
8098
- );
8118
+ throw new TwakNetworkError(`RPC fetch failed on ${method}: ${errorMessage(err)}`, {
8119
+ method,
8120
+ rpcUrl,
8121
+ timeoutMs
8122
+ });
8099
8123
  }
8100
8124
  if (!response.ok) {
8101
8125
  const body = await response.text().catch(() => "");
@@ -8112,7 +8136,7 @@ var init_dist3 = __esm({
8112
8136
  json = await response.json();
8113
8137
  } catch (err) {
8114
8138
  throw new TwakNetworkError(
8115
- `RPC response JSON parse failed on ${method}: ${err instanceof Error ? err.message : String(err)}`,
8139
+ `RPC response JSON parse failed on ${method}: ${errorMessage(err)}`,
8116
8140
  { method, rpcUrl }
8117
8141
  );
8118
8142
  }
@@ -8523,50 +8547,83 @@ var init_dist3 = __esm({
8523
8547
  WalletContext = class {
8524
8548
  _state = "unbound";
8525
8549
  _wallet;
8526
- /**
8527
- * The WC instance we're currently pairing with. Used to ignore callbacks
8528
- * from a previous pairing if the user re-calls `beginWalletConnect` while
8529
- * already in `wc-pairing` (e.g. the first QR went stale and they want a
8530
- * fresh one). Without this guard, the first wc's session-established
8531
- * callback could land later and overwrite `_wallet` with the wrong instance.
8532
- */
8533
- _pendingWc;
8534
8550
  _pairing;
8535
8551
  _peerName;
8536
8552
  _lastError;
8553
+ _walletConnectFactory;
8554
+ _wc;
8537
8555
  /**
8538
8556
  * Mode-switch serializer. Concurrent `bindLocal` / `beginWalletConnect` /
8539
- * `reset` calls otherwise interleave their state writes — `_pendingWc`
8540
- * identity guards prevent signing-against-wrong-wallet, but `_state` and
8541
- * `_pairing` can land in an inconsistent shape. We queue each entry so the
8557
+ * `reset` calls otherwise interleave their state writes, leaving `_state`
8558
+ * and `_pairing` in an inconsistent shape. We queue each entry so the
8542
8559
  * second call waits for the first to settle. `.catch(()=>{})` on the field
8543
8560
  * itself keeps a rejected switch from poisoning the queue for later calls
8544
8561
  * (the caller still sees the original rejection via the awaited promise
8545
8562
  * we return from each entry method).
8546
8563
  */
8547
8564
  _switchInflight = Promise.resolve();
8565
+ /** Bumped by every explicit mode switch, so a session restore that
8566
+ * resolves after the user already chose a mode is not bound. */
8567
+ _generation = 0;
8568
+ constructor(options = {}) {
8569
+ this._walletConnectFactory = options.walletConnectFactory;
8570
+ }
8571
+ /** Whether WalletConnect can be used (a project ID was configured). */
8572
+ get walletConnectAvailable() {
8573
+ return this._walletConnectFactory !== void 0;
8574
+ }
8548
8575
  /**
8549
- * Disconnect any in-flight or established WC session. Used internally when
8550
- * switching modes and externally by `reset()`. Best-effort — disconnect
8551
- * failures are swallowed (state still progresses to a clean slot).
8552
- *
8553
- * Covers both `wc-connected` (via `_wallet`) and `wc-pairing` (via
8554
- * `_pendingWc`). Without the pending-wc disconnect, switching away from
8555
- * `wc-pairing` would leave the relay client open until expiry.
8576
+ * If the peer wallet later revokes the session (user dismisses on their
8577
+ * phone, relay-side expiry, etc.), flip context back to `unbound` so the
8578
+ * next sign attempt surfaces WALLET_NOT_BOUND with a recoverable hint
8579
+ * instead of a deep WC-RPC failure. Only reacts while bound to WC.
8556
8580
  */
8557
- async disconnectWcIfAny() {
8558
- const targets = [];
8559
- if (this._wallet && this._wallet.name === "walletconnect") {
8560
- targets.push(this._wallet);
8561
- }
8562
- if (this._pendingWc && this._pendingWc !== this._wallet) {
8563
- targets.push(this._pendingWc);
8581
+ handleSessionEnded(reason) {
8582
+ if (!this._wc || this._wallet !== this._wc) return;
8583
+ this._wallet = void 0;
8584
+ this._pairing = void 0;
8585
+ this._peerName = void 0;
8586
+ this._state = "unbound";
8587
+ this._lastError = {
8588
+ code: "WC_NOT_CONNECTED",
8589
+ message: `WalletConnect session ended (${reason}). Call switch_wallet_mode to re-pair or switch to local.`
8590
+ };
8591
+ }
8592
+ /** Bind the approved pairing, unless the user switched away meanwhile. */
8593
+ handleSessionEstablished(session) {
8594
+ if (this._state !== "wc-pairing" || !this._wc) return;
8595
+ this._wallet = this._wc;
8596
+ this._peerName = session.peerName;
8597
+ this._state = "wc-connected";
8598
+ this._pairing = void 0;
8599
+ }
8600
+ /** The process's single WC provider, created on first use. */
8601
+ walletConnect() {
8602
+ if (this._wc) return this._wc;
8603
+ if (!this._walletConnectFactory) {
8604
+ throw new WalletError(
8605
+ "VALIDATION_ERROR",
8606
+ "WalletConnect is not configured: no project ID. Set WALLETCONNECT_PROJECT_ID or run `twak init --wc-project-id <id>`, then restart the server."
8607
+ );
8564
8608
  }
8565
- for (const t of targets) {
8566
- try {
8567
- await t.disconnect();
8568
- } catch {
8569
- }
8609
+ const wc3 = this._walletConnectFactory();
8610
+ wc3.onSessionEstablished((session) => this.handleSessionEstablished(session));
8611
+ wc3.onSessionEnded((reason) => this.handleSessionEnded(reason));
8612
+ this._wc = wc3;
8613
+ return wc3;
8614
+ }
8615
+ /**
8616
+ * Revoke the WC session, if a provider was ever used. Covers bound,
8617
+ * pairing and still-restoring sessions alike (the provider waits for an
8618
+ * in-flight init). Bounded: peer notification is time-limited by the
8619
+ * provider and local removal never needs the relay. Best-effort: failures
8620
+ * are swallowed so the switch still lands in a clean state.
8621
+ */
8622
+ async disconnectWcIfAny() {
8623
+ if (!this._wc) return;
8624
+ try {
8625
+ await this._wc.disconnect();
8626
+ } catch {
8570
8627
  }
8571
8628
  }
8572
8629
  get state() {
@@ -8608,102 +8665,101 @@ var init_dist3 = __esm({
8608
8665
  return base3;
8609
8666
  }
8610
8667
  /**
8611
- * Bind to the local agent wallet. Idempotent for the same provider; when
8612
- * called while bound to WC (`wc-connected` or `wc-pairing`), the WC session
8613
- * is disconnected first and any in-flight pairing is invalidated so its
8614
- * later session-established callback can't reclaim state.
8668
+ * Run `task` after any in-flight mode switch finishes. The returned promise
8669
+ * settles with `task`'s outcome (success or failure). The internal queue
8670
+ * promise always resolves so a failing switch can't block subsequent
8671
+ * switches.
8672
+ */
8673
+ enqueue(task) {
8674
+ const next = this._switchInflight.then(task);
8675
+ this._switchInflight = next.catch(() => void 0);
8676
+ return next;
8677
+ }
8678
+ /**
8679
+ * Bind to the local agent wallet. This is an explicit choice: any WC
8680
+ * session (bound, pairing, or still being restored) is revoked first, so a
8681
+ * restart does not resume it.
8615
8682
  */
8616
8683
  async bindLocal(agent) {
8617
8684
  return this.enqueue(() => this.bindLocalImpl(agent));
8618
8685
  }
8619
8686
  async bindLocalImpl(agent) {
8687
+ this._generation++;
8620
8688
  await this.disconnectWcIfAny();
8621
- this._pendingWc = void 0;
8622
8689
  this._wallet = agent;
8623
8690
  this._pairing = void 0;
8624
8691
  this._peerName = void 0;
8625
8692
  this._state = "local";
8626
8693
  this._lastError = void 0;
8627
8694
  }
8628
- /**
8629
- * Run `task` after any in-flight mode switch finishes. The returned promise
8630
- * settles with `task`'s outcome (success or failure). The internal queue
8631
- * promise always resolves so a failing switch can't block subsequent
8632
- * switches.
8633
- */
8634
- enqueue(task) {
8635
- const next = this._switchInflight.then(task);
8636
- this._switchInflight = next.catch(() => void 0);
8637
- return next;
8638
- }
8639
8695
  /**
8640
8696
  * Start a WalletConnect pairing. Returns the pairing payload immediately;
8641
8697
  * the caller surfaces it (QR code, deep link). When any compatible client
8642
8698
  * approves, the provider's session_established event fires and the context
8643
- * transitions to `wc-connected`.
8699
+ * transitions to `wc-connected`. The previous session is revoked first:
8700
+ * asking to pair again expresses the intent to invalidate it.
8644
8701
  *
8645
- * V1 does not restrict the peer wallet — any WC v2 client can pair. The
8646
- * user's choice to scan the QR is the consent moment. A peer-identity gate
8647
- * (e.g. Trust-Wallet-only) is deferred until we have a real pairing log to
8648
- * write it against.
8649
- *
8650
- * `wc` is opaque here — we don't import the WalletConnect package directly
8651
- * to avoid forcing wallet-core to depend on it. The caller (typically the
8652
- * MCP action provider) constructs the WC provider and passes it in.
8702
+ * V1 does not restrict the peer wallet: any WC v2 client can pair. The
8703
+ * user's choice to scan the QR is the consent moment.
8653
8704
  */
8654
- async beginWalletConnect(wc3, chains) {
8655
- return this.enqueue(() => this.beginWalletConnectImpl(wc3, chains));
8705
+ async beginWalletConnect(chains) {
8706
+ return this.enqueue(() => this.beginWalletConnectImpl(chains));
8656
8707
  }
8657
- async beginWalletConnectImpl(wc3, chains) {
8708
+ async beginWalletConnectImpl(chains) {
8709
+ const wc3 = this.walletConnect();
8710
+ this._generation++;
8658
8711
  await this.disconnectWcIfAny();
8659
8712
  try {
8660
8713
  this._pairing = await wc3.connect(chains);
8661
8714
  } catch (err) {
8662
8715
  this._wallet = void 0;
8663
- this._pendingWc = void 0;
8664
8716
  this._pairing = void 0;
8665
8717
  this._peerName = void 0;
8666
8718
  this._state = "unbound";
8667
8719
  this._lastError = {
8668
8720
  code: "WC_PAIRING_FAILED",
8669
- message: `WalletConnect pairing failed: ${err instanceof Error ? err.message : String(err)}`
8721
+ message: `WalletConnect pairing failed: ${errorMessage(err)}`
8670
8722
  };
8671
8723
  throw err;
8672
8724
  }
8673
8725
  this._wallet = void 0;
8674
- this._pendingWc = wc3;
8675
8726
  this._state = "wc-pairing";
8676
8727
  this._lastError = void 0;
8677
8728
  this._peerName = void 0;
8678
- wc3.onSessionEstablished((session) => {
8679
- if (this._pendingWc !== wc3) return;
8729
+ return this.snapshot().pairing;
8730
+ }
8731
+ /**
8732
+ * Bind the WC session persisted by a previous process, if any. Returns
8733
+ * true and moves to `wc-connected` when the provider finds a live session;
8734
+ * leaves the context untouched otherwise (and when WC is not configured).
8735
+ *
8736
+ * Runs outside the mode-switch queue so a switch made meanwhile is never
8737
+ * blocked; that switch wins (the late session is not bound) and, being
8738
+ * explicit, revokes the session through the shared provider.
8739
+ */
8740
+ async restoreWalletConnect() {
8741
+ if (!this.walletConnectAvailable) return false;
8742
+ const generation = this._generation;
8743
+ const wc3 = this.walletConnect();
8744
+ const session = await wc3.restore();
8745
+ return this.enqueue(async () => {
8746
+ if (!session || this._generation !== generation) return false;
8680
8747
  this._wallet = wc3;
8748
+ this._pairing = void 0;
8681
8749
  this._peerName = session.peerName;
8682
8750
  this._state = "wc-connected";
8683
- this._pendingWc = void 0;
8684
- this._pairing = void 0;
8685
- });
8686
- wc3.onSessionEnded((reason) => {
8687
- if (this._wallet !== wc3) return;
8688
- this._wallet = void 0;
8689
- this._pairing = void 0;
8690
- this._peerName = void 0;
8691
- this._state = "unbound";
8692
- this._lastError = {
8693
- code: "WC_NOT_CONNECTED",
8694
- message: `WalletConnect session ended (${reason}). Call switch_wallet_mode to re-pair or switch to local.`
8695
- };
8751
+ this._lastError = void 0;
8752
+ return true;
8696
8753
  });
8697
- return this.snapshot().pairing;
8698
8754
  }
8699
- /** Disconnect the active wallet (best-effort) and return to `unbound`. */
8755
+ /** Revoke any WC session (best-effort) and return to `unbound`. */
8700
8756
  async reset() {
8701
8757
  return this.enqueue(() => this.resetImpl());
8702
8758
  }
8703
8759
  async resetImpl() {
8760
+ this._generation++;
8704
8761
  await this.disconnectWcIfAny();
8705
8762
  this._wallet = void 0;
8706
- this._pendingWc = void 0;
8707
8763
  this._pairing = void 0;
8708
8764
  this._peerName = void 0;
8709
8765
  this._state = "unbound";
@@ -9317,9 +9373,9 @@ var require_cjs3 = __commonJS({
9317
9373
  if (href.indexOf("/") === 0) {
9318
9374
  absoluteHref += href;
9319
9375
  } else {
9320
- const path5 = loc.pathname.split("/");
9321
- path5.pop();
9322
- const finalPath = path5.join("/");
9376
+ const path7 = loc.pathname.split("/");
9377
+ path7.pop();
9378
+ const finalPath = path7.join("/");
9323
9379
  absoluteHref += finalPath + "/" + href;
9324
9380
  }
9325
9381
  icons2.push(absoluteHref);
@@ -12179,7 +12235,7 @@ var require_websocket = __commonJS({
12179
12235
  var http2 = require("http");
12180
12236
  var net = require("net");
12181
12237
  var tls = require("tls");
12182
- var { randomBytes: randomBytes7, createHash: createHash2 } = require("crypto");
12238
+ var { randomBytes: randomBytes8, createHash: createHash2 } = require("crypto");
12183
12239
  var { Readable } = require("stream");
12184
12240
  var { URL: URL2 } = require("url");
12185
12241
  var PerMessageDeflate = require_permessage_deflate();
@@ -12639,7 +12695,7 @@ var require_websocket = __commonJS({
12639
12695
  }
12640
12696
  const isSecure = parsedUrl.protocol === "wss:" || parsedUrl.protocol === "https:";
12641
12697
  const defaultPort = isSecure ? 443 : 80;
12642
- const key = randomBytes7(16).toString("base64");
12698
+ const key = randomBytes8(16).toString("base64");
12643
12699
  const get2 = isSecure ? https2.get : http2.get;
12644
12700
  let perMessageDeflate;
12645
12701
  opts.createConnection = isSecure ? tlsConnect : netConnect;
@@ -16261,15 +16317,15 @@ var dapps_service_exports = {};
16261
16317
  __export(dapps_service_exports, {
16262
16318
  DappsService: () => DappsService
16263
16319
  });
16264
- var fs5, path3, os3, CACHE_FILE, CACHE_TTL_MS, DappsService;
16320
+ var fs7, path5, os3, CACHE_FILE, CACHE_TTL_MS, DappsService;
16265
16321
  var init_dapps_service = __esm({
16266
16322
  "../security/dist/services/dapps-service.mjs"() {
16267
16323
  "use strict";
16268
- fs5 = __toESM(require("fs"), 1);
16269
- path3 = __toESM(require("path"), 1);
16324
+ fs7 = __toESM(require("fs"), 1);
16325
+ path5 = __toESM(require("path"), 1);
16270
16326
  os3 = __toESM(require("os"), 1);
16271
16327
  init_dist();
16272
- CACHE_FILE = path3.join(os3.homedir(), ".twak", "dapps-cache.json");
16328
+ CACHE_FILE = path5.join(os3.homedir(), ".twak", "dapps-cache.json");
16273
16329
  CACHE_TTL_MS = 6 * 36e5;
16274
16330
  DappsService = class {
16275
16331
  protocols = null;
@@ -16286,9 +16342,7 @@ var init_dapps_service = __esm({
16286
16342
  signal: AbortSignal.timeout(15e3)
16287
16343
  });
16288
16344
  } catch (e2) {
16289
- throw new TwakNetworkError(
16290
- `DeFi Llama unreachable: ${e2 instanceof Error ? e2.message : String(e2)}`
16291
- );
16345
+ throw new TwakNetworkError(`DeFi Llama unreachable: ${errorMessage(e2)}`);
16292
16346
  }
16293
16347
  if (!resp.ok) {
16294
16348
  throw new TwakApiError(`DeFi Llama API error: ${resp.status}`, resp.status);
@@ -16337,7 +16391,7 @@ var init_dapps_service = __esm({
16337
16391
  }
16338
16392
  readCache() {
16339
16393
  try {
16340
- const raw = fs5.readFileSync(CACHE_FILE, "utf-8");
16394
+ const raw = fs7.readFileSync(CACHE_FILE, "utf-8");
16341
16395
  const data = JSON.parse(raw);
16342
16396
  if (Date.now() - data.fetchedAt < CACHE_TTL_MS) {
16343
16397
  return data.protocols;
@@ -16348,10 +16402,10 @@ var init_dapps_service = __esm({
16348
16402
  }
16349
16403
  writeCache(protocols) {
16350
16404
  try {
16351
- const dir = path3.dirname(CACHE_FILE);
16352
- fs5.mkdirSync(dir, { recursive: true });
16405
+ const dir = path5.dirname(CACHE_FILE);
16406
+ fs7.mkdirSync(dir, { recursive: true });
16353
16407
  const data = { fetchedAt: Date.now(), protocols };
16354
- fs5.writeFileSync(CACHE_FILE, JSON.stringify(data));
16408
+ fs7.writeFileSync(CACHE_FILE, JSON.stringify(data));
16355
16409
  } catch {
16356
16410
  }
16357
16411
  }
@@ -19552,8 +19606,8 @@ var require_utils2 = __commonJS({
19552
19606
  }
19553
19607
  return ind;
19554
19608
  }
19555
- function removeDotSegments(path5) {
19556
- let input2 = path5;
19609
+ function removeDotSegments(path7) {
19610
+ let input2 = path7;
19557
19611
  const output2 = [];
19558
19612
  let nextSlash = -1;
19559
19613
  let len = 0;
@@ -19752,8 +19806,8 @@ var require_schemes = __commonJS({
19752
19806
  wsComponent.secure = void 0;
19753
19807
  }
19754
19808
  if (wsComponent.resourceName) {
19755
- const [path5, query] = wsComponent.resourceName.split("?");
19756
- wsComponent.path = path5 && path5 !== "/" ? path5 : void 0;
19809
+ const [path7, query] = wsComponent.resourceName.split("?");
19810
+ wsComponent.path = path7 && path7 !== "/" ? path7 : void 0;
19757
19811
  wsComponent.query = query;
19758
19812
  wsComponent.resourceName = void 0;
19759
19813
  }
@@ -23115,12 +23169,12 @@ var require_dist = __commonJS({
23115
23169
  throw new Error(`Unknown format "${name2}"`);
23116
23170
  return f3;
23117
23171
  };
23118
- function addFormats(ajv, list, fs7, exportName) {
23172
+ function addFormats(ajv, list, fs10, exportName) {
23119
23173
  var _a2;
23120
23174
  var _b;
23121
23175
  (_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
23122
23176
  for (const f3 of list)
23123
- ajv.addFormat(f3, fs7[f3]);
23177
+ ajv.addFormat(f3, fs10[f3]);
23124
23178
  }
23125
23179
  module2.exports = exports2 = formatsPlugin;
23126
23180
  Object.defineProperty(exports2, "__esModule", { value: true });
@@ -24660,12 +24714,12 @@ var init_debug = __esm({
24660
24714
  };
24661
24715
  };
24662
24716
  };
24663
- getValueByStringPath = function(obj, path5) {
24717
+ getValueByStringPath = function(obj, path7) {
24664
24718
  var e_1, _a2;
24665
- path5 = path5.replace(/\[(\w+)\]/g, ".$1");
24666
- path5 = path5.replace(/^\./, "");
24719
+ path7 = path7.replace(/\[(\w+)\]/g, ".$1");
24720
+ path7 = path7.replace(/^\./, "");
24667
24721
  try {
24668
- for (var _b = __values2(path5.split(".")), _c2 = _b.next(); !_c2.done; _c2 = _b.next()) {
24722
+ for (var _b = __values2(path7.split(".")), _c2 = _b.next(); !_c2.done; _c2 = _b.next()) {
24669
24723
  var attr = _c2.value;
24670
24724
  if (attr in obj) {
24671
24725
  obj = obj[attr];
@@ -24690,8 +24744,8 @@ var init_debug = __esm({
24690
24744
  var res = {};
24691
24745
  try {
24692
24746
  for (var paths_1 = __values2(paths), paths_1_1 = paths_1.next(); !paths_1_1.done; paths_1_1 = paths_1.next()) {
24693
- var path5 = paths_1_1.value;
24694
- res[path5] = getValueByStringPath(client, path5);
24747
+ var path7 = paths_1_1.value;
24748
+ res[path7] = getValueByStringPath(client, path7);
24695
24749
  }
24696
24750
  } catch (e_2_1) {
24697
24751
  e_2 = { error: e_2_1 };
@@ -24951,7 +25005,7 @@ var init_destination = __esm({
24951
25005
  useRetry = true;
24952
25006
  }
24953
25007
  return __awaiter2(this, void 0, void 0, function() {
24954
- var payload, serverUrl, res, e_1, errorMessage;
25008
+ var payload, serverUrl, res, e_1, errorMessage2;
24955
25009
  return __generator2(this, function(_b) {
24956
25010
  switch (_b.label) {
24957
25011
  case 0:
@@ -25000,13 +25054,13 @@ var init_destination = __esm({
25000
25054
  return [3, 4];
25001
25055
  case 3:
25002
25056
  e_1 = _b.sent();
25003
- errorMessage = getErrorMessage(e_1);
25004
- this.config.loggerProvider.error(errorMessage);
25057
+ errorMessage2 = getErrorMessage(e_1);
25058
+ this.config.loggerProvider.error(errorMessage2);
25005
25059
  (_a2 = this.diagnosticsClient) === null || _a2 === void 0 ? void 0 : _a2.recordEvent("analytics.events.unsuccessful.from.catch.error", {
25006
25060
  events: list.map(function(context) {
25007
25061
  return context.event.event_type;
25008
25062
  }),
25009
- message: errorMessage,
25063
+ message: errorMessage2,
25010
25064
  stack_trace: getStacktrace()
25011
25065
  });
25012
25066
  this.handleResponse({ status: Status.Failed, statusCode: 0 }, list);
@@ -25638,7 +25692,7 @@ function classifyError(err) {
25638
25692
  if (err instanceof CommandError) return err;
25639
25693
  const cls = classifyApiError(err) ?? classifyStorageError(err) ?? classifyWalletLoadError(err);
25640
25694
  if (cls) return new CommandError(cls.code, cls.message);
25641
- return new CommandError("UNKNOWN_ERROR", err instanceof Error ? err.message : String(err));
25695
+ return new CommandError("UNKNOWN_ERROR", errorMessage(err));
25642
25696
  }
25643
25697
  function handleCommandError(err, json, fallbackPrefix) {
25644
25698
  const classified = classifyError(err);
@@ -25670,7 +25724,7 @@ var import_chalk2 = __toESM(require_source());
25670
25724
  init_dist();
25671
25725
  init_dist3();
25672
25726
  init_dist2();
25673
- var EMBEDDED_NAAS_SESSION_ID = "Nr8eogRKLULRxGpVkeMqQBoDpGnZxgYjKK7tu6bKxqUn6eZU";
25727
+ var EMBEDDED_NAAS_SESSION_ID = "NDVjMWRjY2MtYTYwNy00NGNhLWIxZGUtMmJiZDIyNmEyODQ4";
25674
25728
  function resolveNaasSessionId() {
25675
25729
  if (process.env.TWAK_NAAS_SESSION_ID) return process.env.TWAK_NAAS_SESSION_ID;
25676
25730
  if (EMBEDDED_NAAS_SESSION_ID && !EMBEDDED_NAAS_SESSION_ID.startsWith("__"))
@@ -31522,7 +31576,7 @@ function weierstrass(curveDef) {
31522
31576
  function prepSig(msgHash, privateKey, opts = defaultSigOpts) {
31523
31577
  if (["recovered", "canonical"].some((k4) => k4 in opts))
31524
31578
  throw new Error("sign() legacy options not supported");
31525
- const { hash, randomBytes: randomBytes7 } = CURVE;
31579
+ const { hash, randomBytes: randomBytes8 } = CURVE;
31526
31580
  let { lowS, prehash, extraEntropy: ent } = opts;
31527
31581
  if (lowS == null)
31528
31582
  lowS = true;
@@ -31534,7 +31588,7 @@ function weierstrass(curveDef) {
31534
31588
  const d3 = normPrivateKeyToScalar(privateKey);
31535
31589
  const seedArgs = [int2octets(d3), int2octets(h1int)];
31536
31590
  if (ent != null && ent !== false) {
31537
- const e2 = ent === true ? randomBytes7(Fp.BYTES) : ent;
31591
+ const e2 = ent === true ? randomBytes8(Fp.BYTES) : ent;
31538
31592
  seedArgs.push(ensureBytes("extraEntropy", e2));
31539
31593
  }
31540
31594
  const seed = concatBytes2(...seedArgs);
@@ -41568,6 +41622,10 @@ var Os3 = ut4;
41568
41622
  // ../walletconnect/dist/index.mjs
41569
41623
  init_dist3();
41570
41624
  init_dist();
41625
+ var fs5 = __toESM(require("fs"), 1);
41626
+ var path3 = __toESM(require("path"), 1);
41627
+ var crypto7 = __toESM(require("crypto"), 1);
41628
+ init_dist();
41571
41629
  var __require2 = /* @__PURE__ */ ((x6) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x6, {
41572
41630
  get: (a3, b3) => (typeof require !== "undefined" ? require : a3)[b3]
41573
41631
  }) : x6)(function(x6) {
@@ -41586,12 +41644,111 @@ async function renderPngDataUrl(uri) {
41586
41644
  const qrcode = __require2("qrcode");
41587
41645
  return qrcode.toDataURL(uri, { errorCorrectionLevel: "M" });
41588
41646
  }
41647
+ var FileKeyValueStorage = class {
41648
+ constructor(filePath, opts = {}) {
41649
+ this.filePath = filePath;
41650
+ this.opts = opts;
41651
+ }
41652
+ cache = null;
41653
+ warn(msg) {
41654
+ this.opts.onWarning?.(msg);
41655
+ }
41656
+ readFile() {
41657
+ let raw;
41658
+ try {
41659
+ raw = fs5.readFileSync(this.filePath, "utf-8");
41660
+ } catch (err) {
41661
+ if (err.code !== "ENOENT") {
41662
+ this.warn(`Could not read ${this.filePath}: ${errorMessage(err)}`);
41663
+ }
41664
+ return /* @__PURE__ */ new Map();
41665
+ }
41666
+ try {
41667
+ return new Map(Object.entries(JSON.parse(raw)));
41668
+ } catch {
41669
+ const corruptPath = `${this.filePath}.corrupt`;
41670
+ try {
41671
+ fs5.renameSync(this.filePath, corruptPath);
41672
+ this.warn(`WalletConnect storage file was corrupt; moved it to ${corruptPath}`);
41673
+ } catch (err) {
41674
+ this.warn(
41675
+ `WalletConnect storage file is corrupt and could not be moved: ${errorMessage(err)}`
41676
+ );
41677
+ }
41678
+ return /* @__PURE__ */ new Map();
41679
+ }
41680
+ }
41681
+ load() {
41682
+ if (!this.cache) this.cache = this.readFile();
41683
+ return this.cache;
41684
+ }
41685
+ persist() {
41686
+ const snapshot = JSON.stringify(Object.fromEntries(this.load()));
41687
+ const tmpPath = `${this.filePath}.tmp.${crypto7.randomBytes(8).toString("hex")}`;
41688
+ try {
41689
+ fs5.mkdirSync(path3.dirname(this.filePath), { recursive: true, mode: 448 });
41690
+ fs5.writeFileSync(tmpPath, snapshot, { encoding: "utf-8", mode: 384 });
41691
+ fs5.renameSync(tmpPath, this.filePath);
41692
+ } catch (err) {
41693
+ try {
41694
+ fs5.rmSync(tmpPath, { force: true });
41695
+ } catch {
41696
+ }
41697
+ this.warn(`Could not save WalletConnect session to ${this.filePath}: ${errorMessage(err)}`);
41698
+ }
41699
+ }
41700
+ async getKeys() {
41701
+ return [...this.load().keys()];
41702
+ }
41703
+ async getEntries() {
41704
+ return [...this.load().entries()];
41705
+ }
41706
+ async getItem(key) {
41707
+ return this.load().get(key);
41708
+ }
41709
+ async setItem(key, value) {
41710
+ this.load().set(key, value);
41711
+ this.persist();
41712
+ }
41713
+ async removeItem(key) {
41714
+ if (this.load().delete(key)) this.persist();
41715
+ }
41716
+ };
41589
41717
  var DEFAULT_METADATA = {
41590
- name: "TWAK Agent",
41591
- description: "AI Agent for Trust Wallet operations",
41718
+ name: "TWAK",
41719
+ description: "Trust Wallet Agent Kit",
41592
41720
  url: "https://portal.trustwallet.com",
41593
- icons: ["https://avatars.githubusercontent.com/u/32179365"]
41721
+ icons: ["https://avatars.githubusercontent.com/u/32179889"]
41594
41722
  };
41723
+ var SESSION_CLEARED_REASON = { code: 6e3, message: "Session cleared" };
41724
+ var PEER_NOTIFY_TIMEOUT_MS = 5e3;
41725
+ var USER_REJECTION_CODES = /* @__PURE__ */ new Set([5e3, 4001]);
41726
+ var GENERIC_SERVER_ERROR_CODE = -32e3;
41727
+ var USER_REJECTION_MESSAGE_RE = /\buser (cancell?ed|rejected|denied)\b/i;
41728
+ function isUserRejection(code2, message) {
41729
+ if (USER_REJECTION_CODES.has(code2)) return true;
41730
+ return code2 === GENERIC_SERVER_ERROR_CODE && USER_REJECTION_MESSAGE_RE.test(message);
41731
+ }
41732
+ function toRequestError(err) {
41733
+ if (err instanceof Error || !err || typeof err !== "object") return err;
41734
+ const { code: code2, message } = err;
41735
+ if (typeof code2 !== "number" || typeof message !== "string") return err;
41736
+ if (isUserRejection(code2, message)) {
41737
+ return new WalletError("USER_REJECTED", `Request rejected in the wallet: ${message}`);
41738
+ }
41739
+ return new WalletError("WC_REQUEST_FAILED", `${message} (code ${code2})`);
41740
+ }
41741
+ async function settleWithin(promise, ms3) {
41742
+ let timer;
41743
+ const elapsed = new Promise((resolve2) => {
41744
+ timer = setTimeout(resolve2, ms3);
41745
+ });
41746
+ try {
41747
+ await Promise.race([promise.catch(() => void 0), elapsed]);
41748
+ } finally {
41749
+ clearTimeout(timer);
41750
+ }
41751
+ }
41595
41752
  var WC_NOT_CONNECTED_MSG = 'WalletConnect session not established. If pairing is in progress, complete the approval in Trust Wallet mobile and retry; otherwise call switch_wallet_mode({mode: "walletconnect"}) to start pairing.';
41596
41753
  var WalletConnectProvider = class extends WalletProvider {
41597
41754
  name = "walletconnect";
@@ -41599,12 +41756,17 @@ var WalletConnectProvider = class extends WalletProvider {
41599
41756
  initPromise = null;
41600
41757
  currentSession = null;
41601
41758
  config;
41759
+ storage;
41760
+ /** Bumped by each pairing request and by disconnect(); an approval that
41761
+ * arrives for an older pairing is revoked instead of bound. */
41762
+ pairingGeneration = 0;
41602
41763
  sessionCallback = null;
41603
41764
  sessionEndedCallback = null;
41604
41765
  pairingEventCallback = null;
41605
41766
  constructor(config) {
41606
41767
  super();
41607
41768
  this.config = config;
41769
+ this.storage = config.storagePath ? new FileKeyValueStorage(config.storagePath, { onWarning: config.onStorageWarning }) : null;
41608
41770
  }
41609
41771
  async ensureClient() {
41610
41772
  if (this.client) return this.client;
@@ -41612,12 +41774,26 @@ var WalletConnectProvider = class extends WalletProvider {
41612
41774
  this.initPromise = Os3.init({
41613
41775
  projectId: this.config.projectId,
41614
41776
  relayUrl: this.config.relayUrl || "wss://relay.walletconnect.com",
41615
- metadata: this.config.metadata || DEFAULT_METADATA
41777
+ metadata: this.config.metadata || DEFAULT_METADATA,
41778
+ ...this.storage ? { storage: this.storage } : {}
41616
41779
  });
41617
- this.client = await this.initPromise;
41780
+ try {
41781
+ this.client = await this.initPromise;
41782
+ } catch (err) {
41783
+ this.initPromise = null;
41784
+ throw err;
41785
+ }
41618
41786
  this.setupEventHandlers(this.client);
41619
41787
  return this.client;
41620
41788
  }
41789
+ /** `client.request` with wallet-side errors converted (see toRequestError). */
41790
+ async request(client, args) {
41791
+ try {
41792
+ return await client.request(args);
41793
+ } catch (err) {
41794
+ throw toRequestError(err);
41795
+ }
41796
+ }
41621
41797
  onPairingEvent(callback) {
41622
41798
  this.pairingEventCallback = callback;
41623
41799
  }
@@ -41704,7 +41880,7 @@ var WalletConnectProvider = class extends WalletProvider {
41704
41880
  if (!session) throw new WalletError("WC_NOT_CONNECTED", WC_NOT_CONNECTED_MSG);
41705
41881
  const client = await this.ensureClient();
41706
41882
  const hexMessage = `0x${Buffer.from(message, "utf8").toString("hex")}`;
41707
- return client.request({
41883
+ return this.request(client, {
41708
41884
  topic: session.topic,
41709
41885
  chainId,
41710
41886
  request: {
@@ -41718,7 +41894,7 @@ var WalletConnectProvider = class extends WalletProvider {
41718
41894
  if (!session) throw new WalletError("WC_NOT_CONNECTED", WC_NOT_CONNECTED_MSG);
41719
41895
  const client = await this.ensureClient();
41720
41896
  const caipChainId = chainKey.includes(":") ? chainKey : `eip155:${chainId}`;
41721
- return client.request({
41897
+ return this.request(client, {
41722
41898
  topic: session.topic,
41723
41899
  chainId: caipChainId,
41724
41900
  request: {
@@ -41742,7 +41918,7 @@ var WalletConnectProvider = class extends WalletProvider {
41742
41918
  if (value && !value.startsWith("0x")) {
41743
41919
  value = "0x" + BigInt(value).toString(16);
41744
41920
  }
41745
- return client.request({
41921
+ return this.request(client, {
41746
41922
  topic: session.topic,
41747
41923
  chainId: params.chainId,
41748
41924
  request: {
@@ -41763,8 +41939,38 @@ var WalletConnectProvider = class extends WalletProvider {
41763
41939
  supportsChain(chainId) {
41764
41940
  return chainId.startsWith("eip155:");
41765
41941
  }
41942
+ /**
41943
+ * Ends a session in two phases:
41944
+ * 1. Tell the peer wallet (best-effort, bounded by PEER_NOTIFY_TIMEOUT_MS:
41945
+ * the relay may be unreachable and `client.disconnect` then never
41946
+ * settles).
41947
+ * 2. Always drop the session locally if it is still stored, so it never
41948
+ * lingers in persisted storage. If the peer was not reached, it keeps
41949
+ * a dead entry until the session expires.
41950
+ */
41951
+ async revokeSession(client, topic) {
41952
+ await settleWithin(
41953
+ client.disconnect({ topic, reason: SESSION_CLEARED_REASON }),
41954
+ PEER_NOTIFY_TIMEOUT_MS
41955
+ );
41956
+ if (!client.session.getAll().some((s2) => s2.topic === topic)) return;
41957
+ try {
41958
+ await client.session.delete(topic, SESSION_CLEARED_REASON);
41959
+ } catch {
41960
+ }
41961
+ }
41962
+ /**
41963
+ * Starts a new pairing. Only one session is kept per storage, so any
41964
+ * existing session is revoked first: asking to pair again expresses the
41965
+ * intent to invalidate the previous one.
41966
+ */
41766
41967
  async connect(chains = getDefaultWcChains()) {
41767
41968
  const client = await this.ensureClient();
41969
+ const generation = ++this.pairingGeneration;
41970
+ for (const session of client.session.getAll()) {
41971
+ await this.revokeSession(client, session.topic);
41972
+ }
41973
+ this.currentSession = null;
41768
41974
  const nsMap = {};
41769
41975
  for (const chain2 of chains) {
41770
41976
  const ns2 = chain2.split(":")[0];
@@ -41786,7 +41992,7 @@ var WalletConnectProvider = class extends WalletProvider {
41786
41992
  "WalletConnect relay handshake failed: no pairing URI returned. Check network and project ID, then retry."
41787
41993
  );
41788
41994
  }
41789
- this.handleApproval(approval());
41995
+ this.handleApproval(approval(), generation);
41790
41996
  const encodedUri = encodeURIComponent(uri);
41791
41997
  const terminal = renderTerminalQR(uri);
41792
41998
  const imageUrl = await renderPngDataUrl(uri);
@@ -41803,9 +42009,13 @@ var WalletConnectProvider = class extends WalletProvider {
41803
42009
  expiresAt: Date.now() + 5 * 60 * 1e3
41804
42010
  };
41805
42011
  }
41806
- async handleApproval(approval) {
42012
+ async handleApproval(approval, generation) {
41807
42013
  try {
41808
42014
  const session = await approval;
42015
+ if (generation !== this.pairingGeneration) {
42016
+ if (this.client) await this.revokeSession(this.client, session.topic);
42017
+ return;
42018
+ }
41809
42019
  this.currentSession = this.sessionToWalletSession(session);
41810
42020
  if (this.currentSession && this.sessionCallback) {
41811
42021
  this.sessionCallback(this.currentSession);
@@ -41832,20 +42042,42 @@ var WalletConnectProvider = class extends WalletProvider {
41832
42042
  `Timed out after ${Math.round(timeoutMs / 1e3)}s waiting for WalletConnect pairing approval.`
41833
42043
  );
41834
42044
  }
42045
+ /**
42046
+ * Revokes every stored session. Waits for an in-flight client init (e.g. a
42047
+ * restore still loading storage) so a session being restored is revoked
42048
+ * too; a no-op when the client was never started.
42049
+ */
41835
42050
  async disconnect() {
41836
- if (!this.client) return;
41837
- const sessions = this.client.session.getAll();
41838
- for (const session of sessions) {
42051
+ this.pairingGeneration++;
42052
+ let client = this.client;
42053
+ if (!client && this.initPromise) {
41839
42054
  try {
41840
- await this.client.disconnect({
41841
- topic: session.topic,
41842
- reason: { code: 6e3, message: "Session cleared" }
41843
- });
42055
+ client = await this.initPromise;
41844
42056
  } catch {
42057
+ return;
41845
42058
  }
41846
42059
  }
42060
+ if (!client) return;
42061
+ for (const session of client.session.getAll()) {
42062
+ await this.revokeSession(client, session.topic);
42063
+ }
41847
42064
  this.currentSession = null;
41848
42065
  }
42066
+ /**
42067
+ * Resumes the session persisted by a previous process. Keeps the live
42068
+ * session with the latest expiry and revokes any other, so at most one
42069
+ * session remains. Returns null when none is live.
42070
+ */
42071
+ async restore() {
42072
+ const client = await this.ensureClient();
42073
+ const now = Math.floor(Date.now() / 1e3);
42074
+ const live = client.session.getAll().filter((s2) => s2.expiry > now).sort((a3, b3) => b3.expiry - a3.expiry);
42075
+ for (const stale of live.slice(1)) {
42076
+ await this.revokeSession(client, stale.topic);
42077
+ }
42078
+ this.currentSession = live.length > 0 ? this.sessionToWalletSession(live[0]) : null;
42079
+ return this.currentSession;
42080
+ }
41849
42081
  isConnected() {
41850
42082
  return this.getActiveSession() !== null;
41851
42083
  }
@@ -41961,7 +42193,10 @@ async function tryRegisterWallet() {
41961
42193
  }
41962
42194
  }
41963
42195
  function registerInitCommand(program3) {
41964
- program3.command("init").description("Initialize twak configuration").option("--api-key <key>", "TWAK API access ID (prefer TWAK_ACCESS_ID env var)").option("--api-secret <secret>", "TWAK HMAC secret (prefer TWAK_HMAC_SECRET env var)").option("--wc-project-id <id>", "WalletConnect project ID").option("--json", "JSON output").action(async (opts) => {
42196
+ program3.command("init").description("Initialize twak configuration").option("--api-key <key>", "TWAK API access ID (prefer TWAK_ACCESS_ID env var)").option("--api-secret <secret>", "TWAK HMAC secret (prefer TWAK_HMAC_SECRET env var)").option(
42197
+ "--wc-project-id <id>",
42198
+ "WalletConnect project ID override (default: Trust Wallet project)"
42199
+ ).option("--json", "JSON output").action(async (opts) => {
41965
42200
  try {
41966
42201
  const credManager = new CredentialManager();
41967
42202
  if (opts.apiKey && opts.apiSecret) {
@@ -42058,18 +42293,18 @@ var import_promises2 = require("fs/promises");
42058
42293
  var import_node_crypto = require("crypto");
42059
42294
  var import_promises = require("fs/promises");
42060
42295
  var import_node_path = require("path");
42061
- async function writeAtomic(path5, contents, mode = 420) {
42062
- await (0, import_promises.mkdir)((0, import_node_path.dirname)(path5), { recursive: true });
42296
+ async function writeAtomic(path7, contents, mode = 420) {
42297
+ await (0, import_promises.mkdir)((0, import_node_path.dirname)(path7), { recursive: true });
42063
42298
  let effectiveMode = mode;
42064
42299
  try {
42065
- const st4 = await (0, import_promises.stat)(path5);
42300
+ const st4 = await (0, import_promises.stat)(path7);
42066
42301
  effectiveMode = st4.mode & 511;
42067
42302
  } catch {
42068
42303
  }
42069
- const tmpPath = `${path5}.tmp.${(0, import_node_crypto.randomBytes)(8).toString("hex")}`;
42304
+ const tmpPath = `${path7}.tmp.${(0, import_node_crypto.randomBytes)(8).toString("hex")}`;
42070
42305
  await (0, import_promises.writeFile)(tmpPath, contents, { mode: effectiveMode });
42071
42306
  try {
42072
- await (0, import_promises.rename)(tmpPath, path5);
42307
+ await (0, import_promises.rename)(tmpPath, path7);
42073
42308
  } catch (err) {
42074
42309
  try {
42075
42310
  await (0, import_promises.rm)(tmpPath, { force: true });
@@ -42326,8 +42561,8 @@ async function runInteractive(deps) {
42326
42561
  const hmacSecret = (await deps.prompter.promptHmacSecret()).trim();
42327
42562
  info(
42328
42563
  `
42329
- WalletConnect Project ID is only used when signing via WalletConnect in MCP mode.
42330
- Skip if you only plan to use the local agent wallet. Get one at: ${WC_DASHBOARD_URL}
42564
+ WalletConnect Project ID is optional: twak uses Trust Wallet's project by default.
42565
+ Set one only to use your own project. Get one at: ${WC_DASHBOARD_URL}
42331
42566
  `
42332
42567
  );
42333
42568
  const walletConnectProjectId = (await deps.prompter.promptWalletConnectProjectId())?.trim() || void 0;
@@ -42481,7 +42716,7 @@ async function processHarness(writer, deps, verbose, entry) {
42481
42716
  try {
42482
42717
  current = await writer.getCurrentEntry();
42483
42718
  } catch (err) {
42484
- const msg = err instanceof Error ? err.message : String(err);
42719
+ const msg = errorMessage(err);
42485
42720
  error(
42486
42721
  `Skipping ${writer.displayName}: config at ${writer.configPath()} is invalid (${msg}). Fix it manually and re-run \`twak setup --harness ${writer.name}\`.`
42487
42722
  );
@@ -42535,7 +42770,7 @@ async function runSkillsInstall(harness, deps) {
42535
42770
  await deps.skillsInstaller.install(agent);
42536
42771
  success(` Skills installed for ${harness}.`);
42537
42772
  } catch (err) {
42538
- const msg = err instanceof Error ? err.message : String(err);
42773
+ const msg = errorMessage(err);
42539
42774
  warn(
42540
42775
  ` Skills install for ${harness} failed (${msg}). The MCP wiring is in place; you can re-run \`npx skills add trustwallet/tw-agent-skills -a ${agent}\` manually.`
42541
42776
  );
@@ -42567,7 +42802,7 @@ async function runNonInteractive2(opts, deps) {
42567
42802
  try {
42568
42803
  current = await writer.getCurrentEntry();
42569
42804
  } catch (err) {
42570
- const msg = err instanceof Error ? err.message : String(err);
42805
+ const msg = errorMessage(err);
42571
42806
  throw new CommandError(
42572
42807
  "VALIDATION_ERROR",
42573
42808
  `Cannot wire ${writer.displayName}: config at ${writer.configPath()} is invalid (${msg}). Fix it manually and re-run.`
@@ -42875,7 +43110,10 @@ var import_chalk4 = __toESM(require_source());
42875
43110
  init_dist2();
42876
43111
  function registerAuthCommand(program3) {
42877
43112
  const auth = program3.command("auth").description("Manage authentication");
42878
- auth.command("setup").description("Set up API credentials").requiredOption("--api-key <key>", "TWAK API access ID").requiredOption("--api-secret <secret>", "TWAK HMAC secret").option("--wc-project-id <id>", "WalletConnect project ID").action(async (opts) => {
43113
+ auth.command("setup").description("Set up API credentials").requiredOption("--api-key <key>", "TWAK API access ID").requiredOption("--api-secret <secret>", "TWAK HMAC secret").option(
43114
+ "--wc-project-id <id>",
43115
+ "WalletConnect project ID override (default: Trust Wallet project)"
43116
+ ).action(async (opts) => {
42879
43117
  const credManager = new CredentialManager();
42880
43118
  await credManager.save({
42881
43119
  accessId: opts.apiKey,
@@ -42894,7 +43132,9 @@ function registerAuthCommand(program3) {
42894
43132
  if (opts.json) {
42895
43133
  output({ configured, source, accessId }, true);
42896
43134
  } else {
42897
- console.log(` ${import_chalk4.default.dim("Status")} ${configured ? import_chalk4.default.green("configured") : import_chalk4.default.yellow("not configured")}`);
43135
+ console.log(
43136
+ ` ${import_chalk4.default.dim("Status")} ${configured ? import_chalk4.default.green("configured") : import_chalk4.default.yellow("not configured")}`
43137
+ );
42898
43138
  console.log(` ${import_chalk4.default.dim("Source")} ${source}`);
42899
43139
  if (accessId) console.log(` ${import_chalk4.default.dim("Access ID")} ${accessId}...`);
42900
43140
  if (!configured) info("\nRun `twak init` to set up credentials.");
@@ -42903,6 +43143,7 @@ function registerAuthCommand(program3) {
42903
43143
  }
42904
43144
 
42905
43145
  // src/commands/wallet.ts
43146
+ var fs6 = __toESM(require("fs"));
42906
43147
  init_dist2();
42907
43148
  var import_chalk6 = __toESM(require_source());
42908
43149
  init_dist();
@@ -42930,7 +43171,7 @@ function mapBroadcastError(err) {
42930
43171
  throw new TransferError("BROADCAST_FAILED", err.message);
42931
43172
  }
42932
43173
  }
42933
- const msg = err instanceof Error ? err.message : String(err);
43174
+ const msg = errorMessage(err);
42934
43175
  if (/\binsufficient\b/i.test(msg)) {
42935
43176
  throw new TransferError("INSUFFICIENT_BALANCE", msg);
42936
43177
  }
@@ -42961,7 +43202,7 @@ var TransferExecutor = class {
42961
43202
  return {
42962
43203
  success: false,
42963
43204
  code: "UNKNOWN_ERROR",
42964
- message: err instanceof Error ? err.message : String(err)
43205
+ message: errorMessage(err)
42965
43206
  };
42966
43207
  }
42967
43208
  }
@@ -42981,7 +43222,7 @@ var TransferExecutor = class {
42981
43222
  try {
42982
43223
  resolvedTo = await resolveAddressOrName(params.to);
42983
43224
  } catch (err) {
42984
- const msg = err instanceof Error ? err.message : String(err);
43225
+ const msg = errorMessage(err);
42985
43226
  if (msg.includes("Could not resolve name")) {
42986
43227
  throw new TransferError("ENS_NOT_FOUND", `Could not resolve ENS name: ${params.to}`);
42987
43228
  }
@@ -43122,7 +43363,7 @@ function parseInterval(value) {
43122
43363
  return num;
43123
43364
  }
43124
43365
  function errMsg(err) {
43125
- return err instanceof Error ? err.message : String(err);
43366
+ return errorMessage(err);
43126
43367
  }
43127
43368
  async function fetchPriceMap(client, assetIds) {
43128
43369
  const assets = [...new Set(assetIds)];
@@ -43572,6 +43813,47 @@ function box(content) {
43572
43813
  });
43573
43814
  }
43574
43815
 
43816
+ // src/walletconnect-config.ts
43817
+ var path4 = __toESM(require("path"));
43818
+ init_dist();
43819
+ init_dist2();
43820
+ var RESTORE_TIMEOUT_MS = 1e4;
43821
+ function wcStoragePath() {
43822
+ return path4.join(new FileStore().getDataDir(), "walletconnect.json");
43823
+ }
43824
+ var DEFAULT_WC_PROJECT_ID = "a863b78b54c4222603c8db4d6d880172";
43825
+ async function resolveWcProjectId(flag) {
43826
+ const fromFlagOrEnv = flag || process.env.WALLETCONNECT_PROJECT_ID;
43827
+ if (fromFlagOrEnv) return fromFlagOrEnv;
43828
+ try {
43829
+ const creds = await new CredentialManager().load();
43830
+ return creds?.walletConnectProjectId || DEFAULT_WC_PROJECT_ID;
43831
+ } catch {
43832
+ return DEFAULT_WC_PROJECT_ID;
43833
+ }
43834
+ }
43835
+ function createWalletConnectProvider(projectId) {
43836
+ return new WalletConnectProvider({
43837
+ projectId,
43838
+ storagePath: wcStoragePath(),
43839
+ onStorageWarning: (msg) => warn(msg)
43840
+ });
43841
+ }
43842
+ async function withTimeout(promise, ms3) {
43843
+ let timer;
43844
+ const timeout = new Promise((_2, reject) => {
43845
+ timer = setTimeout(
43846
+ () => reject(new WalletError("NETWORK_ERROR", `Timed out after ${ms3 / 1e3}s`)),
43847
+ ms3
43848
+ );
43849
+ });
43850
+ try {
43851
+ return await Promise.race([promise, timeout]);
43852
+ } finally {
43853
+ clearTimeout(timer);
43854
+ }
43855
+ }
43856
+
43575
43857
  // src/commands/wallet.ts
43576
43858
  function getQRString(address) {
43577
43859
  try {
@@ -43585,6 +43867,29 @@ function getQRString(address) {
43585
43867
  return "";
43586
43868
  }
43587
43869
  }
43870
+ async function openStoredWalletConnect(projectIdFlag) {
43871
+ if (!fs6.existsSync(wcStoragePath())) return null;
43872
+ const projectId = await resolveWcProjectId(projectIdFlag);
43873
+ const provider = createWalletConnectProvider(projectId);
43874
+ const session = await withTimeout(provider.restore(), RESTORE_TIMEOUT_MS);
43875
+ return { provider, session };
43876
+ }
43877
+ async function getWalletConnectStatus(projectIdFlag) {
43878
+ try {
43879
+ const stored = await openStoredWalletConnect(projectIdFlag);
43880
+ if (!stored?.session) return { connected: false };
43881
+ const { peerName, address, expiry } = stored.session;
43882
+ return {
43883
+ connected: true,
43884
+ peer: peerName,
43885
+ address,
43886
+ expiresAt: new Date(expiry * 1e3).toISOString()
43887
+ };
43888
+ } catch (err) {
43889
+ warn(`Could not check WalletConnect session: ${errorMessage(err)}`);
43890
+ return { connected: false };
43891
+ }
43892
+ }
43588
43893
  function registerWalletCommand(program3) {
43589
43894
  const wallet = program3.command("wallet").description("Manage agent wallet");
43590
43895
  wallet.command("create").description("Create a new agent wallet").requiredOption("--password <password>", "Password to encrypt the wallet").option("--no-keychain", "Do not save password to OS keychain").option("--skip-password-check", "Skip password strength validation").option("--json", "JSON output").action(async (opts) => {
@@ -43864,21 +44169,17 @@ ${qr3}`;
43864
44169
  }
43865
44170
  safeExit();
43866
44171
  });
43867
- wallet.command("connect").description("Connect external wallet via WalletConnect").option("--project-id <id>", "WalletConnect project ID (or set WALLETCONNECT_PROJECT_ID)").option(
44172
+ wallet.command("connect").description("Connect external wallet via WalletConnect").option(
44173
+ "--project-id <id>",
44174
+ "WalletConnect project ID override (default: Trust Wallet project; or WALLETCONNECT_PROJECT_ID / twak init --wc-project-id)"
44175
+ ).option(
43868
44176
  "--chains <list>",
43869
- "Comma-separated CAIP-2 chain IDs",
43870
- "eip155:1,eip155:56,eip155:137,eip155:8453,eip155:42161"
44177
+ "Comma-separated CAIP-2 chain IDs (default: all supported EVM chains)"
43871
44178
  ).option("--timeout <seconds>", "Connection timeout in seconds", "300").option("--json", "JSON output").action(async (opts) => {
43872
44179
  try {
43873
- const projectId = opts.projectId || process.env.WALLETCONNECT_PROJECT_ID;
43874
- if (!projectId) {
43875
- throw new WalletError(
43876
- "VALIDATION_ERROR",
43877
- "WalletConnect project ID required. Use --project-id or set WALLETCONNECT_PROJECT_ID"
43878
- );
43879
- }
43880
- const provider = new WalletConnectProvider({ projectId });
43881
- const chains = opts.chains.split(",").map((s2) => s2.trim()).filter(Boolean);
44180
+ const projectId = await resolveWcProjectId(opts.projectId);
44181
+ const provider = createWalletConnectProvider(projectId);
44182
+ const chains = opts.chains ? opts.chains.split(",").map((s2) => s2.trim()).filter(Boolean) : getDefaultWcChains();
43882
44183
  info("Connecting via WalletConnect...");
43883
44184
  const result = await provider.connect(chains);
43884
44185
  const qr3 = getQRString(result.uri);
@@ -43910,7 +44211,32 @@ ${qr3}`;
43910
44211
  }
43911
44212
  safeExit();
43912
44213
  });
43913
- wallet.command("status").description("Check wallet configuration status").option("--json", "JSON output").action(async (opts) => {
44214
+ wallet.command("disconnect").description("Revoke the stored WalletConnect session").option(
44215
+ "--project-id <id>",
44216
+ "WalletConnect project ID override (default: Trust Wallet project; or WALLETCONNECT_PROJECT_ID / twak init --wc-project-id)"
44217
+ ).option("--json", "JSON output").action(async (opts) => {
44218
+ try {
44219
+ const stored = await openStoredWalletConnect(opts.projectId);
44220
+ const disconnected = !!stored?.session;
44221
+ if (stored?.session) {
44222
+ await withTimeout(stored.provider.disconnect(), RESTORE_TIMEOUT_MS);
44223
+ }
44224
+ if (opts.json) {
44225
+ output({ disconnected }, true);
44226
+ } else if (disconnected) {
44227
+ success("WalletConnect session disconnected");
44228
+ } else {
44229
+ info("No WalletConnect session");
44230
+ }
44231
+ } catch (err) {
44232
+ handleCommandError(err, opts.json, "WalletConnect disconnect failed");
44233
+ }
44234
+ safeExit();
44235
+ });
44236
+ wallet.command("status").description("Check wallet configuration status").option(
44237
+ "--project-id <id>",
44238
+ "WalletConnect project ID override used to check the stored session (or WALLETCONNECT_PROJECT_ID / twak init --wc-project-id)"
44239
+ ).option("--json", "JSON output").action(async (opts) => {
43914
44240
  try {
43915
44241
  const store = new WalletConfigStore();
43916
44242
  const exists = await store.exists();
@@ -43924,13 +44250,15 @@ ${qr3}`;
43924
44250
  createdAt = config?.createdAt;
43925
44251
  addressCount = config?.chains?.length ?? totalChains;
43926
44252
  }
44253
+ const walletConnect = await getWalletConnectStatus(opts.projectId);
43927
44254
  const result = {
43928
44255
  agentWallet: exists ? "configured" : "not configured",
43929
44256
  keychainPassword: keychainHasPassword ? "stored" : "not stored",
43930
44257
  chains: totalChains,
43931
44258
  supportedChains: totalChains,
43932
44259
  ...createdAt ? { createdAt } : {},
43933
- ...addressCount !== void 0 ? { addressCount } : {}
44260
+ ...addressCount !== void 0 ? { addressCount } : {},
44261
+ walletConnect
43934
44262
  };
43935
44263
  if (opts.json) {
43936
44264
  output(result, true);
@@ -43945,6 +44273,11 @@ ${qr3}`;
43945
44273
  console.log(` ${import_chalk6.default.dim("Chains")} ${totalChains} supported`);
43946
44274
  if (addressCount !== void 0)
43947
44275
  console.log(` ${import_chalk6.default.dim("Addresses")} ${addressCount}`);
44276
+ console.log(
44277
+ ` ${import_chalk6.default.dim("WalletConnect")} ${walletConnect.connected ? import_chalk6.default.green(
44278
+ `connected to ${walletConnect.peer || "wallet"} (${walletConnect.address}), expires ${walletConnect.expiresAt}`
44279
+ ) : import_chalk6.default.yellow("not connected")}`
44280
+ );
43948
44281
  if (!exists) info("\nRun `twak wallet create --password <pw>` to create a wallet.");
43949
44282
  }
43950
44283
  } catch (err) {
@@ -44312,8 +44645,8 @@ var SwapExecutor = class {
44312
44645
  explorerUrl: getExplorerTxUrl(params.fromChain, hash)
44313
44646
  };
44314
44647
  } catch (err) {
44315
- let msg = err instanceof Error ? err.message : "Unknown error";
44316
- let code2 = classifySwapError(msg);
44648
+ let msg = errorMessage(err);
44649
+ let code2 = err instanceof CommandError ? err.code : classifySwapError(msg);
44317
44650
  if (approvalHash) {
44318
44651
  code2 = "APPROVAL_SENT_SWAP_FAILED";
44319
44652
  msg += `. Approval was sent (tx: ${approvalHash}). Check allowance before retrying.`;
@@ -44387,7 +44720,7 @@ var SwapExecutor = class {
44387
44720
  if (status.failed)
44388
44721
  throw new Error(`Transaction failed: ${status.error || "unknown error"}`);
44389
44722
  } catch (e2) {
44390
- const err = e2 instanceof Error ? e2 : new Error(String(e2));
44723
+ const err = e2 instanceof Error ? e2 : new Error(errorMessage(e2));
44391
44724
  if (err.message.startsWith("Transaction failed")) throw err;
44392
44725
  if (!err.message.includes("not found") && !err.message.includes("Transaction not found"))
44393
44726
  throw err;
@@ -45704,7 +46037,7 @@ var SecurityService = class {
45704
46037
  query.toString()
45705
46038
  );
45706
46039
  } catch (err) {
45707
- const msg = err instanceof Error ? err.message : String(err);
46040
+ const msg = errorMessage(err);
45708
46041
  const details = err?.details;
45709
46042
  let detailsStr = "";
45710
46043
  if (details != null) {
@@ -46788,6 +47121,9 @@ function registerQueryCommand(program3) {
46788
47121
  });
46789
47122
  }
46790
47123
 
47124
+ // src/commands/serve.ts
47125
+ var fs8 = __toESM(require("fs"));
47126
+
46791
47127
  // ../actions/dist/index.mjs
46792
47128
  init_dist();
46793
47129
  var import_zod3 = require("zod");
@@ -48533,7 +48869,7 @@ function validateOfframpInput(input2, chainKey, nativeDecimals) {
48533
48869
  try {
48534
48870
  assertStrictPositiveAmount(input2.cryptoAmount, decimals);
48535
48871
  } catch (err) {
48536
- throw new TransferError("VALIDATION_ERROR", err instanceof Error ? err.message : String(err));
48872
+ throw new TransferError("VALIDATION_ERROR", errorMessage(err));
48537
48873
  }
48538
48874
  return decimals;
48539
48875
  }
@@ -49198,12 +49534,10 @@ var WalletModeActionProvider = class extends ActionProvider {
49198
49534
  description = "Switch wallet modes mid-session. The local agent wallet is auto-bound at server startup when configured, so signing actions work out of the box without calling this. Only call switch_wallet_mode when the user explicitly asks to use WalletConnect (or to return to the local wallet afterwards).";
49199
49535
  walletContext;
49200
49536
  agentWallet;
49201
- walletConnectFactory;
49202
49537
  constructor(options) {
49203
49538
  super();
49204
49539
  this.walletContext = options.walletContext;
49205
49540
  this.agentWallet = options.agentWallet;
49206
- this.walletConnectFactory = options.walletConnectFactory;
49207
49541
  }
49208
49542
  getActions() {
49209
49543
  return [this.switchWalletMode(), this.getWalletStatus()];
@@ -49246,14 +49580,13 @@ var WalletModeActionProvider = class extends ActionProvider {
49246
49580
  await this.walletContext.bindLocal(this.agentWallet);
49247
49581
  return this.snapshotResponse(this.walletContext.snapshot());
49248
49582
  }
49249
- if (!this.walletConnectFactory) {
49583
+ if (!this.walletContext.walletConnectAvailable) {
49250
49584
  throw new WalletError(
49251
49585
  "VALIDATION_ERROR",
49252
49586
  "WalletConnect is not configured. Set WALLETCONNECT_PROJECT_ID (env var or `twak init --wc-project-id <id>`) and restart the MCP server."
49253
49587
  );
49254
49588
  }
49255
- const wc3 = this.walletConnectFactory();
49256
- await this.walletContext.beginWalletConnect(wc3);
49589
+ await this.walletContext.beginWalletConnect();
49257
49590
  return this.snapshotResponse(this.walletContext.snapshot());
49258
49591
  }
49259
49592
  };
@@ -51539,19 +51872,19 @@ var getRefs = (options) => {
51539
51872
  };
51540
51873
 
51541
51874
  // ../../node_modules/.pnpm/zod-to-json-schema@3.25.1_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/errorMessages.js
51542
- function addErrorMessage(res, key, errorMessage, refs) {
51875
+ function addErrorMessage(res, key, errorMessage2, refs) {
51543
51876
  if (!refs?.errorMessages)
51544
51877
  return;
51545
- if (errorMessage) {
51878
+ if (errorMessage2) {
51546
51879
  res.errorMessage = {
51547
51880
  ...res.errorMessage,
51548
- [key]: errorMessage
51881
+ [key]: errorMessage2
51549
51882
  };
51550
51883
  }
51551
51884
  }
51552
- function setResponseValueAndErrors(res, key, value, errorMessage, refs) {
51885
+ function setResponseValueAndErrors(res, key, value, errorMessage2, refs) {
51553
51886
  res[key] = value;
51554
- addErrorMessage(res, key, errorMessage, refs);
51887
+ addErrorMessage(res, key, errorMessage2, refs);
51555
51888
  }
51556
51889
 
51557
51890
  // ../../node_modules/.pnpm/zod-to-json-schema@3.25.1_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/getRelativePath.js
@@ -52848,8 +53181,8 @@ var Protocol = class {
52848
53181
  if (queuedMessage.type === "response") {
52849
53182
  resolver(message);
52850
53183
  } else {
52851
- const errorMessage = message;
52852
- const error2 = new McpError(errorMessage.error.code, errorMessage.error.message, errorMessage.error.data);
53184
+ const errorMessage2 = message;
53185
+ const error2 = new McpError(errorMessage2.error.code, errorMessage2.error.message, errorMessage2.error.data);
52853
53186
  resolver(error2);
52854
53187
  }
52855
53188
  } else {
@@ -54143,23 +54476,23 @@ var Server = class extends Protocol {
54143
54476
  const wrappedHandler = async (request, extra) => {
54144
54477
  const validatedRequest = safeParse2(CallToolRequestSchema, request);
54145
54478
  if (!validatedRequest.success) {
54146
- const errorMessage = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
54147
- throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage}`);
54479
+ const errorMessage2 = validatedRequest.error instanceof Error ? validatedRequest.error.message : String(validatedRequest.error);
54480
+ throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call request: ${errorMessage2}`);
54148
54481
  }
54149
54482
  const { params } = validatedRequest.data;
54150
54483
  const result = await Promise.resolve(handler(request, extra));
54151
54484
  if (params.task) {
54152
54485
  const taskValidationResult = safeParse2(CreateTaskResultSchema, result);
54153
54486
  if (!taskValidationResult.success) {
54154
- const errorMessage = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
54155
- throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage}`);
54487
+ const errorMessage2 = taskValidationResult.error instanceof Error ? taskValidationResult.error.message : String(taskValidationResult.error);
54488
+ throw new McpError(ErrorCode.InvalidParams, `Invalid task creation result: ${errorMessage2}`);
54156
54489
  }
54157
54490
  return taskValidationResult.data;
54158
54491
  }
54159
54492
  const validationResult = safeParse2(CallToolResultSchema, result);
54160
54493
  if (!validationResult.success) {
54161
- const errorMessage = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
54162
- throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage}`);
54494
+ const errorMessage2 = validationResult.error instanceof Error ? validationResult.error.message : String(validationResult.error);
54495
+ throw new McpError(ErrorCode.InvalidParams, `Invalid tools/call result: ${errorMessage2}`);
54163
54496
  }
54164
54497
  return validationResult.data;
54165
54498
  };
@@ -54605,7 +54938,7 @@ var TwakMcpServer = class {
54605
54938
  const isZodValidation = error2 instanceof Error && error2.name === "ZodError";
54606
54939
  const payload = classified ?? (isZodValidation ? { code: "VALIDATION_ERROR", message: error2.message } : {
54607
54940
  code: "UNKNOWN_ERROR",
54608
- message: error2 instanceof Error ? error2.message : "Unknown error"
54941
+ message: errorMessage(error2)
54609
54942
  });
54610
54943
  return {
54611
54944
  content: [{ type: "text", text: JSON.stringify(payload) }],
@@ -54639,6 +54972,8 @@ function errorCodeToHttpStatus(code2) {
54639
54972
  case "SLIPPAGE_EXCEEDED":
54640
54973
  case "VALUE_LIMIT_EXCEEDED":
54641
54974
  case "TX_FAILED":
54975
+ case "USER_REJECTED":
54976
+ case "WC_REQUEST_FAILED":
54642
54977
  return 422;
54643
54978
  case "RATE_LIMITED":
54644
54979
  return 429;
@@ -54777,7 +55112,7 @@ var TwakRestServer = class {
54777
55112
  const isZodValidation = err instanceof Error && err.name === "ZodError";
54778
55113
  const payload = classified ?? (isZodValidation ? { code: "VALIDATION_ERROR", message: err.message } : {
54779
55114
  code: err instanceof CommandError ? err.code : "UNKNOWN_ERROR",
54780
- message: err instanceof Error ? err.message : "Unknown error"
55115
+ message: errorMessage(err)
54781
55116
  });
54782
55117
  const status = isZodValidation ? 400 : errorCodeToHttpStatus(payload.code);
54783
55118
  res.writeHead(status);
@@ -54788,11 +55123,15 @@ var TwakRestServer = class {
54788
55123
  };
54789
55124
 
54790
55125
  // src/commands/serve.ts
55126
+ init_dist();
54791
55127
  function registerServeCommand(program3) {
54792
55128
  program3.command("serve").description("Start MCP server (stdio) or REST API server for AI agent connection").option("--rest", "Start REST HTTP server instead of MCP stdio server").option("--port <n>", "Port for REST server", "3000").option(
54793
55129
  "--host <host>",
54794
55130
  "Host to bind REST server (default: 127.0.0.1; use 0.0.0.0 to expose externally)"
54795
- ).option("--password <password>", "Wallet password (falls back to keychain)").option("--auto-lock <minutes>", "Auto-lock wallet after N minutes of inactivity").option("--wc-project-id <id>", "WalletConnect project ID (or set WALLETCONNECT_PROJECT_ID)").option(
55131
+ ).option("--password <password>", "Wallet password (falls back to keychain)").option("--auto-lock <minutes>", "Auto-lock wallet after N minutes of inactivity").option(
55132
+ "--wc-project-id <id>",
55133
+ "WalletConnect project ID override (default: Trust Wallet project; or set WALLETCONNECT_PROJECT_ID)"
55134
+ ).option(
54796
55135
  "--watch",
54797
55136
  "Run a background watcher that fires DCA/limit automations (created via CLI or the create_automation MCP action); requires the local agent wallet"
54798
55137
  ).option(
@@ -54823,25 +55162,33 @@ function registerServeCommand(program3) {
54823
55162
  passwordResolver: () => resolvePassword(opts)
54824
55163
  });
54825
55164
  }
54826
- let wcProjectId = opts.wcProjectId || process.env.WALLETCONNECT_PROJECT_ID;
54827
- if (!wcProjectId) {
55165
+ const wcProjectId = await resolveWcProjectId(opts.wcProjectId);
55166
+ const walletContext = new WalletContext({
55167
+ walletConnectFactory: () => createWalletConnectProvider(wcProjectId)
55168
+ });
55169
+ let resumeOutcome = "none";
55170
+ if (fs8.existsSync(wcStoragePath())) {
54828
55171
  try {
54829
- const creds = await new CredentialManager().load();
54830
- wcProjectId = creds?.walletConnectProjectId;
54831
- } catch {
55172
+ const resumed = await withTimeout(
55173
+ walletContext.restoreWalletConnect(),
55174
+ RESTORE_TIMEOUT_MS
55175
+ );
55176
+ if (resumed) {
55177
+ resumeOutcome = "resumed";
55178
+ info("Resumed WalletConnect session");
55179
+ }
55180
+ } catch (err) {
55181
+ resumeOutcome = "pending";
55182
+ warn(`Could not resume WalletConnect session: ${errorMessage(err)}`);
54832
55183
  }
54833
55184
  }
54834
- const walletConnectFactory = wcProjectId ? () => new WalletConnectProvider({ projectId: wcProjectId }) : void 0;
54835
- const walletContext = new WalletContext();
54836
- if (agentWallet) {
55185
+ if (agentWallet && resumeOutcome === "none") {
54837
55186
  await walletContext.bindLocal(agentWallet);
54838
55187
  blockchainClient.setSigner(agentWallet.getSigner());
54839
55188
  }
54840
55189
  const registry = new ActionRegistry();
54841
55190
  registry.register(new BalanceActionProvider());
54842
- registry.register(
54843
- new WalletModeActionProvider({ walletContext, agentWallet, walletConnectFactory })
54844
- );
55191
+ registry.register(new WalletModeActionProvider({ walletContext, agentWallet }));
54845
55192
  registry.register(new SwapActionProvider(swapClient, walletContext, blockchainClient));
54846
55193
  registry.register(new SecurityActionProvider());
54847
55194
  registry.register(new HistoryActionProvider());
@@ -54890,7 +55237,7 @@ function registerServeCommand(program3) {
54890
55237
  },
54891
55238
  {
54892
55239
  intervalSec,
54893
- onTickError: (err) => error(`Automation tick error: ${err instanceof Error ? err.message : String(err)}`)
55240
+ onTickError: (err) => error(`Automation tick error: ${errorMessage(err)}`)
54894
55241
  }
54895
55242
  );
54896
55243
  info(
@@ -54908,21 +55255,19 @@ function registerServeCommand(program3) {
54908
55255
  host: opts.host,
54909
55256
  apiKey: client.hmacSecret
54910
55257
  });
54911
- registerShutdownHandlers([agentWallet], async () => {
55258
+ registerShutdownHandlers([agentWallet], () => {
54912
55259
  watcher?.stop();
54913
- await walletContext.reset();
54914
55260
  restServer.stop();
54915
55261
  });
54916
55262
  try {
54917
55263
  await restServer.start();
54918
55264
  } catch (err) {
54919
- error(`Error: ${err instanceof Error ? err.message : String(err)}`);
55265
+ error(`Error: ${errorMessage(err)}`);
54920
55266
  process.exit(1);
54921
55267
  }
54922
55268
  } else {
54923
55269
  registerShutdownHandlers([agentWallet], () => {
54924
55270
  watcher?.stop();
54925
- return walletContext.reset();
54926
55271
  });
54927
55272
  const mcpServer = new TwakMcpServer(registry, context);
54928
55273
  console.error("TW Agent MCP server starting...");
@@ -55273,10 +55618,7 @@ ${url}`);
55273
55618
  try {
55274
55619
  assertStrictPositiveAmount(opts.amount, decimals);
55275
55620
  } catch (err) {
55276
- throw new TransferError(
55277
- "VALIDATION_ERROR",
55278
- err instanceof Error ? err.message : String(err)
55279
- );
55621
+ throw new TransferError("VALIDATION_ERROR", errorMessage(err));
55280
55622
  }
55281
55623
  if (!opts.skipSafetyCheck) {
55282
55624
  const maxUsd = parseFloat(opts.maxUsd ?? String(DEFAULT_MAX_OFFRAMP_USD2));
@@ -55831,15 +56173,15 @@ function registerX402Command(program3) {
55831
56173
  }
55832
56174
 
55833
56175
  // src/analytics.ts
55834
- var fs6 = __toESM(require("fs"));
56176
+ var fs9 = __toESM(require("fs"));
55835
56177
  var os4 = __toESM(require("os"));
55836
- var path4 = __toESM(require("path"));
55837
- var crypto7 = __toESM(require("crypto"));
56178
+ var path6 = __toESM(require("path"));
56179
+ var crypto8 = __toESM(require("crypto"));
55838
56180
 
55839
56181
  // package.json
55840
56182
  var package_default = {
55841
56183
  name: "@trustwallet/cli",
55842
- version: "0.20.0",
56184
+ version: "0.22.0",
55843
56185
  description: "TWAK CLI \u2014 command-line interface for Trust Wallet Agent Kit",
55844
56186
  main: "dist/index.js",
55845
56187
  types: "dist/index.d.ts",
@@ -55916,10 +56258,10 @@ try {
55916
56258
  } catch (_e6) {
55917
56259
  }
55918
56260
  var AMPLITUDE_API_KEY = process.env.AMPLITUDE_API_KEY || "__AMPLITUDE_API_KEY__";
55919
- var CONFIG_DIR = path4.join(os4.homedir(), ".twak");
55920
- var ID_FILE = path4.join(CONFIG_DIR, "analytics-id");
55921
- var OPT_IN_FILE = path4.join(CONFIG_DIR, "telemetry-on");
55922
- var OPT_OUT_FILE = path4.join(CONFIG_DIR, "telemetry-off");
56261
+ var CONFIG_DIR = path6.join(os4.homedir(), ".twak");
56262
+ var ID_FILE = path6.join(CONFIG_DIR, "analytics-id");
56263
+ var OPT_IN_FILE = path6.join(CONFIG_DIR, "telemetry-on");
56264
+ var OPT_OUT_FILE = path6.join(CONFIG_DIR, "telemetry-off");
55923
56265
  var initialized2 = false;
55924
56266
  function isOptedOut() {
55925
56267
  if (process.argv.includes("--no-analytics")) return true;
@@ -55927,31 +56269,31 @@ function isOptedOut() {
55927
56269
  if (env === "off" || env === "0" || env === "false") return true;
55928
56270
  if (env === "on" || env === "1" || env === "true") return false;
55929
56271
  try {
55930
- if (fs6.existsSync(OPT_OUT_FILE)) return true;
55931
- return !fs6.existsSync(OPT_IN_FILE);
56272
+ if (fs9.existsSync(OPT_OUT_FILE)) return true;
56273
+ return !fs9.existsSync(OPT_IN_FILE);
55932
56274
  } catch (_e6) {
55933
56275
  return true;
55934
56276
  }
55935
56277
  }
55936
56278
  function setOptOut2(optOut) {
55937
- fs6.mkdirSync(CONFIG_DIR, { recursive: true });
56279
+ fs9.mkdirSync(CONFIG_DIR, { recursive: true });
55938
56280
  if (optOut) {
55939
- if (fs6.existsSync(OPT_IN_FILE)) fs6.unlinkSync(OPT_IN_FILE);
55940
- fs6.writeFileSync(OPT_OUT_FILE, "");
56281
+ if (fs9.existsSync(OPT_IN_FILE)) fs9.unlinkSync(OPT_IN_FILE);
56282
+ fs9.writeFileSync(OPT_OUT_FILE, "");
55941
56283
  } else {
55942
- fs6.writeFileSync(OPT_IN_FILE, "");
55943
- if (fs6.existsSync(OPT_OUT_FILE)) fs6.unlinkSync(OPT_OUT_FILE);
56284
+ fs9.writeFileSync(OPT_IN_FILE, "");
56285
+ if (fs9.existsSync(OPT_OUT_FILE)) fs9.unlinkSync(OPT_OUT_FILE);
55944
56286
  }
55945
56287
  }
55946
56288
  function getUserId() {
55947
56289
  try {
55948
- if (fs6.existsSync(ID_FILE)) return fs6.readFileSync(ID_FILE, "utf-8").trim();
56290
+ if (fs9.existsSync(ID_FILE)) return fs9.readFileSync(ID_FILE, "utf-8").trim();
55949
56291
  } catch (_e6) {
55950
56292
  }
55951
- const id = crypto7.randomUUID();
56293
+ const id = crypto8.randomUUID();
55952
56294
  try {
55953
- fs6.mkdirSync(CONFIG_DIR, { recursive: true });
55954
- fs6.writeFileSync(ID_FILE, id);
56295
+ fs9.mkdirSync(CONFIG_DIR, { recursive: true });
56296
+ fs9.writeFileSync(ID_FILE, id);
55955
56297
  } catch (_e6) {
55956
56298
  }
55957
56299
  return id;