@trustwallet/cli 0.13.0 → 0.14.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
@@ -3124,6 +3124,7 @@ __export(dist_exports, {
3124
3124
  CommandError: () => CommandError,
3125
3125
  ERC20_ERROR_CODES: () => ERC20_ERROR_CODES,
3126
3126
  Erc20Error: () => Erc20Error,
3127
+ McpResponse: () => McpResponse,
3127
3128
  NATIVE_TOKEN_ADDRESS: () => NATIVE_TOKEN_ADDRESS,
3128
3129
  QUERY_ERROR_CODES: () => QUERY_ERROR_CODES,
3129
3130
  QueryError: () => QueryError,
@@ -3613,7 +3614,7 @@ async function fetchBalance(httpClient, assetId, address, currency = "USD") {
3613
3614
  throw new BalanceError("NETWORK_ERROR", "Could not fetch balance. Please try again later.");
3614
3615
  }
3615
3616
  }
3616
- var import_crypto, NATIVE_TOKEN_ADDRESS, SOLANA_NATIVE_ADDRESS, CHAINS, COMMON_TOKENS, BSC_18_DECIMAL_TOKENS, NATIVE_SYMBOLS, TOKEN_DECIMALS, CELO_NATIVE_ADDRESS, TwakError, TwakApiError, TwakNetworkError, TwakValidationError, CommandError, COMMON_ERROR_CODES, FS_ERRNO_CODES, SWAP_ERROR_CODES, SwapError, TRANSFER_ERROR_CODES, TransferError, WALLET_ERROR_CODES, WalletError, QUERY_ERROR_CODES, QueryError, BALANCE_ERROR_CODES, BalanceError, ALERT_ERROR_CODES, AlertError, ERC20_ERROR_CODES, Erc20Error, WATCH_ERROR_CODES, WatchError, AUTOMATION_ERROR_CODES, AutomationError, X402_ERROR_CODES, X402Error, DECIMAL_AMOUNT_RE, UNSAFE_KEYS, NAMING_API, DEFAULT_GATEWAY_URL, DEFAULT_MARKET_URL, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT, DEFAULT_X_ORIGIN, TwakHttpClient, ALLOWED_OFFRAMP_PROVIDERS;
3617
+ var import_crypto, NATIVE_TOKEN_ADDRESS, SOLANA_NATIVE_ADDRESS, CHAINS, COMMON_TOKENS, BSC_18_DECIMAL_TOKENS, NATIVE_SYMBOLS, TOKEN_DECIMALS, CELO_NATIVE_ADDRESS, TwakError, TwakApiError, TwakNetworkError, TwakValidationError, CommandError, COMMON_ERROR_CODES, FS_ERRNO_CODES, SWAP_ERROR_CODES, SwapError, TRANSFER_ERROR_CODES, TransferError, WALLET_ERROR_CODES, WalletError, QUERY_ERROR_CODES, QueryError, BALANCE_ERROR_CODES, BalanceError, ALERT_ERROR_CODES, AlertError, ERC20_ERROR_CODES, Erc20Error, WATCH_ERROR_CODES, WatchError, AUTOMATION_ERROR_CODES, AutomationError, X402_ERROR_CODES, X402Error, DECIMAL_AMOUNT_RE, UNSAFE_KEYS, NAMING_API, DEFAULT_GATEWAY_URL, DEFAULT_MARKET_URL, DEFAULT_TIMEOUT, DEFAULT_USER_AGENT, DEFAULT_X_ORIGIN, TwakHttpClient, ALLOWED_OFFRAMP_PROVIDERS, McpResponse;
3617
3618
  var init_dist = __esm({
3618
3619
  "../core/dist/index.mjs"() {
3619
3620
  "use strict";
@@ -4182,7 +4183,11 @@ var init_dist = __esm({
4182
4183
  // Raised by WalletConnect provider methods when the session isn't yet
4183
4184
  // established (user hasn't scanned/approved) or has been dropped/expired.
4184
4185
  // Actionable: agent prompts the user to complete pairing, then retry.
4185
- WC_NOT_CONNECTED: "WC_NOT_CONNECTED"
4186
+ WC_NOT_CONNECTED: "WC_NOT_CONNECTED",
4187
+ // Raised by signing-path action providers when no wallet mode has been
4188
+ // selected yet for this server lifetime. Actionable: agent calls
4189
+ // switch_wallet_mode (local or walletconnect) and retries.
4190
+ WALLET_NOT_BOUND: "WALLET_NOT_BOUND"
4186
4191
  };
4187
4192
  WalletError = class extends CommandError {
4188
4193
  constructor(code2, message) {
@@ -4373,6 +4378,11 @@ var init_dist = __esm({
4373
4378
  }
4374
4379
  };
4375
4380
  ALLOWED_OFFRAMP_PROVIDERS = /* @__PURE__ */ new Set(["MoonPay"]);
4381
+ McpResponse = class {
4382
+ constructor(content) {
4383
+ this.content = content;
4384
+ }
4385
+ };
4376
4386
  }
4377
4387
  });
4378
4388
 
@@ -6880,7 +6890,7 @@ function registerShutdownHandlers(providers, onBeforeExit) {
6880
6890
  process.removeListener("SIGTERM", handler);
6881
6891
  };
6882
6892
  }
6883
- var fs2, path2, import_trust_sdk, import_trust_sdk2, import_trust_sdk3, import_trust_sdk4, import_crypto2, __require, polyfillsApplied, _credentials, _naasSessionId, nodeStubsPatched, NAAS_SESSION_RE, naasSessionPatched, coinTypeByValue, walletCore, initialized, SPONSORED_RPC, BlockchainClient, WalletCoreSigner, domainsByCoinId, GATEWAY_URL, DEVICE_INFO_BODY, VerifyCredentialsError, WALLET_ID_SUFFIX, WALLET_ID_LENGTH, EVM_COIN_IDS, MAINTAINED_COIN_IDS, WalletProvider, AgentWalletProvider, SHUTDOWN_TIMEOUT_MS;
6893
+ var fs2, path2, import_trust_sdk, import_trust_sdk2, import_trust_sdk3, import_trust_sdk4, import_crypto2, __require, polyfillsApplied, _credentials, _naasSessionId, nodeStubsPatched, NAAS_SESSION_RE, naasSessionPatched, coinTypeByValue, walletCore, initialized, SPONSORED_RPC, BlockchainClient, WalletCoreSigner, domainsByCoinId, GATEWAY_URL, DEVICE_INFO_BODY, VerifyCredentialsError, WALLET_ID_SUFFIX, WALLET_ID_LENGTH, EVM_COIN_IDS, MAINTAINED_COIN_IDS, WalletProvider, AgentWalletProvider, WalletContext, SHUTDOWN_TIMEOUT_MS;
6884
6894
  var init_dist3 = __esm({
6885
6895
  "../wallet-core/dist/index.mjs"() {
6886
6896
  "use strict";
@@ -6896,6 +6906,7 @@ var init_dist3 = __esm({
6896
6906
  import_crypto2 = require("crypto");
6897
6907
  init_dist();
6898
6908
  init_dist2();
6909
+ init_dist();
6899
6910
  __require = /* @__PURE__ */ ((x6) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x6, {
6900
6911
  get: (a3, b3) => (typeof require !== "undefined" ? require : a3)[b3]
6901
6912
  }) : x6)(function(x6) {
@@ -7916,10 +7927,33 @@ var init_dist3 = __esm({
7916
7927
  }
7917
7928
  }
7918
7929
  }
7930
+ async signTypedData(chainKey, chainId, typedDataJson) {
7931
+ await this.ensureConnected();
7932
+ this.resetAutoLockTimer();
7933
+ return this.blockchainClient.signTypedData({
7934
+ chainKey,
7935
+ chainId,
7936
+ typedDataJson,
7937
+ hdWallet: this.signer.getHDWallet()
7938
+ });
7939
+ }
7940
+ async approveErc20(chainKey, token, spender, amount) {
7941
+ await this.ensureConnected();
7942
+ this.resetAutoLockTimer();
7943
+ const coinId = chainKeyToCoinType(chainKey);
7944
+ if (coinId === void 0) throw new Error(`Unsupported chain: ${chainKey}`);
7945
+ const result = await this.blockchainClient.approve({
7946
+ assetId: `c${coinId}_t${token}`,
7947
+ spender,
7948
+ amount,
7949
+ hdWallet: this.signer.getHDWallet()
7950
+ });
7951
+ return { txHash: result.hash };
7952
+ }
7919
7953
  async sendTransaction(params) {
7920
7954
  await this.ensureConnected();
7921
7955
  this.resetAutoLockTimer();
7922
- const { getChainByEvmId: getChainByEvmId2, chainKeyToCoinType: chainKeyToCoinType3 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
7956
+ const { getChainByEvmId: getChainByEvmId2, chainKeyToCoinType: chainKeyToCoinType4 } = await Promise.resolve().then(() => (init_dist(), dist_exports));
7923
7957
  let assetId;
7924
7958
  if (params.chainId.startsWith("eip155:")) {
7925
7959
  const chain2 = getChainByEvmId2(params.chainId.replace("eip155:", ""));
@@ -7928,7 +7962,7 @@ var init_dist3 = __esm({
7928
7962
  } else if (params.chainId.startsWith("solana:")) {
7929
7963
  assetId = "c501";
7930
7964
  } else {
7931
- const coinId = chainKeyToCoinType3(params.chainId);
7965
+ const coinId = chainKeyToCoinType4(params.chainId);
7932
7966
  if (coinId === void 0) throw new Error(`Unsupported chain: ${params.chainId}`);
7933
7967
  assetId = `c${coinId}`;
7934
7968
  }
@@ -7988,14 +8022,206 @@ var init_dist3 = __esm({
7988
8022
  return this.connected;
7989
8023
  }
7990
8024
  getBlockchainClient() {
7991
- if (!this.connected) throw new Error("Wallet is locked. Use an async method to trigger re-auth.");
8025
+ if (!this.connected)
8026
+ throw new Error("Wallet is locked. Use an async method to trigger re-auth.");
7992
8027
  return this.blockchainClient;
7993
8028
  }
7994
8029
  getSigner() {
7995
- if (!this.connected) throw new Error("Wallet is locked. Use an async method to trigger re-auth.");
8030
+ if (!this.connected)
8031
+ throw new Error("Wallet is locked. Use an async method to trigger re-auth.");
7996
8032
  return this.signer;
7997
8033
  }
7998
8034
  };
8035
+ WalletContext = class {
8036
+ _state = "unbound";
8037
+ _wallet;
8038
+ /**
8039
+ * The WC instance we're currently pairing with. Used to ignore callbacks
8040
+ * from a previous pairing if the user re-calls `beginWalletConnect` while
8041
+ * already in `wc-pairing` (e.g. the first QR went stale and they want a
8042
+ * fresh one). Without this guard, the first wc's session-established
8043
+ * callback could land later and overwrite `_wallet` with the wrong instance.
8044
+ */
8045
+ _pendingWc;
8046
+ _pairing;
8047
+ _peerName;
8048
+ _lastError;
8049
+ /**
8050
+ * Mode-switch serializer. Concurrent `bindLocal` / `beginWalletConnect` /
8051
+ * `reset` calls otherwise interleave their state writes — `_pendingWc`
8052
+ * identity guards prevent signing-against-wrong-wallet, but `_state` and
8053
+ * `_pairing` can land in an inconsistent shape. We queue each entry so the
8054
+ * second call waits for the first to settle. `.catch(()=>{})` on the field
8055
+ * itself keeps a rejected switch from poisoning the queue for later calls
8056
+ * (the caller still sees the original rejection via the awaited promise
8057
+ * we return from each entry method).
8058
+ */
8059
+ _switchInflight = Promise.resolve();
8060
+ /**
8061
+ * Disconnect any in-flight or established WC session. Used internally when
8062
+ * switching modes and externally by `reset()`. Best-effort — disconnect
8063
+ * failures are swallowed (state still progresses to a clean slot).
8064
+ *
8065
+ * Covers both `wc-connected` (via `_wallet`) and `wc-pairing` (via
8066
+ * `_pendingWc`). Without the pending-wc disconnect, switching away from
8067
+ * `wc-pairing` would leave the relay client open until expiry.
8068
+ */
8069
+ async disconnectWcIfAny() {
8070
+ const targets = [];
8071
+ if (this._wallet && this._wallet.name === "walletconnect") {
8072
+ targets.push(this._wallet);
8073
+ }
8074
+ if (this._pendingWc && this._pendingWc !== this._wallet) {
8075
+ targets.push(this._pendingWc);
8076
+ }
8077
+ for (const t of targets) {
8078
+ try {
8079
+ await t.disconnect();
8080
+ } catch {
8081
+ }
8082
+ }
8083
+ }
8084
+ get state() {
8085
+ return this._state;
8086
+ }
8087
+ /**
8088
+ * Returns the bound wallet, or throws WALLET_NOT_BOUND. All signing-path
8089
+ * action providers (transfer/swap/x402/etc.) should call this at the top
8090
+ * of each action method.
8091
+ */
8092
+ activeWallet() {
8093
+ if (this._state !== "local" && this._state !== "wc-connected") {
8094
+ throw new WalletError(
8095
+ "WALLET_NOT_BOUND",
8096
+ "No wallet mode selected. Ask the user whether to use the local agent wallet or pair via WalletConnect, then call switch_wallet_mode."
8097
+ );
8098
+ }
8099
+ return this._wallet;
8100
+ }
8101
+ /** Snapshot for get_wallet_status MCP action and CLI introspection. */
8102
+ snapshot() {
8103
+ const base3 = {
8104
+ state: this._state,
8105
+ // Spread-copy so consumers can't mutate our internal _lastError object.
8106
+ lastError: this._lastError ? { ...this._lastError } : void 0
8107
+ };
8108
+ if (this._wallet) {
8109
+ base3.walletType = this._wallet.name === "walletconnect" ? "walletconnect" : "agent";
8110
+ }
8111
+ if (this._pairing) {
8112
+ base3.pairing = {
8113
+ uri: this._pairing.uri,
8114
+ deepLink: this._pairing.deepLinks.universal,
8115
+ qrCodeImageUrl: this._pairing.qrCode.imageUrl,
8116
+ expiresAt: this._pairing.expiresAt
8117
+ };
8118
+ }
8119
+ if (this._peerName) base3.peerName = this._peerName;
8120
+ return base3;
8121
+ }
8122
+ /**
8123
+ * Bind to the local agent wallet. Idempotent for the same provider; when
8124
+ * called while bound to WC (`wc-connected` or `wc-pairing`), the WC session
8125
+ * is disconnected first and any in-flight pairing is invalidated so its
8126
+ * later session-established callback can't reclaim state.
8127
+ */
8128
+ async bindLocal(agent) {
8129
+ return this.enqueue(() => this.bindLocalImpl(agent));
8130
+ }
8131
+ async bindLocalImpl(agent) {
8132
+ await this.disconnectWcIfAny();
8133
+ this._pendingWc = void 0;
8134
+ this._wallet = agent;
8135
+ this._pairing = void 0;
8136
+ this._peerName = void 0;
8137
+ this._state = "local";
8138
+ this._lastError = void 0;
8139
+ }
8140
+ /**
8141
+ * Run `task` after any in-flight mode switch finishes. The returned promise
8142
+ * settles with `task`'s outcome (success or failure). The internal queue
8143
+ * promise always resolves so a failing switch can't block subsequent
8144
+ * switches.
8145
+ */
8146
+ enqueue(task) {
8147
+ const next = this._switchInflight.then(task);
8148
+ this._switchInflight = next.catch(() => void 0);
8149
+ return next;
8150
+ }
8151
+ /**
8152
+ * Start a WalletConnect pairing. Returns the pairing payload immediately;
8153
+ * the caller surfaces it (QR code, deep link). When any compatible client
8154
+ * approves, the provider's session_established event fires and the context
8155
+ * transitions to `wc-connected`.
8156
+ *
8157
+ * V1 does not restrict the peer wallet — any WC v2 client can pair. The
8158
+ * user's choice to scan the QR is the consent moment. A peer-identity gate
8159
+ * (e.g. Trust-Wallet-only) is deferred until we have a real pairing log to
8160
+ * write it against.
8161
+ *
8162
+ * `wc` is opaque here — we don't import the WalletConnect package directly
8163
+ * to avoid forcing wallet-core to depend on it. The caller (typically the
8164
+ * MCP action provider) constructs the WC provider and passes it in.
8165
+ */
8166
+ async beginWalletConnect(wc2, chains) {
8167
+ return this.enqueue(() => this.beginWalletConnectImpl(wc2, chains));
8168
+ }
8169
+ async beginWalletConnectImpl(wc2, chains) {
8170
+ await this.disconnectWcIfAny();
8171
+ try {
8172
+ this._pairing = await wc2.connect(chains);
8173
+ } catch (err) {
8174
+ this._wallet = void 0;
8175
+ this._pendingWc = void 0;
8176
+ this._pairing = void 0;
8177
+ this._peerName = void 0;
8178
+ this._state = "unbound";
8179
+ this._lastError = {
8180
+ code: "WC_PAIRING_FAILED",
8181
+ message: `WalletConnect pairing failed: ${err instanceof Error ? err.message : String(err)}`
8182
+ };
8183
+ throw err;
8184
+ }
8185
+ this._wallet = void 0;
8186
+ this._pendingWc = wc2;
8187
+ this._state = "wc-pairing";
8188
+ this._lastError = void 0;
8189
+ this._peerName = void 0;
8190
+ wc2.onSessionEstablished((session) => {
8191
+ if (this._pendingWc !== wc2) return;
8192
+ this._wallet = wc2;
8193
+ this._peerName = session.peerName;
8194
+ this._state = "wc-connected";
8195
+ this._pendingWc = void 0;
8196
+ this._pairing = void 0;
8197
+ });
8198
+ wc2.onSessionEnded((reason) => {
8199
+ if (this._wallet !== wc2) return;
8200
+ this._wallet = void 0;
8201
+ this._pairing = void 0;
8202
+ this._peerName = void 0;
8203
+ this._state = "unbound";
8204
+ this._lastError = {
8205
+ code: "WC_NOT_CONNECTED",
8206
+ message: `WalletConnect session ended (${reason}). Call switch_wallet_mode to re-pair or switch to local.`
8207
+ };
8208
+ });
8209
+ return this.snapshot().pairing;
8210
+ }
8211
+ /** Disconnect the active wallet (best-effort) and return to `unbound`. */
8212
+ async reset() {
8213
+ return this.enqueue(() => this.resetImpl());
8214
+ }
8215
+ async resetImpl() {
8216
+ await this.disconnectWcIfAny();
8217
+ this._wallet = void 0;
8218
+ this._pendingWc = void 0;
8219
+ this._pairing = void 0;
8220
+ this._peerName = void 0;
8221
+ this._state = "unbound";
8222
+ this._lastError = void 0;
8223
+ }
8224
+ };
7999
8225
  SHUTDOWN_TIMEOUT_MS = 3e3;
8000
8226
  }
8001
8227
  });
@@ -38734,10 +38960,10 @@ async function renderPngDataUrl(uri) {
38734
38960
  var DEFAULT_METADATA = {
38735
38961
  name: "TWAK Agent",
38736
38962
  description: "AI Agent for Trust Wallet operations",
38737
- url: "https://github.com/trustwallet/agent-kit",
38963
+ url: "https://portal.trustwallet.com",
38738
38964
  icons: ["https://avatars.githubusercontent.com/u/32179365"]
38739
38965
  };
38740
- var WC_NOT_CONNECTED_MSG = "WalletConnect session not established. If pairing is in progress, complete the approval in Trust Wallet mobile and retry; otherwise run connect_wallet to start pairing.";
38966
+ 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.';
38741
38967
  var WalletConnectProvider = class extends WalletProvider {
38742
38968
  name = "walletconnect";
38743
38969
  client = null;
@@ -38745,6 +38971,7 @@ var WalletConnectProvider = class extends WalletProvider {
38745
38971
  currentSession = null;
38746
38972
  config;
38747
38973
  sessionCallback = null;
38974
+ sessionEndedCallback = null;
38748
38975
  pairingEventCallback = null;
38749
38976
  constructor(config) {
38750
38977
  super();
@@ -38769,12 +38996,13 @@ var WalletConnectProvider = class extends WalletProvider {
38769
38996
  const emit = (event, data) => {
38770
38997
  if (this.pairingEventCallback) this.pairingEventCallback(event, data);
38771
38998
  };
38772
- client.on("session_delete", ({ topic }) => {
38773
- if (this.currentSession?.topic === topic) this.currentSession = null;
38774
- });
38775
- client.on("session_expire", ({ topic }) => {
38776
- if (this.currentSession?.topic === topic) this.currentSession = null;
38777
- });
38999
+ const handleSessionEnded = (topic, reason) => {
39000
+ if (this.currentSession?.topic !== topic) return;
39001
+ this.currentSession = null;
39002
+ if (this.sessionEndedCallback) this.sessionEndedCallback(reason);
39003
+ };
39004
+ client.on("session_delete", ({ topic }) => handleSessionEnded(topic, "deleted"));
39005
+ client.on("session_expire", ({ topic }) => handleSessionEnded(topic, "expired"));
38778
39006
  client.on("session_update", () => {
38779
39007
  if (this.sessionCallback) {
38780
39008
  const session = this.getActiveSession();
@@ -38792,6 +39020,15 @@ var WalletConnectProvider = class extends WalletProvider {
38792
39020
  onSessionEstablished(callback) {
38793
39021
  this.sessionCallback = callback;
38794
39022
  }
39023
+ /**
39024
+ * Register a callback for when the WC session ends (peer wallet revoked,
39025
+ * relay-side expiry, etc.). WalletContext uses this to flip back to
39026
+ * `unbound` so subsequent signing surfaces `WALLET_NOT_BOUND` rather than
39027
+ * a deep WC-RPC failure.
39028
+ */
39029
+ onSessionEnded(callback) {
39030
+ this.sessionEndedCallback = callback;
39031
+ }
38795
39032
  /**
38796
39033
  * Returns the WC account to use as the `from` field for a signing request
38797
39034
  * on `chainId`. EVM-only: throws `CHAIN_UNSUPPORTED` if `chainId` isn't in
@@ -38846,6 +39083,36 @@ var WalletConnectProvider = class extends WalletProvider {
38846
39083
  }
38847
39084
  });
38848
39085
  }
39086
+ async signTypedData(chainKey, chainId, typedDataJson) {
39087
+ const session = this.getActiveSession();
39088
+ if (!session) throw new WalletError("WC_NOT_CONNECTED", WC_NOT_CONNECTED_MSG);
39089
+ const client = await this.ensureClient();
39090
+ const caipChainId = chainKey.includes(":") ? chainKey : `eip155:${chainId}`;
39091
+ return client.request({
39092
+ topic: session.topic,
39093
+ chainId: caipChainId,
39094
+ request: {
39095
+ method: "eth_signTypedData_v4",
39096
+ params: [this.resolveFromAddress(caipChainId), typedDataJson]
39097
+ }
39098
+ });
39099
+ }
39100
+ async approveErc20(chainKey, token, spender, amount) {
39101
+ const chain2 = getChainById(chainKey);
39102
+ if (!chain2) throw new WalletError("CHAIN_UNSUPPORTED", `Unsupported chain: ${chainKey}`);
39103
+ const caipChainId = `${chain2.namespace}:${chain2.chainId}`;
39104
+ const selector = "0x095ea7b3";
39105
+ const paddedSpender = spender.replace(/^0x/, "").toLowerCase().padStart(64, "0");
39106
+ const paddedAmount = BigInt(amount).toString(16).padStart(64, "0");
39107
+ const data = selector + paddedSpender + paddedAmount;
39108
+ const txHash = await this.sendTransaction({
39109
+ chainId: caipChainId,
39110
+ to: token,
39111
+ data,
39112
+ value: "0x0"
39113
+ });
39114
+ return { txHash };
39115
+ }
38849
39116
  async sendTransaction(params) {
38850
39117
  const session = this.getActiveSession();
38851
39118
  if (!session) throw new WalletError("WC_NOT_CONNECTED", WC_NOT_CONNECTED_MSG);
@@ -42665,29 +42932,30 @@ function registerQueryCommand(program3) {
42665
42932
  }
42666
42933
 
42667
42934
  // ../actions/dist/index.mjs
42935
+ init_dist();
42668
42936
  var import_zod3 = require("zod");
42669
42937
  init_dist();
42670
42938
  var import_zod4 = require("zod");
42939
+ init_dist();
42671
42940
  var import_zod5 = require("zod");
42672
42941
  init_dist();
42673
42942
  var import_zod6 = require("zod");
42674
- init_dist();
42675
42943
  var import_zod7 = require("zod");
42944
+ init_dist();
42676
42945
  var import_zod8 = require("zod");
42677
42946
  init_dist();
42678
42947
  var import_zod9 = require("zod");
42679
42948
  init_dist();
42680
42949
  var import_zod10 = require("zod");
42950
+ init_dist3();
42681
42951
  init_dist();
42682
42952
  var import_zod11 = require("zod");
42683
- init_dist();
42684
42953
  var import_zod12 = require("zod");
42954
+ init_dist();
42685
42955
  var import_zod13 = require("zod");
42686
42956
  init_dist();
42687
42957
  var import_zod14 = require("zod");
42688
42958
  init_dist();
42689
- var import_zod15 = require("zod");
42690
- init_dist();
42691
42959
 
42692
42960
  // ../x402/dist/index.mjs
42693
42961
  init_dist();
@@ -42911,12 +43179,11 @@ async function signTransferWithAuthorization(p3) {
42911
43179
  domain: p3.token,
42912
43180
  message: authorization
42913
43181
  });
42914
- const rawSignature = await p3.signer.blockchainClient.signTypedData({
42915
- chainKey: p3.signer.chainKey,
42916
- chainId: p3.token.chainId,
42917
- typedDataJson: JSON.stringify(typedData),
42918
- hdWallet: p3.signer.hdWallet
42919
- });
43182
+ const rawSignature = await p3.walletProvider.signTypedData(
43183
+ p3.chainKey,
43184
+ p3.token.chainId,
43185
+ JSON.stringify(typedData)
43186
+ );
42920
43187
  const signature = normalizeEip712Signature(rawSignature);
42921
43188
  return { signature, authorization };
42922
43189
  }
@@ -42981,12 +43248,11 @@ async function signPermit2Authorization(p3) {
42981
43248
  witnessTo: p3.payTo,
42982
43249
  validAfter
42983
43250
  });
42984
- const rawSignature = await p3.signer.blockchainClient.signTypedData({
42985
- chainKey: p3.signer.chainKey,
42986
- chainId: p3.chainId,
42987
- typedDataJson: JSON.stringify(typedData),
42988
- hdWallet: p3.signer.hdWallet
42989
- });
43251
+ const rawSignature = await p3.walletProvider.signTypedData(
43252
+ p3.chainKey,
43253
+ p3.chainId,
43254
+ JSON.stringify(typedData)
43255
+ );
42990
43256
  const signature = normalizeEip712Signature(rawSignature);
42991
43257
  return {
42992
43258
  signature,
@@ -43082,17 +43348,16 @@ async function payAndFetch(args) {
43082
43348
  if (!confirmed) {
43083
43349
  throw new X402Error("PAYMENT_CANCELLED", "Payment cancelled by user");
43084
43350
  }
43085
- const signer = args.provider.getSigner();
43086
- const hdWallet = signer.getHDWallet();
43087
- const bc2 = new BlockchainClient(signer);
43088
- const fromAddress = signer.getAddress(chainKey);
43351
+ const bc2 = new BlockchainClient();
43352
+ const fromAddress = await args.provider.getAddress(chainKey);
43089
43353
  const tokenAddress = getAsset(kind);
43090
43354
  const amount = getAmount(kind);
43091
43355
  let xPayment;
43092
43356
  let nonce;
43093
43357
  if (method === "eip3009") {
43094
43358
  const { signature, authorization } = await signTransferWithAuthorization({
43095
- signer: { blockchainClient: bc2, hdWallet, chainKey },
43359
+ walletProvider: args.provider,
43360
+ chainKey,
43096
43361
  token: {
43097
43362
  name: rawName,
43098
43363
  version: rawVersion,
@@ -43114,6 +43379,7 @@ async function payAndFetch(args) {
43114
43379
  nonce = authorization.nonce;
43115
43380
  } else {
43116
43381
  approval = await ensurePermit2Allowance({
43382
+ walletProvider: args.provider,
43117
43383
  blockchainClient: bc2,
43118
43384
  chainKey,
43119
43385
  chainId: evmChainId,
@@ -43121,13 +43387,13 @@ async function payAndFetch(args) {
43121
43387
  owner: fromAddress,
43122
43388
  tokenAddress,
43123
43389
  requiredAmount: BigInt(amount),
43124
- confirmApproval: args.confirmApproval,
43125
- hdWallet
43390
+ confirmApproval: args.confirmApproval
43126
43391
  }) ?? void 0;
43127
43392
  const rawExtra2 = getExtra(kind);
43128
43393
  const dynamicSpender = typeof rawExtra2?.spenderAddress === "string" ? rawExtra2.spenderAddress : void 0;
43129
43394
  const { signature, permit2Authorization } = await signPermit2Authorization({
43130
- signer: { blockchainClient: bc2, hdWallet, chainKey },
43395
+ walletProvider: args.provider,
43396
+ chainKey,
43131
43397
  chainId: evmChainId,
43132
43398
  tokenAddress,
43133
43399
  amount,
@@ -43188,14 +43454,14 @@ async function ensurePermit2Allowance(args) {
43188
43454
  `Permit2 needs a one-time approval for ${args.tokenAddress} on ${args.chainKey}. Run: twak erc20 approve --asset ${assetId} --spender ${PERMIT2_ADDRESS} --amount unlimited`
43189
43455
  );
43190
43456
  }
43191
- const result = await args.blockchainClient.approve({
43192
- assetId,
43193
- spender: PERMIT2_ADDRESS,
43194
- amount: MAX_UINT256_DECIMAL,
43195
- hdWallet: args.hdWallet
43196
- });
43197
- await waitForTxConfirmation(args.blockchainClient, args.chainKey, args.owner, result.hash);
43198
- return { token: args.tokenAddress, chain: args.chainKey, txHash: result.hash };
43457
+ const { txHash } = await args.walletProvider.approveErc20(
43458
+ args.chainKey,
43459
+ args.tokenAddress,
43460
+ PERMIT2_ADDRESS,
43461
+ MAX_UINT256_DECIMAL
43462
+ );
43463
+ await waitForTxConfirmation(args.blockchainClient, args.chainKey, args.owner, txHash);
43464
+ return { token: args.tokenAddress, chain: args.chainKey, txHash };
43199
43465
  }
43200
43466
  var APPROVE_TIMEOUT_MS = 6e4;
43201
43467
  var APPROVE_POLL_MS = 2e3;
@@ -43412,6 +43678,17 @@ async function readChallenge(resp) {
43412
43678
  );
43413
43679
  }
43414
43680
  }
43681
+ function isPayableNetwork(network) {
43682
+ try {
43683
+ parseNetwork(network);
43684
+ return true;
43685
+ } catch {
43686
+ return false;
43687
+ }
43688
+ }
43689
+ function safeText(s2) {
43690
+ return s2.replace(/[\x00-\x1f\x7f]/g, "");
43691
+ }
43415
43692
  async function quoteX402(args) {
43416
43693
  try {
43417
43694
  await validateX402Url(args.url);
@@ -43458,7 +43735,10 @@ async function quoteX402(args) {
43458
43735
  }
43459
43736
  let preferredKind = null;
43460
43737
  try {
43461
- preferredKind = pickSupportedKind(challenge.accepts);
43738
+ const picked = pickSupportedKind(challenge.accepts);
43739
+ if (isPayableNetwork(getNetwork(picked))) {
43740
+ preferredKind = picked;
43741
+ }
43462
43742
  } catch {
43463
43743
  }
43464
43744
  const accepts = challenge.accepts.map((kind) => {
@@ -43482,22 +43762,32 @@ async function quoteX402(args) {
43482
43762
  maxTimeoutSeconds: getMaxTimeoutSeconds(kind),
43483
43763
  preferred: preferredKind === kind,
43484
43764
  requiresApproval,
43485
- description: `${amount} of ${tokenLabel} on ${network} via ${transferMethod}${approvalTag}`
43765
+ // `description` is a human-readable string consumers may render to a
43766
+ // terminal (CLI, LLM chat UI). `tokenName` and `transferMethod` are
43767
+ // seller-controlled (`extra.name`, `extra.assetTransferMethod`) so we
43768
+ // strip control chars to defend against terminal-escape injection.
43769
+ // Raw values stay on the structured `tokenName` / `transferMethod`
43770
+ // fields so consumers can apply their own escaping policy.
43771
+ description: `${amount} of ${safeText(tokenLabel)} on ${network} via ${safeText(transferMethod)}${approvalTag}`
43486
43772
  };
43487
43773
  });
43488
43774
  const chosen = accepts.find((a3) => a3.preferred);
43489
- const summary = chosen ? `Endpoint requires ${chosen.amount} of ${chosen.tokenName || chosen.asset} on ${chosen.network} via ${chosen.transferMethod}${chosen.requiresApproval ? " (requires one-time Permit2 approval)" : " (gasless)"}.` : "Endpoint has no supported payment options.";
43775
+ const safeTokenLabel = chosen ? safeText(chosen.tokenName) || chosen.asset : "";
43776
+ const safeMethod = chosen ? safeText(chosen.transferMethod) : "";
43777
+ const summary = chosen ? `Endpoint offers ${accepts.length} payment option(s). Default route: ${chosen.amount} of ${safeTokenLabel} on ${chosen.network} via ${safeMethod}${chosen.requiresApproval ? " (requires one-time Permit2 approval)" : " (gasless)"}.` : `Endpoint offers ${accepts.length} payment option(s), but none are supported by this client.`;
43490
43778
  const requestShapeHint = supportsBody ? args.body ? ` with method "${httpMethod}" and the same request body` : ` with method "${httpMethod}"` : "";
43491
43779
  return {
43492
43780
  success: true,
43493
43781
  resource: challenge.version === 2 ? challenge.resource : void 0,
43494
43782
  accepts,
43495
43783
  summary,
43496
- nextAction: chosen ? `Call x402_request with this url${requestShapeHint} and maxPaymentAtomic = "${chosen.amount}" to pay.` : "No supported method offered."
43784
+ nextAction: chosen ? `Show these payment options to the user and let them pick one. Then call x402_request with this url${requestShapeHint}, maxPaymentAtomic set to the picked option's amount, and preferAsset set to its asset contract.` : "No supported route. The endpoint offered no payment method this client can sign."
43497
43785
  };
43498
43786
  }
43499
43787
 
43500
43788
  // ../actions/dist/index.mjs
43789
+ var import_zod15 = require("zod");
43790
+ init_dist();
43501
43791
  var ActionProvider = class {
43502
43792
  supportsChain(_chainId) {
43503
43793
  return true;
@@ -43614,88 +43904,6 @@ var BalanceActionProvider = class extends ActionProvider {
43614
43904
  };
43615
43905
  }
43616
43906
  };
43617
- var WalletActionProvider = class extends ActionProvider {
43618
- constructor(walletProvider, agentWallet) {
43619
- super();
43620
- this.walletProvider = walletProvider;
43621
- this.agentWallet = agentWallet;
43622
- }
43623
- name = "wallet";
43624
- description = "Wallet connection and address management";
43625
- getActions() {
43626
- if (!this.walletProvider) return [];
43627
- return [this.getWalletAddress(), this.getWalletStatus(), this.connectWallet()];
43628
- }
43629
- getWalletAddress() {
43630
- return {
43631
- name: "get_wallet_address",
43632
- description: "Get the connected wallet address",
43633
- schema: import_zod4.z.object({
43634
- chainId: import_zod4.z.string().optional().default("eip155:1").describe("Chain ID")
43635
- }),
43636
- invoke: async (_ctx, input2) => {
43637
- const provider = this.agentWallet ?? this.walletProvider;
43638
- if (!provider) {
43639
- return {
43640
- connected: false,
43641
- code: "WALLET_NOT_FOUND",
43642
- message: "No wallet provider configured"
43643
- };
43644
- }
43645
- try {
43646
- const address = await provider.getAddress(input2.chainId);
43647
- return { connected: true, address, chainId: input2.chainId, provider: provider.name };
43648
- } catch {
43649
- return { connected: false };
43650
- }
43651
- }
43652
- };
43653
- }
43654
- connectWallet() {
43655
- return {
43656
- name: "connect_wallet",
43657
- description: "Connect a wallet via WalletConnect (returns QR code / deep link)",
43658
- schema: import_zod4.z.object({
43659
- chains: import_zod4.z.array(import_zod4.z.string()).optional().default(["eip155:1"]).describe("Chains to connect")
43660
- }),
43661
- invoke: async (_ctx, input2) => {
43662
- if (!this.walletProvider) {
43663
- return {
43664
- success: false,
43665
- code: "WALLET_NOT_FOUND",
43666
- message: "No wallet provider configured"
43667
- };
43668
- }
43669
- const result = await this.walletProvider.connect(input2.chains);
43670
- return { success: true, ...result };
43671
- }
43672
- };
43673
- }
43674
- getWalletStatus() {
43675
- return {
43676
- name: "get_wallet_status",
43677
- description: "Check wallet connection status",
43678
- schema: import_zod4.z.object({}),
43679
- invoke: async (_ctx) => {
43680
- const status = {};
43681
- if (this.agentWallet) {
43682
- status.agent = {
43683
- connected: this.agentWallet.isConnected(),
43684
- addresses: await this.agentWallet.getAddresses()
43685
- };
43686
- }
43687
- if (this.walletProvider) {
43688
- status.walletConnect = {
43689
- connected: this.walletProvider.isConnected(),
43690
- addresses: this.walletProvider.isConnected() ? await this.walletProvider.getAddresses() : []
43691
- };
43692
- }
43693
- const hasAny = this.agentWallet || this.walletProvider;
43694
- return hasAny ? status : { connected: false, provider: "none" };
43695
- }
43696
- };
43697
- }
43698
- };
43699
43907
  var ERC20_TRANSFER_SIG = "0xa9059cbb";
43700
43908
  var NATIVE_TOKEN_PLACEHOLDER = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE";
43701
43909
  var DEFAULT_MAX_TRANSFER_USD2 = 1e4;
@@ -43731,9 +43939,9 @@ function toCaip2(chain2) {
43731
43939
  return caip2;
43732
43940
  }
43733
43941
  var TransferActionProvider = class extends ActionProvider {
43734
- constructor(walletProvider, priceLookup, maxTransferUsd = DEFAULT_MAX_TRANSFER_USD2) {
43942
+ constructor(walletContext, priceLookup, maxTransferUsd = DEFAULT_MAX_TRANSFER_USD2) {
43735
43943
  super();
43736
- this.walletProvider = walletProvider;
43944
+ this.walletContext = walletContext;
43737
43945
  this.priceLookup = priceLookup;
43738
43946
  this.maxTransferUsd = maxTransferUsd;
43739
43947
  }
@@ -43746,20 +43954,17 @@ var TransferActionProvider = class extends ActionProvider {
43746
43954
  return {
43747
43955
  name: "transfer",
43748
43956
  description: 'Transfer native or ERC-20 tokens to a destination address on any EVM chain. Resolves ENS for the "to" field, applies a USD safety cap, and works with both the local agent wallet and an external WalletConnect-paired wallet.',
43749
- schema: import_zod5.z.object({
43750
- chain: import_zod5.z.string().describe(
43957
+ schema: import_zod4.z.object({
43958
+ chain: import_zod4.z.string().describe(
43751
43959
  'Chain name (e.g., ethereum, bsc, polygon, arbitrum, base). CAIP-2 ids like "eip155:1" also accepted.'
43752
43960
  ),
43753
- to: import_zod5.z.string().describe("Destination address or ENS name (e.g., vitalik.eth)"),
43754
- amount: import_zod5.z.string().describe('Amount in human-readable format (e.g., "1.5", "0.01")'),
43755
- token: import_zod5.z.string().optional().describe("ERC-20 token contract address (omit for native token)"),
43756
- decimals: import_zod5.z.coerce.number().optional().default(18).describe("Token decimals (default 18, use 6 for USDC/USDT)")
43961
+ to: import_zod4.z.string().describe("Destination address or ENS name (e.g., vitalik.eth)"),
43962
+ amount: import_zod4.z.string().describe('Amount in human-readable format (e.g., "1.5", "0.01")'),
43963
+ token: import_zod4.z.string().optional().describe("ERC-20 token contract address (omit for native token)"),
43964
+ decimals: import_zod4.z.coerce.number().optional().default(18).describe("Token decimals (default 18, use 6 for USDC/USDT)")
43757
43965
  }),
43758
43966
  invoke: async (_ctx, input2) => {
43759
- const signer = this.walletProvider;
43760
- if (!signer) {
43761
- throw new TransferError("WALLET_NOT_FOUND", "No wallet provider configured");
43762
- }
43967
+ const signer = this.walletContext.activeWallet();
43763
43968
  const caip2 = toCaip2(input2.chain);
43764
43969
  if (this.priceLookup) {
43765
43970
  try {
@@ -43816,10 +44021,10 @@ var TransferActionProvider = class extends ActionProvider {
43816
44021
  }
43817
44022
  };
43818
44023
  var SwapActionProvider = class extends ActionProvider {
43819
- constructor(swapClient, walletProvider, blockchainClient) {
44024
+ constructor(swapClient, walletContext, blockchainClient) {
43820
44025
  super();
43821
44026
  this.swapClient = swapClient;
43822
- this.walletProvider = walletProvider;
44027
+ this.walletContext = walletContext;
43823
44028
  this.blockchainClient = blockchainClient;
43824
44029
  }
43825
44030
  name = "swap";
@@ -43871,19 +44076,19 @@ var SwapActionProvider = class extends ActionProvider {
43871
44076
  return {
43872
44077
  name: "swap",
43873
44078
  description: "Execute a token swap via Trust Wallet (supports all chains including cross-chain)",
43874
- schema: import_zod6.z.object({
43875
- fromChain: import_zod6.z.string().describe("Source chain (e.g., ethereum, polygon, solana)"),
43876
- fromToken: import_zod6.z.string().describe("Source token symbol or address"),
43877
- toChain: import_zod6.z.string().describe("Destination chain"),
43878
- toToken: import_zod6.z.string().describe("Destination token symbol or address"),
43879
- amount: import_zod6.z.string().describe("Human-readable amount to swap"),
43880
- slippage: import_zod6.z.string().optional().default("1").describe("Slippage tolerance % (max 50)")
44079
+ schema: import_zod5.z.object({
44080
+ fromChain: import_zod5.z.string().describe("Source chain (e.g., ethereum, polygon, solana)"),
44081
+ fromToken: import_zod5.z.string().describe("Source token symbol or address"),
44082
+ toChain: import_zod5.z.string().describe("Destination chain"),
44083
+ toToken: import_zod5.z.string().describe("Destination token symbol or address"),
44084
+ amount: import_zod5.z.string().describe("Human-readable amount to swap"),
44085
+ slippage: import_zod5.z.string().optional().default("1").describe("Slippage tolerance % (max 50)")
43881
44086
  }),
43882
44087
  invoke: async (_ctx, input2) => {
43883
- const signer = this.walletProvider;
43884
- if (!signer || !this.swapClient) {
43885
- throw new SwapError("WALLET_NOT_FOUND", "Wallet and swap client required");
44088
+ if (!this.swapClient) {
44089
+ throw new SwapError("WALLET_NOT_FOUND", "Swap client not configured");
43886
44090
  }
44091
+ const signer = this.walletContext.activeWallet();
43887
44092
  const slippage = parseFloat(input2.slippage ?? "1");
43888
44093
  if (isNaN(slippage) || slippage <= 0 || slippage > 50) {
43889
44094
  return {
@@ -43958,12 +44163,12 @@ var SwapActionProvider = class extends ActionProvider {
43958
44163
  return {
43959
44164
  name: "get_swap_quote",
43960
44165
  description: "Get a swap quote without executing",
43961
- schema: import_zod6.z.object({
43962
- fromChain: import_zod6.z.string().describe("Source chain"),
43963
- fromToken: import_zod6.z.string().describe("Source token symbol or address"),
43964
- toChain: import_zod6.z.string().describe("Destination chain"),
43965
- toToken: import_zod6.z.string().describe("Destination token symbol or address"),
43966
- amount: import_zod6.z.string().describe("Human-readable amount")
44166
+ schema: import_zod5.z.object({
44167
+ fromChain: import_zod5.z.string().describe("Source chain"),
44168
+ fromToken: import_zod5.z.string().describe("Source token symbol or address"),
44169
+ toChain: import_zod5.z.string().describe("Destination chain"),
44170
+ toToken: import_zod5.z.string().describe("Destination token symbol or address"),
44171
+ amount: import_zod5.z.string().describe("Human-readable amount")
43967
44172
  }),
43968
44173
  invoke: async (_ctx, input2) => {
43969
44174
  if (!this.swapClient) {
@@ -44016,9 +44221,9 @@ var SwapActionProvider = class extends ActionProvider {
44016
44221
  return {
44017
44222
  name: "get_token_price",
44018
44223
  description: "Get current USD price for a token",
44019
- schema: import_zod6.z.object({
44020
- chain: import_zod6.z.string().describe("Chain name (e.g., ethereum)"),
44021
- token: import_zod6.z.string().describe("Token symbol or address")
44224
+ schema: import_zod5.z.object({
44225
+ chain: import_zod5.z.string().describe("Chain name (e.g., ethereum)"),
44226
+ token: import_zod5.z.string().describe("Token symbol or address")
44022
44227
  }),
44023
44228
  invoke: async (_ctx, input2) => {
44024
44229
  if (!this.swapClient) {
@@ -44067,10 +44272,10 @@ var SecurityActionProvider = class extends ActionProvider {
44067
44272
  return {
44068
44273
  name: "validate_transaction",
44069
44274
  description: "Validate an address or transaction for security",
44070
- schema: import_zod7.z.object({
44071
- address: import_zod7.z.string().describe("Address to validate"),
44072
- assetId: import_zod7.z.string().optional().describe("Asset ID"),
44073
- type: import_zod7.z.string().optional().describe("Validation type")
44275
+ schema: import_zod6.z.object({
44276
+ address: import_zod6.z.string().describe("Address to validate"),
44277
+ assetId: import_zod6.z.string().optional().describe("Asset ID"),
44278
+ type: import_zod6.z.string().optional().describe("Validation type")
44074
44279
  }),
44075
44280
  invoke: async (ctx, input2) => {
44076
44281
  return this.getService(ctx).validate(input2);
@@ -44081,8 +44286,8 @@ var SecurityActionProvider = class extends ActionProvider {
44081
44286
  return {
44082
44287
  name: "get_asset_info",
44083
44288
  description: "Get detailed information about an asset",
44084
- schema: import_zod7.z.object({
44085
- assetId: import_zod7.z.string().describe("Asset ID (e.g., c60_t0xA0b8...)")
44289
+ schema: import_zod6.z.object({
44290
+ assetId: import_zod6.z.string().describe("Asset ID (e.g., c60_t0xA0b8...)")
44086
44291
  }),
44087
44292
  invoke: async (ctx, input2) => {
44088
44293
  return this.getService(ctx).getAssetInfo(input2.assetId);
@@ -44107,12 +44312,12 @@ var HistoryActionProvider = class extends ActionProvider {
44107
44312
  return {
44108
44313
  name: "get_transaction_history",
44109
44314
  description: "Get transaction history for an address",
44110
- schema: import_zod8.z.object({
44111
- address: import_zod8.z.string().describe("Wallet address"),
44112
- chain: import_zod8.z.string().optional().describe("Chain name"),
44113
- limit: import_zod8.z.coerce.number().optional().default(20).describe("Max results"),
44114
- from: import_zod8.z.string().optional().describe("Start date (ISO)"),
44115
- to: import_zod8.z.string().optional().describe("End date (ISO)")
44315
+ schema: import_zod7.z.object({
44316
+ address: import_zod7.z.string().describe("Wallet address"),
44317
+ chain: import_zod7.z.string().optional().describe("Chain name"),
44318
+ limit: import_zod7.z.coerce.number().optional().default(20).describe("Max results"),
44319
+ from: import_zod7.z.string().optional().describe("Start date (ISO)"),
44320
+ to: import_zod7.z.string().optional().describe("End date (ISO)")
44116
44321
  }),
44117
44322
  invoke: async (ctx, input2) => {
44118
44323
  return this.getService(ctx).getHistory(input2);
@@ -44123,9 +44328,9 @@ var HistoryActionProvider = class extends ActionProvider {
44123
44328
  return {
44124
44329
  name: "get_transaction_details",
44125
44330
  description: "Get details of a specific transaction by hash",
44126
- schema: import_zod8.z.object({
44127
- hash: import_zod8.z.string().describe("Transaction hash"),
44128
- chain: import_zod8.z.string().describe("Chain name")
44331
+ schema: import_zod7.z.object({
44332
+ hash: import_zod7.z.string().describe("Transaction hash"),
44333
+ chain: import_zod7.z.string().describe("Chain name")
44129
44334
  }),
44130
44335
  invoke: async (ctx, input2) => {
44131
44336
  const details = await this.getService(ctx).getByHash(input2.hash, input2.chain);
@@ -44138,10 +44343,10 @@ var HistoryActionProvider = class extends ActionProvider {
44138
44343
  return {
44139
44344
  name: "get_swap_history",
44140
44345
  description: "Get swap transaction history",
44141
- schema: import_zod8.z.object({
44142
- address: import_zod8.z.string().describe("Wallet address"),
44143
- chain: import_zod8.z.string().optional().describe("Chain name"),
44144
- limit: import_zod8.z.coerce.number().optional().default(20).describe("Max results")
44346
+ schema: import_zod7.z.object({
44347
+ address: import_zod7.z.string().describe("Wallet address"),
44348
+ chain: import_zod7.z.string().optional().describe("Chain name"),
44349
+ limit: import_zod7.z.coerce.number().optional().default(20).describe("Max results")
44145
44350
  }),
44146
44351
  invoke: async (ctx, input2) => {
44147
44352
  return this.getService(ctx).getSwapHistory(input2);
@@ -44171,11 +44376,11 @@ var AlertActionProvider = class extends ActionProvider {
44171
44376
  return {
44172
44377
  name: "create_alert",
44173
44378
  description: "Create a price alert for a token. Triggers when price goes above or below a target.",
44174
- schema: import_zod9.z.object({
44175
- tokenId: import_zod9.z.string().describe('Token ID (e.g., "c60" for ETH, "c20000714_t0x55d3..." for BSC USDT)'),
44176
- chainKey: import_zod9.z.string().describe('Chain key (e.g., "ethereum", "bsc")'),
44177
- condition: import_zod9.z.enum(["above", "below"]).describe("Alert when price goes above or below target"),
44178
- targetPrice: import_zod9.z.coerce.number().positive().describe("Target price in USD")
44379
+ schema: import_zod8.z.object({
44380
+ tokenId: import_zod8.z.string().describe('Token ID (e.g., "c60" for ETH, "c20000714_t0x55d3..." for BSC USDT)'),
44381
+ chainKey: import_zod8.z.string().describe('Chain key (e.g., "ethereum", "bsc")'),
44382
+ condition: import_zod8.z.enum(["above", "below"]).describe("Alert when price goes above or below target"),
44383
+ targetPrice: import_zod8.z.coerce.number().positive().describe("Target price in USD")
44179
44384
  }),
44180
44385
  invoke: async (_ctx, input2) => {
44181
44386
  try {
@@ -44200,8 +44405,8 @@ var AlertActionProvider = class extends ActionProvider {
44200
44405
  return {
44201
44406
  name: "list_alerts",
44202
44407
  description: "List all price alerts",
44203
- schema: import_zod9.z.object({
44204
- activeOnly: import_zod9.z.boolean().optional().default(false).describe("Only show active alerts")
44408
+ schema: import_zod8.z.object({
44409
+ activeOnly: import_zod8.z.boolean().optional().default(false).describe("Only show active alerts")
44205
44410
  }),
44206
44411
  invoke: async (_ctx, input2) => {
44207
44412
  const alerts = input2.activeOnly ? await this.alertStore.getActive() : await this.alertStore.list();
@@ -44213,7 +44418,7 @@ var AlertActionProvider = class extends ActionProvider {
44213
44418
  return {
44214
44419
  name: "check_alerts",
44215
44420
  description: "Check active alerts against current prices and trigger any that match",
44216
- schema: import_zod9.z.object({}),
44421
+ schema: import_zod8.z.object({}),
44217
44422
  invoke: async (_ctx, _input) => {
44218
44423
  const activeAlerts = await this.alertStore.getActive();
44219
44424
  const triggered = [];
@@ -44246,8 +44451,8 @@ var AlertActionProvider = class extends ActionProvider {
44246
44451
  return {
44247
44452
  name: "delete_alert",
44248
44453
  description: "Delete a price alert by ID",
44249
- schema: import_zod9.z.object({
44250
- id: import_zod9.z.string().describe("Alert ID to delete")
44454
+ schema: import_zod8.z.object({
44455
+ id: import_zod8.z.string().describe("Alert ID to delete")
44251
44456
  }),
44252
44457
  invoke: async (_ctx, input2) => {
44253
44458
  const deleted = await this.alertStore.delete(input2.id);
@@ -44260,31 +44465,25 @@ var AlertActionProvider = class extends ActionProvider {
44260
44465
  }
44261
44466
  };
44262
44467
  var AgentWalletActionProvider = class extends ActionProvider {
44263
- constructor(blockchainClient, agentWallet) {
44468
+ constructor(blockchainClient, walletContext) {
44264
44469
  super();
44265
44470
  this.blockchainClient = blockchainClient;
44266
- this.agentWallet = agentWallet;
44471
+ this.walletContext = walletContext;
44267
44472
  }
44268
44473
  name = "agent-wallet";
44269
- description = "Derive addresses and read native balances for the HD wallet at ~/.twak/wallet.json. The wallet is unlocked once at MCP server startup; these actions read the in-memory wallet directly and never require a password.";
44474
+ description = "Derive addresses and read native balances for the active wallet (local HD or WalletConnect-paired). Returns the address that will actually sign \u2014 call these to surface the user-facing wallet identity.";
44270
44475
  getActions() {
44271
44476
  return [this.getAddress(), this.listAddresses(), this.walletBalance()];
44272
44477
  }
44273
44478
  requireWallet() {
44274
- if (!this.agentWallet) {
44275
- throw new WalletError(
44276
- "WALLET_NOT_FOUND",
44277
- "Agent wallet not loaded. Create one with `twak init` (CLI) and restart the MCP server so it can unlock the wallet at startup."
44278
- );
44279
- }
44280
- return this.agentWallet;
44479
+ return this.walletContext.activeWallet();
44281
44480
  }
44282
44481
  getAddress() {
44283
44482
  return {
44284
44483
  name: "get_address",
44285
- description: "Derive the HD wallet's address for a specific chain.",
44286
- schema: import_zod10.z.object({
44287
- chainKey: import_zod10.z.string().describe('Chain key (e.g., "ethereum", "bsc", "solana")')
44484
+ description: "Get the active wallet's address for a specific chain.",
44485
+ schema: import_zod9.z.object({
44486
+ chainKey: import_zod9.z.string().describe('Chain key (e.g., "ethereum", "bsc", "solana")')
44288
44487
  }),
44289
44488
  invoke: async (_ctx, input2) => {
44290
44489
  const wallet = this.requireWallet();
@@ -44296,8 +44495,8 @@ var AgentWalletActionProvider = class extends ActionProvider {
44296
44495
  listAddresses() {
44297
44496
  return {
44298
44497
  name: "list_addresses",
44299
- description: "Derive the HD wallet's addresses for every supported chain. Returns one address per chain.",
44300
- schema: import_zod10.z.object({}),
44498
+ description: "Get the active wallet's addresses for every supported chain. Returns one address per chain.",
44499
+ schema: import_zod9.z.object({}),
44301
44500
  invoke: async () => {
44302
44501
  const wallet = this.requireWallet();
44303
44502
  const addresses = await wallet.getAddresses();
@@ -44308,9 +44507,9 @@ var AgentWalletActionProvider = class extends ActionProvider {
44308
44507
  walletBalance() {
44309
44508
  return {
44310
44509
  name: "wallet_balance",
44311
- description: "Read the native-coin balance (BNB on BSC, ETH on Ethereum / Base / X-Layer, SOL on Solana) for the HD wallet on a specific chain. For ERC-20 / SPL token balances, use `get_balance`.",
44312
- schema: import_zod10.z.object({
44313
- chainKey: import_zod10.z.string().describe('Chain key (e.g., "ethereum", "bsc")')
44510
+ description: "Read the native-coin balance (BNB on BSC, ETH on Ethereum / Base / Arbitrum, SOL on Solana, etc.) for the active wallet on a specific chain. For ERC-20 / SPL token balances, use `get_balance`.",
44511
+ schema: import_zod9.z.object({
44512
+ chainKey: import_zod9.z.string().describe('Chain key (e.g., "ethereum", "bsc")')
44314
44513
  }),
44315
44514
  invoke: async (_ctx, input2) => {
44316
44515
  const wallet = this.requireWallet();
@@ -44330,10 +44529,10 @@ var AgentWalletActionProvider = class extends ActionProvider {
44330
44529
  }
44331
44530
  };
44332
44531
  var TokenActionProvider = class extends ActionProvider {
44333
- constructor(blockchainClient, agentWallet) {
44532
+ constructor(blockchainClient, walletContext) {
44334
44533
  super();
44335
44534
  this.blockchainClient = blockchainClient;
44336
- this.agentWallet = agentWallet;
44535
+ this.walletContext = walletContext;
44337
44536
  }
44338
44537
  name = "token";
44339
44538
  description = "Chain-agnostic token operations \u2014 balances, approvals, and transfers";
@@ -44350,9 +44549,9 @@ var TokenActionProvider = class extends ActionProvider {
44350
44549
  return {
44351
44550
  name: "token_balance",
44352
44551
  description: "Get balance for a specific token or native asset by asset ID",
44353
- schema: import_zod11.z.object({
44354
- assetId: import_zod11.z.string().describe('Token ID (e.g., "c60" for ETH, "c20000714_t0x55d3..." for BSC USDT)'),
44355
- address: import_zod11.z.string().describe("Wallet address to check balance for")
44552
+ schema: import_zod10.z.object({
44553
+ assetId: import_zod10.z.string().describe('Token ID (e.g., "c60" for ETH, "c20000714_t0x55d3..." for BSC USDT)'),
44554
+ address: import_zod10.z.string().describe("Wallet address to check balance for")
44356
44555
  }),
44357
44556
  invoke: async (_ctx, input2) => {
44358
44557
  if (!this.blockchainClient) {
@@ -44366,29 +44565,64 @@ var TokenActionProvider = class extends ActionProvider {
44366
44565
  transferToken() {
44367
44566
  return {
44368
44567
  name: "transfer_token",
44369
- description: "Transfer a token or native asset to a destination address",
44370
- schema: import_zod11.z.object({
44371
- assetId: import_zod11.z.string().describe("Token ID to transfer"),
44372
- to: import_zod11.z.string().describe("Destination address"),
44373
- amount: import_zod11.z.string().describe("Amount to transfer (in smallest unit)"),
44374
- memo: import_zod11.z.string().optional().describe("Optional memo/note")
44568
+ description: "Transfer a token or native asset to a destination address. Works in both local and WalletConnect modes for EVM chains; non-EVM chains (e.g. Solana) require local wallet mode.",
44569
+ schema: import_zod10.z.object({
44570
+ assetId: import_zod10.z.string().describe("Token ID to transfer"),
44571
+ to: import_zod10.z.string().describe("Destination address"),
44572
+ amount: import_zod10.z.string().describe("Amount to transfer (in smallest unit)"),
44573
+ memo: import_zod10.z.string().optional().describe("Optional memo/note")
44375
44574
  }),
44376
44575
  invoke: async (_ctx, input2) => {
44377
- if (!this.blockchainClient) {
44378
- throw new WalletError("WALLET_NOT_FOUND", "BlockchainClient not available");
44576
+ const wallet = this.walletContext.activeWallet();
44577
+ if (wallet instanceof AgentWalletProvider) {
44578
+ if (!this.blockchainClient) {
44579
+ throw new WalletError("WALLET_NOT_FOUND", "BlockchainClient not available");
44580
+ }
44581
+ const hdWallet = wallet.getSigner().getHDWallet();
44582
+ const result = await this.blockchainClient.transfer({
44583
+ assetId: input2.assetId,
44584
+ to: input2.to,
44585
+ amount: input2.amount,
44586
+ memo: input2.memo,
44587
+ hdWallet
44588
+ });
44589
+ return { hash: result.hash, chainKey: result.chainKey };
44379
44590
  }
44380
- if (!this.agentWallet) {
44381
- throw new WalletError("WALLET_NOT_FOUND", "Wallet not connected");
44591
+ const { coinId, contractAddress } = parseTokenId(input2.assetId);
44592
+ const chainKey = coinTypeToChainKey(coinId);
44593
+ if (!chainKey) {
44594
+ throw new WalletError("CHAIN_UNSUPPORTED", `Unknown coin type: ${coinId}`);
44382
44595
  }
44383
- const hdWallet = this.agentWallet.getSigner().getHDWallet();
44384
- const result = await this.blockchainClient.transfer({
44385
- assetId: input2.assetId,
44386
- to: input2.to,
44387
- amount: input2.amount,
44388
- memo: input2.memo,
44389
- hdWallet
44390
- });
44391
- return { hash: result.hash, chainKey: result.chainKey };
44596
+ const chain2 = getChainById(chainKey);
44597
+ if (!chain2 || chain2.namespace !== "eip155") {
44598
+ throw new WalletError(
44599
+ "CHAIN_UNSUPPORTED",
44600
+ `transfer_token on ${chainKey} is not supported in WalletConnect mode (EVM-only). Switch to local wallet mode for ${chainKey} transfers.`
44601
+ );
44602
+ }
44603
+ const caipChainId = `${chain2.namespace}:${chain2.chainId}`;
44604
+ let txHash;
44605
+ if (contractAddress) {
44606
+ const selector = "0xa9059cbb";
44607
+ const paddedTo = input2.to.replace(/^0x/, "").toLowerCase().padStart(64, "0");
44608
+ const paddedAmount = BigInt(input2.amount).toString(16).padStart(64, "0");
44609
+ const data = selector + paddedTo + paddedAmount;
44610
+ txHash = await wallet.sendTransaction({
44611
+ chainId: caipChainId,
44612
+ to: contractAddress,
44613
+ data,
44614
+ value: "0x0"
44615
+ });
44616
+ } else {
44617
+ const valueHex = "0x" + BigInt(input2.amount).toString(16);
44618
+ txHash = await wallet.sendTransaction({
44619
+ chainId: caipChainId,
44620
+ to: input2.to,
44621
+ data: "0x",
44622
+ value: valueHex
44623
+ });
44624
+ }
44625
+ return { hash: txHash, chainKey };
44392
44626
  }
44393
44627
  };
44394
44628
  }
@@ -44396,27 +44630,31 @@ var TokenActionProvider = class extends ActionProvider {
44396
44630
  return {
44397
44631
  name: "approve_token",
44398
44632
  description: `Approve a spender to use tokens on your behalf (ERC-20 approve). SAFETY: when amount is "unlimited" (or the MaxUint256 decimal string), you MUST surface the risk to the user and get their explicit consent before calling this tool \u2014 unlimited approvals grant the spender access to ALL of the user's tokens of this type, including future transfers, and have been used in many drainer attacks. Prefer a finite amount equal to the immediate need. Use revoke_token_approval (or amount: "0") to revoke an existing approval.`,
44399
- schema: import_zod11.z.object({
44400
- assetId: import_zod11.z.string().describe("Token ID to approve"),
44401
- spender: import_zod11.z.string().describe("Spender address to approve"),
44402
- amount: import_zod11.z.string().describe('Amount to approve (in smallest unit, or "unlimited")')
44633
+ schema: import_zod10.z.object({
44634
+ assetId: import_zod10.z.string().describe("Token ID to approve"),
44635
+ spender: import_zod10.z.string().describe("Spender address to approve"),
44636
+ amount: import_zod10.z.string().describe('Amount to approve (in smallest unit, or "unlimited")')
44403
44637
  }),
44404
44638
  invoke: async (_ctx, input2) => {
44405
- if (!this.blockchainClient) {
44406
- throw new WalletError("WALLET_NOT_FOUND", "BlockchainClient not available");
44639
+ const wallet = this.walletContext.activeWallet();
44640
+ const { coinId, contractAddress } = parseTokenId(input2.assetId);
44641
+ if (!contractAddress) {
44642
+ throw new WalletError("VALIDATION_ERROR", "Cannot approve native token (no contract).");
44407
44643
  }
44408
- if (!this.agentWallet) {
44409
- throw new WalletError("WALLET_NOT_FOUND", "Wallet not connected");
44644
+ const chainKey = coinTypeToChainKey(coinId);
44645
+ if (!chainKey) {
44646
+ throw new WalletError("CHAIN_UNSUPPORTED", `Unsupported coin type: ${coinId}`);
44647
+ }
44648
+ const chain2 = getChainById(chainKey);
44649
+ if (!chain2 || chain2.namespace !== "eip155") {
44650
+ throw new WalletError(
44651
+ "CHAIN_UNSUPPORTED",
44652
+ `approve_token on ${chainKey} is not supported \u2014 ERC-20 approvals are EVM-only.`
44653
+ );
44410
44654
  }
44411
- const hdWallet = this.agentWallet.getSigner().getHDWallet();
44412
44655
  const amount = input2.amount === "unlimited" ? "115792089237316195423570985008687907853269984665640564039457584007913129639935" : input2.amount;
44413
- const result = await this.blockchainClient.approve({
44414
- assetId: input2.assetId,
44415
- spender: input2.spender,
44416
- amount,
44417
- hdWallet
44418
- });
44419
- return { hash: result.hash, chainKey: result.chainKey };
44656
+ const result = await wallet.approveErc20(chainKey, contractAddress, input2.spender, amount);
44657
+ return { hash: result.txHash, chainKey };
44420
44658
  }
44421
44659
  };
44422
44660
  }
@@ -44424,27 +44662,31 @@ var TokenActionProvider = class extends ActionProvider {
44424
44662
  return {
44425
44663
  name: "revoke_token_approval",
44426
44664
  description: 'Revoke an ERC-20 token approval by setting the spender allowance to 0. Use this to remove a previously-granted allowance \u2014 especially important after unlimited approvals or any approval you no longer need. Applies only to ERC-20 tokens (asset IDs of form "c{coinId}_t{contract}"); native tokens have no approvals to revoke. This is the inverse of approve_token.',
44427
- schema: import_zod11.z.object({
44428
- assetId: import_zod11.z.string().describe(
44665
+ schema: import_zod10.z.object({
44666
+ assetId: import_zod10.z.string().describe(
44429
44667
  'ERC-20 token ID to revoke approval for (e.g., "c60_t0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" for USDC on Ethereum)'
44430
44668
  ),
44431
- spender: import_zod11.z.string().describe("Spender address whose allowance should be set to 0")
44669
+ spender: import_zod10.z.string().describe("Spender address whose allowance should be set to 0")
44432
44670
  }),
44433
44671
  invoke: async (_ctx, input2) => {
44434
- if (!this.blockchainClient) {
44435
- throw new WalletError("WALLET_NOT_FOUND", "BlockchainClient not available");
44672
+ const wallet = this.walletContext.activeWallet();
44673
+ const { coinId, contractAddress } = parseTokenId(input2.assetId);
44674
+ if (!contractAddress) {
44675
+ throw new WalletError("VALIDATION_ERROR", "Native tokens have no approval to revoke.");
44436
44676
  }
44437
- if (!this.agentWallet) {
44438
- throw new WalletError("WALLET_NOT_FOUND", "Wallet not connected");
44677
+ const chainKey = coinTypeToChainKey(coinId);
44678
+ if (!chainKey) {
44679
+ throw new WalletError("CHAIN_UNSUPPORTED", `Unsupported coin type: ${coinId}`);
44439
44680
  }
44440
- const hdWallet = this.agentWallet.getSigner().getHDWallet();
44441
- const result = await this.blockchainClient.approve({
44442
- assetId: input2.assetId,
44443
- spender: input2.spender,
44444
- amount: "0",
44445
- hdWallet
44446
- });
44447
- return { hash: result.hash, chainKey: result.chainKey };
44681
+ const chain2 = getChainById(chainKey);
44682
+ if (!chain2 || chain2.namespace !== "eip155") {
44683
+ throw new WalletError(
44684
+ "CHAIN_UNSUPPORTED",
44685
+ `revoke_token_approval on ${chainKey} is not supported \u2014 ERC-20 approvals are EVM-only.`
44686
+ );
44687
+ }
44688
+ const result = await wallet.approveErc20(chainKey, contractAddress, input2.spender, "0");
44689
+ return { hash: result.txHash, chainKey };
44448
44690
  }
44449
44691
  };
44450
44692
  }
@@ -44452,10 +44694,10 @@ var TokenActionProvider = class extends ActionProvider {
44452
44694
  return {
44453
44695
  name: "check_allowance",
44454
44696
  description: "Check the token allowance for a spender address",
44455
- schema: import_zod11.z.object({
44456
- assetId: import_zod11.z.string().describe("Token ID to check allowance for"),
44457
- owner: import_zod11.z.string().describe("Token owner address"),
44458
- spender: import_zod11.z.string().describe("Spender address to check")
44697
+ schema: import_zod10.z.object({
44698
+ assetId: import_zod10.z.string().describe("Token ID to check allowance for"),
44699
+ owner: import_zod10.z.string().describe("Token owner address"),
44700
+ spender: import_zod10.z.string().describe("Spender address to check")
44459
44701
  }),
44460
44702
  invoke: async (_ctx, input2) => {
44461
44703
  if (!this.blockchainClient) {
@@ -44493,8 +44735,8 @@ var MarketActionProvider = class extends ActionProvider {
44493
44735
  return {
44494
44736
  name: "get_trending_tokens",
44495
44737
  description: "Get a list of trending tokens by market activity",
44496
- schema: import_zod12.z.object({
44497
- limit: import_zod12.z.coerce.number().optional().default(20).describe("Number of tokens to return (default 20)")
44738
+ schema: import_zod11.z.object({
44739
+ limit: import_zod11.z.coerce.number().optional().default(20).describe("Number of tokens to return (default 20)")
44498
44740
  }),
44499
44741
  invoke: async (ctx, input2) => {
44500
44742
  const tokens = await this.getService(ctx).getAssetListings({ limit: input2.limit });
@@ -44506,8 +44748,8 @@ var MarketActionProvider = class extends ActionProvider {
44506
44748
  return {
44507
44749
  name: "check_token_risk",
44508
44750
  description: "Check security and risk information for a token (honeypot detection, audit status, freeze authority)",
44509
- schema: import_zod12.z.object({
44510
- assetId: import_zod12.z.string().describe("Asset ID (e.g., c60_t0xA0b8... for ERC-20, c60 for native)")
44751
+ schema: import_zod11.z.object({
44752
+ assetId: import_zod11.z.string().describe("Asset ID (e.g., c60_t0xA0b8... for ERC-20, c60 for native)")
44511
44753
  }),
44512
44754
  invoke: async (ctx, input2) => {
44513
44755
  const status = await this.getService(ctx).checkCoinStatus(input2.assetId);
@@ -44613,9 +44855,9 @@ var OnrampActionProvider = class extends ActionProvider {
44613
44855
  // TransferActionProvider for the off-ramp payout path (confirm_offramp_transfer).
44614
44856
  // Off-ramp sends crypto to a third-party deposit address, so the cap is
44615
44857
  // strictly more important here than for peer-to-peer transfers.
44616
- constructor(walletProvider, priceLookup, maxTransferUsd = DEFAULT_MAX_OFFRAMP_USD) {
44858
+ constructor(walletContext, priceLookup, maxTransferUsd = DEFAULT_MAX_OFFRAMP_USD) {
44617
44859
  super();
44618
- this.walletProvider = walletProvider;
44860
+ this.walletContext = walletContext;
44619
44861
  this.priceLookup = priceLookup;
44620
44862
  this.maxTransferUsd = maxTransferUsd;
44621
44863
  }
@@ -44632,12 +44874,6 @@ var OnrampActionProvider = class extends ActionProvider {
44632
44874
  }
44633
44875
  async resolveAddress(providedAddress, cryptoAsset) {
44634
44876
  if (providedAddress) return providedAddress;
44635
- if (!this.walletProvider) {
44636
- throw new TransferError(
44637
- "WALLET_NOT_FOUND",
44638
- "walletAddress not provided and no wallet provider is configured. Pass walletAddress explicitly or configure an agent wallet."
44639
- );
44640
- }
44641
44877
  if (!cryptoAsset) {
44642
44878
  throw new TransferError(
44643
44879
  "VALIDATION_ERROR",
@@ -44651,19 +44887,19 @@ var OnrampActionProvider = class extends ActionProvider {
44651
44887
  `Cannot determine chain for asset ${cryptoAsset}. Pass walletAddress explicitly.`
44652
44888
  );
44653
44889
  }
44654
- return this.walletProvider.getAddress(chainKey);
44890
+ return this.walletContext.activeWallet().getAddress(chainKey);
44655
44891
  }
44656
44892
  getOnrampQuote() {
44657
44893
  return {
44658
44894
  name: "get_onramp_quote",
44659
44895
  description: "Step 1 of buying crypto with fiat: get quotes from available payment providers (MoonPay, Transak, etc.) for a given fiat amount. Returns an array of quotes each with a quoteId \u2014 pass one to get_onramp_url to generate the checkout redirect.",
44660
- schema: import_zod13.z.object({
44661
- fiatAmount: import_zod13.z.coerce.number().positive().describe("Amount of fiat currency to spend (e.g., 100 for $100)."),
44662
- fiatCurrency: import_zod13.z.string().length(3).default("USD").describe("Fiat currency as a 3-letter ISO 4217 code (e.g., USD, EUR, GBP)."),
44663
- cryptoAsset: import_zod13.z.string().describe(
44896
+ schema: import_zod12.z.object({
44897
+ fiatAmount: import_zod12.z.coerce.number().positive().describe("Amount of fiat currency to spend (e.g., 100 for $100)."),
44898
+ fiatCurrency: import_zod12.z.string().length(3).default("USD").describe("Fiat currency as a 3-letter ISO 4217 code (e.g., USD, EUR, GBP)."),
44899
+ cryptoAsset: import_zod12.z.string().describe(
44664
44900
  "Target crypto asset ID. Format: c<coinId> for native coins (e.g., c60 for ETH, c0 for BTC, c501 for SOL) or c<coinId>_t<contract> for tokens (e.g., c60_t0xA0b8... for USDC on Ethereum)."
44665
44901
  ),
44666
- walletAddress: import_zod13.z.string().optional().describe(
44902
+ walletAddress: import_zod12.z.string().optional().describe(
44667
44903
  "Destination wallet address where the purchased crypto will be sent. OMIT to auto-derive from the configured wallet using cryptoAsset's chain \u2014 this is the safe default and prevents sending funds to a stranger by mistake. Only supply this when operating on behalf of a different user."
44668
44904
  )
44669
44905
  }),
@@ -44697,12 +44933,12 @@ var OnrampActionProvider = class extends ActionProvider {
44697
44933
  return {
44698
44934
  name: "get_onramp_url",
44699
44935
  description: "Step 2 of buying crypto with fiat: exchange a quoteId from get_onramp_quote for a browser redirect URL. The user opens that URL to complete KYC and payment with the provider; on success, the provider sends the purchased crypto to the destination address. After this tool returns a URL, poll the balance tool named in the `followUp` field to observe the incoming funds \u2014 there is no background polling, webhook, or push notification.",
44700
- schema: import_zod13.z.object({
44701
- quoteId: import_zod13.z.string().describe("Quote ID returned by get_onramp_quote."),
44702
- walletAddress: import_zod13.z.string().optional().describe(
44936
+ schema: import_zod12.z.object({
44937
+ quoteId: import_zod12.z.string().describe("Quote ID returned by get_onramp_quote."),
44938
+ walletAddress: import_zod12.z.string().optional().describe(
44703
44939
  "Destination wallet address. OMIT to auto-derive from the configured wallet \u2014 requires cryptoAsset so the chain can be determined. Only supply this when operating on behalf of a different user."
44704
44940
  ),
44705
- cryptoAsset: import_zod13.z.string().optional().describe(
44941
+ cryptoAsset: import_zod12.z.string().optional().describe(
44706
44942
  "Crypto asset ID that was used for the quote (e.g., c60 for ETH). Required when walletAddress is omitted so the chain can be derived for address lookup."
44707
44943
  )
44708
44944
  }),
@@ -44745,16 +44981,16 @@ var OnrampActionProvider = class extends ActionProvider {
44745
44981
  return {
44746
44982
  name: "get_offramp_quote",
44747
44983
  description: "Step 1 of selling crypto for fiat: get quotes from available providers for converting a crypto amount into fiat. Returns an array of quotes each with a quoteId \u2014 pass one to get_offramp_url to generate the checkout redirect for completing KYC and order creation. The provider page cannot sign the payout (no key access in the browser); the agent must broadcast it natively via confirm_offramp_transfer once the deposit address is known.",
44748
- schema: import_zod13.z.object({
44749
- cryptoAmount: import_zod13.z.coerce.number().positive().describe("Amount of crypto to sell (e.g., 0.1 for 0.1 ETH)."),
44750
- cryptoAsset: import_zod13.z.string().describe(
44984
+ schema: import_zod12.z.object({
44985
+ cryptoAmount: import_zod12.z.coerce.number().positive().describe("Amount of crypto to sell (e.g., 0.1 for 0.1 ETH)."),
44986
+ cryptoAsset: import_zod12.z.string().describe(
44751
44987
  "Crypto asset ID to sell. Format: c<coinId> for native coins (e.g., c60 for ETH, c0 for BTC) or c<coinId>_t<contract> for tokens."
44752
44988
  ),
44753
- fiatCurrency: import_zod13.z.string().length(3).default("USD").describe("Target fiat currency as a 3-letter ISO 4217 code (e.g., USD, EUR, GBP)."),
44754
- paymentMethod: import_zod13.z.enum(["ANY", "bank_transfer", "card"]).optional().default("ANY").describe(
44989
+ fiatCurrency: import_zod12.z.string().length(3).default("USD").describe("Target fiat currency as a 3-letter ISO 4217 code (e.g., USD, EUR, GBP)."),
44990
+ paymentMethod: import_zod12.z.enum(["ANY", "bank_transfer", "card"]).optional().default("ANY").describe(
44755
44991
  'Payout method. Default "ANY" is a sentinel that matches any provider \u2014 recommended unless you specifically need "card" or "bank_transfer". Sending an unrecognised value (or `bank_transfer` in regions without bank-payout coverage) silently returns an empty quotes array.'
44756
44992
  ),
44757
- walletAddress: import_zod13.z.string().optional().describe(
44993
+ walletAddress: import_zod12.z.string().optional().describe(
44758
44994
  "Source wallet address holding the crypto to be sold. OMIT to auto-derive from the configured wallet using cryptoAsset's chain \u2014 this is the safe default. Only supply this when operating on behalf of a different user."
44759
44995
  )
44760
44996
  }),
@@ -44797,12 +45033,12 @@ var OnrampActionProvider = class extends ActionProvider {
44797
45033
  return {
44798
45034
  name: "get_offramp_url",
44799
45035
  description: "Step 2 of selling crypto for fiat: exchange a quoteId from get_offramp_quote for a browser checkout URL. The user opens that URL to complete KYC and order creation with the provider; the provider then displays a deposit address (plus a memo/tag on chains that require one) and the exact amount to send. The agent must then call confirm_offramp_transfer with those values to sign and broadcast the payout natively \u2014 the browser has no access to wallet keys and cannot sign the transfer itself. After the payout broadcasts, poll the balance tool named in the `followUp` field to observe the outgoing tx. Fiat payout arrives separately via the provider.",
44800
- schema: import_zod13.z.object({
44801
- quoteId: import_zod13.z.string().describe("Quote ID returned by get_offramp_quote."),
44802
- walletAddress: import_zod13.z.string().optional().describe(
45036
+ schema: import_zod12.z.object({
45037
+ quoteId: import_zod12.z.string().describe("Quote ID returned by get_offramp_quote."),
45038
+ walletAddress: import_zod12.z.string().optional().describe(
44803
45039
  "Source wallet address. OMIT to auto-derive from the configured wallet \u2014 requires cryptoAsset so the chain can be determined. Only supply this when operating on behalf of a different user."
44804
45040
  ),
44805
- cryptoAsset: import_zod13.z.string().optional().describe(
45041
+ cryptoAsset: import_zod12.z.string().optional().describe(
44806
45042
  "Crypto asset ID that was used for the quote (e.g., c60 for ETH). Required when walletAddress is omitted so the chain can be derived for address lookup."
44807
45043
  )
44808
45044
  }),
@@ -44846,31 +45082,28 @@ var OnrampActionProvider = class extends ActionProvider {
44846
45082
  return {
44847
45083
  name: "confirm_offramp_transfer",
44848
45084
  description: "Step 3 of selling crypto for fiat: sign and broadcast the payout transaction to the provider's deposit address. Call this AFTER the user has completed KYC in the browser and can read the deposit address + exact amount the provider displayed. Works with any wallet provider that implements sendTransaction (the local agent wallet, or a WalletConnect-paired external wallet). EVM-only \u2014 for BTC / Solana / Cosmos off-ramps use the `twak onramp sell-confirm` CLI command instead. Returns a tx hash on success; fiat payout from the provider then arrives separately (no on-chain signal for that step).",
44849
- schema: import_zod13.z.object({
44850
- depositAddress: import_zod13.z.string().describe(
45085
+ schema: import_zod12.z.object({
45086
+ depositAddress: import_zod12.z.string().describe(
44851
45087
  "The deposit address displayed by the provider after KYC. This is a third-party address \u2014 verify it matches what the provider shows before calling."
44852
45088
  ),
44853
- cryptoAmount: import_zod13.z.string().describe(
45089
+ cryptoAmount: import_zod12.z.string().describe(
44854
45090
  'Exact amount to send in human-readable format (e.g., "0.1" for 0.1 ETH). Must match the amount the provider displays; under-sending will invalidate the sell order.'
44855
45091
  ),
44856
- cryptoAsset: import_zod13.z.string().describe(
45092
+ cryptoAsset: import_zod12.z.string().describe(
44857
45093
  "Crypto asset being sold. Format: c<coinId> for native coins (e.g., c60 for ETH) or c<coinId>_t<contract> for ERC-20 tokens."
44858
45094
  ),
44859
- decimals: import_zod13.z.coerce.number().optional().describe(
45095
+ decimals: import_zod12.z.coerce.number().optional().describe(
44860
45096
  "Token decimals. REQUIRED for ERC-20 assets (pass 6 for USDC/USDT, 18 for DAI, etc.) \u2014 omitting it will be rejected rather than silently defaulting to 18, because a wrong-decimals encode can over-pay the provider by orders of magnitude. Ignored for native coins."
44861
45097
  ),
44862
- memo: import_zod13.z.string().optional().describe(
45098
+ memo: import_zod12.z.string().optional().describe(
44863
45099
  "Memo / destination tag. Not used on EVM chains, so supplying one on EVM is a no-op warning. Exists in the schema for forward-compat with non-EVM chains."
44864
45100
  ),
44865
- quoteId: import_zod13.z.string().optional().describe(
45101
+ quoteId: import_zod12.z.string().optional().describe(
44866
45102
  "Quote ID from get_offramp_quote. Optional \u2014 included only for traceability in the response; tx details come from depositAddress / cryptoAmount."
44867
45103
  )
44868
45104
  }),
44869
45105
  invoke: async (_ctx, input2) => {
44870
- const signer = this.walletProvider;
44871
- if (!signer) {
44872
- throw new TransferError("WALLET_NOT_FOUND", "No wallet provider configured");
44873
- }
45106
+ const signer = this.walletContext.activeWallet();
44874
45107
  let parsed;
44875
45108
  try {
44876
45109
  parsed = parseTokenId(input2.cryptoAsset);
@@ -44973,11 +45206,11 @@ var OnrampActionProvider = class extends ActionProvider {
44973
45206
  }
44974
45207
  };
44975
45208
  var AutomateActionProvider = class extends ActionProvider {
44976
- constructor(automationStore, swapClient, walletProvider, blockchainClient) {
45209
+ constructor(automationStore, swapClient, walletContext, blockchainClient) {
44977
45210
  super();
44978
45211
  this.automationStore = automationStore;
44979
45212
  this.swapClient = swapClient;
44980
- this.walletProvider = walletProvider;
45213
+ this.walletContext = walletContext;
44981
45214
  this.blockchainClient = blockchainClient;
44982
45215
  }
44983
45216
  name = "automate";
@@ -44994,15 +45227,15 @@ var AutomateActionProvider = class extends ActionProvider {
44994
45227
  return {
44995
45228
  name: "create_automation",
44996
45229
  description: "Create a DCA (recurring buy) or limit order (buy/sell when price condition is met)",
44997
- schema: import_zod14.z.object({
44998
- type: import_zod14.z.enum(["dca", "limit"]).describe("Automation type: dca (recurring) or limit (price trigger)"),
44999
- fromToken: import_zod14.z.string().describe("Source token symbol or address"),
45000
- toToken: import_zod14.z.string().describe("Destination token symbol or address"),
45001
- chain: import_zod14.z.string().default("ethereum").describe("Chain name"),
45002
- amount: import_zod14.z.string().describe("Amount of fromToken per execution"),
45003
- intervalMs: import_zod14.z.coerce.number().optional().describe("Interval in milliseconds for DCA (e.g., 86400000 for daily)"),
45004
- targetPrice: import_zod14.z.coerce.number().optional().describe("Target price in USD for limit orders"),
45005
- condition: import_zod14.z.enum(["above", "below"]).optional().describe("Price condition for limit orders")
45230
+ schema: import_zod13.z.object({
45231
+ type: import_zod13.z.enum(["dca", "limit"]).describe("Automation type: dca (recurring) or limit (price trigger)"),
45232
+ fromToken: import_zod13.z.string().describe("Source token symbol or address"),
45233
+ toToken: import_zod13.z.string().describe("Destination token symbol or address"),
45234
+ chain: import_zod13.z.string().default("ethereum").describe("Chain name"),
45235
+ amount: import_zod13.z.string().describe("Amount of fromToken per execution"),
45236
+ intervalMs: import_zod13.z.coerce.number().optional().describe("Interval in milliseconds for DCA (e.g., 86400000 for daily)"),
45237
+ targetPrice: import_zod13.z.coerce.number().optional().describe("Target price in USD for limit orders"),
45238
+ condition: import_zod13.z.enum(["above", "below"]).optional().describe("Price condition for limit orders")
45006
45239
  }),
45007
45240
  invoke: async (_ctx, input2) => {
45008
45241
  if (input2.type === "dca" && !input2.intervalMs) {
@@ -45038,8 +45271,8 @@ var AutomateActionProvider = class extends ActionProvider {
45038
45271
  return {
45039
45272
  name: "list_automations",
45040
45273
  description: "List all active automation rules (DCA and limit orders)",
45041
- schema: import_zod14.z.object({
45042
- activeOnly: import_zod14.z.boolean().optional().default(true).describe("Only show active automations")
45274
+ schema: import_zod13.z.object({
45275
+ activeOnly: import_zod13.z.boolean().optional().default(true).describe("Only show active automations")
45043
45276
  }),
45044
45277
  invoke: async (_ctx, input2) => {
45045
45278
  const all = await this.automationStore.list();
@@ -45052,8 +45285,8 @@ var AutomateActionProvider = class extends ActionProvider {
45052
45285
  return {
45053
45286
  name: "delete_automation",
45054
45287
  description: "Delete (deactivate) an automation rule",
45055
- schema: import_zod14.z.object({
45056
- id: import_zod14.z.string().describe("Automation ID to delete")
45288
+ schema: import_zod13.z.object({
45289
+ id: import_zod13.z.string().describe("Automation ID to delete")
45057
45290
  }),
45058
45291
  invoke: async (_ctx, input2) => {
45059
45292
  const deleted = await this.automationStore.delete(input2.id);
@@ -45109,16 +45342,17 @@ var AutomateActionProvider = class extends ActionProvider {
45109
45342
  return {
45110
45343
  name: "run_automation_now",
45111
45344
  description: "Manually execute an automation immediately (runs the swap now regardless of schedule)",
45112
- schema: import_zod14.z.object({
45113
- id: import_zod14.z.string().describe("Automation ID to run")
45345
+ schema: import_zod13.z.object({
45346
+ id: import_zod13.z.string().describe("Automation ID to run")
45114
45347
  }),
45115
45348
  invoke: async (_ctx, input2) => {
45116
- if (!this.walletProvider || !this.swapClient) {
45349
+ if (!this.swapClient) {
45117
45350
  throw new AutomationError(
45118
- "WALLET_NOT_FOUND",
45119
- "Agent wallet and swap client required to execute automation"
45351
+ "VALIDATION_ERROR",
45352
+ "Swap client required to execute automation"
45120
45353
  );
45121
45354
  }
45355
+ const walletProvider = this.walletContext.activeWallet();
45122
45356
  const record2 = await this.automationStore.getById(input2.id);
45123
45357
  if (!record2) {
45124
45358
  return {
@@ -45151,7 +45385,7 @@ var AutomateActionProvider = class extends ActionProvider {
45151
45385
  };
45152
45386
  }
45153
45387
  const parsedAmount = parseAmount(record2.amount, fromTokenInfo.decimals);
45154
- const address = await this.walletProvider.getAddress(record2.chain);
45388
+ const address = await walletProvider.getAddress(record2.chain);
45155
45389
  const quote = await this.swapClient.getQuote({
45156
45390
  fromChain: record2.chain,
45157
45391
  fromToken: fromTokenInfo.address,
@@ -45168,7 +45402,7 @@ var AutomateActionProvider = class extends ActionProvider {
45168
45402
  const transactions = await this.swapClient.getSwapTransactions(route);
45169
45403
  let txHash = "";
45170
45404
  for (const txData of transactions) {
45171
- txHash = await this.sendSwapTx(txData, chainConfig, this.walletProvider);
45405
+ txHash = await this.sendSwapTx(txData, chainConfig, walletProvider);
45172
45406
  const isApproval = txData.transaction.type === "approval" || txData.transaction.type === "revokeApproval";
45173
45407
  await this.pollConfirmation(record2.chain, address, txHash, isApproval ? 6e4 : 12e4);
45174
45408
  }
@@ -45186,9 +45420,9 @@ var AutomateActionProvider = class extends ActionProvider {
45186
45420
  }
45187
45421
  };
45188
45422
  var X402ActionProvider = class extends ActionProvider {
45189
- constructor(agentWallet) {
45423
+ constructor(walletContext) {
45190
45424
  super();
45191
- this.agentWallet = agentWallet;
45425
+ this.walletContext = walletContext;
45192
45426
  }
45193
45427
  name = "x402";
45194
45428
  description = "HTTP requests to x402 payment-gated endpoints \u2014 pay and fetch in one call";
@@ -45198,13 +45432,13 @@ var X402ActionProvider = class extends ActionProvider {
45198
45432
  x402Quote() {
45199
45433
  return {
45200
45434
  name: "x402_quote",
45201
- description: 'Preview the payment requirements at an x402 payment-gated endpoint WITHOUT signing or paying. Returns the full list of payment options (each entry has chain, asset address, tokenName, amount, transferMethod, requiresApproval, and a pre-formatted `description` string). IMPORTANT: each entry in `accepts[]` is an INDEPENDENT payment option \u2014 a unique (chain, token, transfer method) triple. Do NOT merge entries by chain (e.g. "BSC USDC via EIP-3009 or Permit2" is wrong if BSC USDC is only offered via Permit2 and a separate BSC entry for a different token offers EIP-3009). Read each entry as its own row; use the per-entry `description` field verbatim when surfacing options to the user. Use this BEFORE x402_request to surface cost + method to the user, get their go-ahead, then call x402_request with the agreed maxPaymentAtomic plus optionally preferNetwork/preferMethod/preferAsset to disambiguate. The `preferred: true` entry is the one x402_request would automatically pick when no overrides are passed. INTERPRETING `requiresApproval`: `false` (EIP-3009, e.g. USDC on Base / Ethereum / X-Layer) \u2014 gasless. Sign the payment and you are done. No setup, no on-chain tx for the buyer, ever. `true` (Permit2, e.g. USDC on BSC) \u2014 the industry-standard Permit2 flow used by Uniswap, 1inch, and most major DeFi apps. THE FIRST x402_request to a Permit2 endpoint for a given token+chain broadcasts a one-time `approve(Permit2, MAX_UINT256)` tx (the buyer pays the gas \u2014 varies by chain: usually a few cents on BSC / Base / X-Layer, can be several dollars on Ethereum mainnet). The allowance granted is unlimited for the canonical Permit2 contract at 0x000000000022D473030F116dDEE9F6B43aC78BA3. After that, every future Permit2 payment for that token is signature-only (gasless). Revocable any time via `twak erc20 revoke`. The per-call spend is still capped at the request `maxPaymentAtomic` \u2014 unlimited allowance does NOT mean unlimited per-call spend. When `requiresApproval: true` AND the buyer has not used Permit2 for that token on that chain before, the agent SHOULD tell the user concretely what is about to happen (one-time approval on <chain> for <token>, gas paid in the chain native token, then the payment) before calling x402_request. BALANCE CHECK \u2014 AFTER this quote returns and BEFORE recommending any route or showing the menu to the user, check the wallet balance for every viable entry in `accepts`. For each entry: call `get_address(chainKey)` to find the wallet address on that chain, then `get_balance` for the entry\'s asset (or `wallet_balance(chainKey)` for native coins). Filter or annotate the menu \u2014 only recommend routes where the wallet has enough of the asset to cover `amount`. If a Permit2 entry needs a first-time approval (`requiresApproval: true`), also confirm the wallet holds some native chain gas (gas amount varies by chain). If no viable route has sufficient balance, tell the user upfront \u2014 do not present a menu that includes routes they cannot pay. This avoids the failure mode where the agent recommends a chain the user has no balance on and the user finds out only after committing. No wallet required \u2014 read-only HTTPS + 402 parse. HTTPS-only; private IPs and non-HTTPS schemes are rejected.',
45202
- schema: import_zod15.z.object({
45203
- url: import_zod15.z.string().describe(
45435
+ description: 'Preview the payment requirements at an x402 payment-gated endpoint WITHOUT signing or paying. Returns the full list of payment options (each entry has chain, asset address, tokenName, amount, transferMethod, requiresApproval, and a pre-formatted `description` string). CRITICAL \u2014 after this returns, ALWAYS surface ALL entries in `accepts[]` to the user (as a short numbered list: chain \xB7 token \xB7 amount \xB7 method \xB7 "preferred" tag if applicable) and ask which to use BEFORE calling x402_request. Do not silently pick the `preferred` entry just because it is marked preferred \u2014 that is the picker default if no override is passed, but the user deserves to see every option (other chains may have cheaper gas, the user may hold balance only on a specific chain, etc.). The only exception: if you have already done a balance check and only one entry is viable, you may proceed with that one after noting the others as "unavailable (no balance)". IMPORTANT: each entry in `accepts[]` is an INDEPENDENT payment option \u2014 a unique (chain, token, transfer method) triple. Do NOT merge entries by chain (e.g. "BSC USDC via EIP-3009 or Permit2" is wrong if BSC USDC is only offered via Permit2 and a separate BSC entry for a different token offers EIP-3009). Read each entry as its own row; use the per-entry `description` field verbatim when surfacing options to the user. Once the user picks, call x402_request with the agreed maxPaymentAtomic plus optionally preferNetwork/preferMethod/preferAsset to disambiguate. INTERPRETING `requiresApproval`: `false` (EIP-3009, e.g. USDC on Base / Ethereum) \u2014 gasless. Sign the payment and you are done. No setup, no on-chain tx for the buyer, ever. `true` (Permit2, e.g. USDC on BSC) \u2014 the industry-standard Permit2 flow used by Uniswap, 1inch, and most major DeFi apps. THE FIRST x402_request to a Permit2 endpoint for a given token+chain broadcasts a one-time `approve(Permit2, MAX_UINT256)` tx (the buyer pays the gas \u2014 usually a few cents on BSC / Base, more on Ethereum mainnet). The allowance granted is unlimited for the canonical Permit2 contract at 0x000000000022D473030F116dDEE9F6B43aC78BA3. After that, every future Permit2 payment for that token is signature-only (gasless). Revocable any time via `twak erc20 revoke`. The per-call spend is still capped at the request `maxPaymentAtomic` \u2014 unlimited allowance does NOT mean unlimited per-call spend. When `requiresApproval: true` AND the buyer has not used Permit2 for that token on that chain before, the agent SHOULD tell the user concretely what is about to happen (one-time approval on <chain> for <token>, gas paid in the chain native token, then the payment) before calling x402_request. BALANCE CHECK \u2014 AFTER this quote returns and BEFORE recommending any route or showing the menu to the user, check the wallet balance for every viable entry in `accepts`. For each entry: call `get_address(chainKey)` to find the wallet address on that chain, then `get_balance` for the entry\'s asset (or `wallet_balance(chainKey)` for native coins). Filter or annotate the menu \u2014 only recommend routes where the wallet has enough of the asset to cover `amount`. If a Permit2 entry needs a first-time approval (`requiresApproval: true`), also confirm the wallet holds some native chain gas (gas amount varies by chain). If no viable route has sufficient balance, tell the user upfront \u2014 do not present a menu that includes routes they cannot pay. This avoids the failure mode where the agent recommends a chain the user has no balance on and the user finds out only after committing. No wallet required \u2014 read-only HTTPS + 402 parse. HTTPS-only; private IPs and non-HTTPS schemes are rejected.',
45436
+ schema: import_zod14.z.object({
45437
+ url: import_zod14.z.string().describe(
45204
45438
  "Target endpoint URL. Must be HTTPS. Resolves via DNS; private/local IPs are rejected."
45205
45439
  ),
45206
- method: import_zod15.z.string().optional().default("GET").describe("HTTP verb (GET, POST, PUT, ...). Case-insensitive."),
45207
- body: import_zod15.z.string().optional().describe(
45440
+ method: import_zod14.z.string().optional().default("GET").describe("HTTP verb (GET, POST, PUT, ...). Case-insensitive."),
45441
+ body: import_zod14.z.string().optional().describe(
45208
45442
  "Pre-serialized request body. For JSON payloads, pass JSON.stringify(...) on the caller side."
45209
45443
  )
45210
45444
  }),
@@ -45214,35 +45448,33 @@ var X402ActionProvider = class extends ActionProvider {
45214
45448
  x402Request() {
45215
45449
  return {
45216
45450
  name: "x402_request",
45217
- description: "Make an HTTP request to an x402 payment-gated endpoint. Handles the full flow: sends the initial request, parses any 402 challenge, signs an EIP-3009 or Permit2 payment authorization with the agent wallet, retries with the X-Payment header. PAYMENT METHODS: x402 endpoints offer one or more options in their 402 response. EIP-3009 is gasless for the buyer (no setup, no approval) \u2014 preferred when available, e.g. USDC on Base / Ethereum / X-Layer. Permit2 needs a one-time approve(Permit2, MAX) tx on the chain (the buyer pays the gas \u2014 usually a few cents on BSC / Base / X-Layer, can be several dollars on Ethereum mainnet), then every future Permit2 payment for that token is signature-only. Permit2 is required when the token does not support EIP-3009 \u2014 e.g. USDC on BSC. SAFETY \u2014 autoApprove decision: When autoApprove=true (default), this action silently broadcasts the one-time Permit2 approval if your wallet has not yet approved the canonical Permit2 contract for that token. The approval is to the same Permit2 contract Uniswap, 1inch, and most major DeFi apps use \u2014 it is not exotic infrastructure. The approval alone does not move funds; each subsequent payment still needs a fresh signature. Revocable any time via `twak erc20 revoke`. For first-time use of an endpoint you SHOULD tell the user concretely what is about to happen (one-time approval on <chain> for <token>, gas paid in the chain native token, then the payment) and get their go-ahead before calling. After the approval lands, subsequent Permit2 payments for that token are free of further approvals. Set autoApprove=false to surface code: 'PERMIT2_APPROVAL_REQUIRED' instead of broadcasting \u2014 useful when the user is not around or you want to handle the approval out-of-band. maxPaymentAtomic is the user's pre-commitment to a maximum payment in the asset's smallest unit (e.g. \"10000\" = 0.01 USDC at 6 decimals). If the server demands more than this cap, the call returns code: 'PAYMENT_AMOUNT_EXCEEDED' \u2014 DO NOT retry with a higher cap without asking the user. BALANCE CHECK \u2014 Before calling this action, verify the wallet holds enough of the chosen asset on the chosen chain. Call `get_address(chainKey)` to find the wallet address, then `get_balance` for the asset (or `wallet_balance(chainKey)` for native coins). For Permit2 first-time use, also confirm the wallet holds some native chain gas (amount varies by chain). Surface insufficient balance to the user BEFORE calling this action \u2014 a failed payment mid-flow can leave the wallet in a half-state (e.g. Permit2 approval broadcast, then the transfer signature fails). The balance check should already have happened during the `x402_quote` flow that produced the menu; redo it here only if you skipped quote or the balance might have changed since. WALLET \u2014 Uses twak's LOCAL agent wallet (an HD wallet stored encrypted on disk at ~/.twak/wallet.json). NOT WalletConnect: x402 signs entirely locally \u2014 no external app prompt, no QR scan, no session pairing, no `connect_wallet` call required. Do NOT call `connect_wallet` or `get_wallet_status` before this action; they relate to the separate WalletConnect signer flow and aren't needed here. To retrieve the local wallet's address for a chain (e.g. to show the user which address will pay, or to call `wallet_balance` / `get_balance`), call `get_address` with the chain key (e.g. \"bsc\", \"base\"), or `list_addresses` for all chains at once. If the local wallet is missing the call returns code: 'WALLET_NOT_FOUND' \u2014 direct the user to `twak init` (CLI) to create one. HTTPS-only; non-HTTPS schemes and private IPs are rejected before any payment signing.",
45218
- schema: import_zod15.z.object({
45219
- url: import_zod15.z.string().describe(
45451
+ description: "Make an HTTP request to an x402 payment-gated endpoint. Handles the full flow: sends the initial request, parses any 402 challenge, signs an EIP-3009 or Permit2 payment authorization with the agent wallet, retries with the X-Payment header. PAYMENT METHODS: x402 endpoints offer one or more options in their 402 response. EIP-3009 is gasless for the buyer (no setup, no approval) \u2014 preferred when available, e.g. USDC on Base / Ethereum. Permit2 needs a one-time approve(Permit2, MAX) tx on the chain (the buyer pays the gas \u2014 usually a few cents on BSC / Base, more on Ethereum mainnet), then every future Permit2 payment for that token is signature-only. Permit2 is required when the token does not support EIP-3009 \u2014 e.g. USDC on BSC. SAFETY \u2014 autoApprove decision: When autoApprove=true (default), this action silently broadcasts the one-time Permit2 approval if your wallet has not yet approved the canonical Permit2 contract for that token. The approval is to the same Permit2 contract Uniswap, 1inch, and most major DeFi apps use \u2014 it is not exotic infrastructure. The approval alone does not move funds; each subsequent payment still needs a fresh signature. Revocable any time via `twak erc20 revoke`. For first-time use of an endpoint you SHOULD tell the user concretely what is about to happen (one-time approval on <chain> for <token>, gas paid in the chain native token, then the payment) and get their go-ahead before calling. After the approval lands, subsequent Permit2 payments for that token are free of further approvals. Set autoApprove=false to surface code: 'PERMIT2_APPROVAL_REQUIRED' instead of broadcasting \u2014 useful when the user is not around or you want to handle the approval out-of-band. maxPaymentAtomic is the user's pre-commitment to a maximum payment in the asset's smallest unit (e.g. \"10000\" = 0.01 USDC at 6 decimals). If the server demands more than this cap, the call returns code: 'PAYMENT_AMOUNT_EXCEEDED' \u2014 DO NOT retry with a higher cap without asking the user. BALANCE CHECK \u2014 Before calling this action, verify the wallet holds enough of the chosen asset on the chosen chain. Call `get_address(chainKey)` to find the wallet address, then `get_balance` for the asset (or `wallet_balance(chainKey)` for native coins). For Permit2 first-time use, also confirm the wallet holds some native chain gas (amount varies by chain). Surface insufficient balance to the user BEFORE calling this action \u2014 a failed payment mid-flow can leave the wallet in a half-state (e.g. Permit2 approval broadcast, then the transfer signature fails). The balance check should already have happened during the `x402_quote` flow that produced the menu; redo it here only if you skipped quote or the balance might have changed since. WALLET \u2014 Signs through whichever wallet mode is active for this session (local by default; switchable via `switch_wallet_mode`). If mode='local', signs in-process (no external prompt). If mode='walletconnect', signs over the WalletConnect relay \u2014 the user must approve each signature on their paired wallet. To retrieve the active wallet's address for a chain (e.g. to call `wallet_balance` / `get_balance`), call `get_address` with the chain key (e.g. \"bsc\", \"base\"), or `list_addresses` for all chains at once. HTTPS-only; non-HTTPS schemes and private IPs are rejected before any payment signing.",
45452
+ schema: import_zod14.z.object({
45453
+ url: import_zod14.z.string().describe(
45220
45454
  "Target endpoint URL. Must be HTTPS. Resolves via DNS; private/local IPs are rejected before any payment."
45221
45455
  ),
45222
- method: import_zod15.z.string().optional().default("GET").describe("HTTP verb (GET, POST, PUT, ...). Case-insensitive."),
45223
- body: import_zod15.z.string().optional().describe(
45456
+ method: import_zod14.z.string().optional().default("GET").describe("HTTP verb (GET, POST, PUT, ...). Case-insensitive."),
45457
+ body: import_zod14.z.string().optional().describe(
45224
45458
  "Pre-serialized request body. For JSON payloads, pass JSON.stringify(...) on the caller side."
45225
45459
  ),
45226
- maxPaymentAtomic: import_zod15.z.string().describe(
45460
+ maxPaymentAtomic: import_zod14.z.string().describe(
45227
45461
  `Hard cap on payment amount in the asset's smallest atomic unit (integer string, e.g. "10000" = 0.01 USDC at 6 decimals). If the server demands more than this cap, the call returns code: 'PAYMENT_AMOUNT_EXCEEDED' rather than overpaying. The user has pre-committed to this cap \u2014 do NOT raise it without asking the user.`
45228
45462
  ),
45229
- autoApprove: import_zod15.z.boolean().optional().default(true).describe(
45463
+ autoApprove: import_zod14.z.boolean().optional().default(true).describe(
45230
45464
  "When true (default), silently grants an UNLIMITED Permit2 allowance if the endpoint requires it \u2014 DANGEROUS, see SAFETY in the action description. Defaults true because MCP agents cannot interact with a prompt, but you MUST consult the user before relying on this on any endpoint you have not paid before. Set to false to surface code: 'PERMIT2_APPROVAL_REQUIRED' instead and route approval out-of-band."
45231
45465
  ),
45232
- preferNetwork: import_zod15.z.string().optional().describe(
45466
+ preferNetwork: import_zod14.z.string().optional().describe(
45233
45467
  `Optional override: restrict payment to this network when the endpoint offers multiple. Accepts either CAIP-2 ("eip155:8453") or a chain key ("base"). Case-insensitive. Use this when the user has a balance on a specific chain \u2014 without it, the picker chooses based on transfer-method preference (EIP-3009 over Permit2), which can land on a chain where the wallet has no funds. If no offered entry matches, the call returns code: 'NO_MATCHING_ROUTE' with the list of offered networks so you can re-ask the user.`
45234
45468
  ),
45235
- preferMethod: import_zod15.z.string().optional().describe(
45469
+ preferMethod: import_zod14.z.string().optional().describe(
45236
45470
  `Optional override: restrict payment to this transfer method ("eip3009", "permit2", or "permit2-exact"). Case-insensitive. Use this to force the gasless route (eip3009) or to insist on a specific Permit2 variant. If no offered entry matches, the call returns code: 'NO_MATCHING_ROUTE'.`
45237
45471
  ),
45238
- preferAsset: import_zod15.z.string().optional().describe(
45472
+ preferAsset: import_zod14.z.string().optional().describe(
45239
45473
  "Optional override: restrict payment to entries matching this asset. Matches the entry's `asset` field (contract address, exact, case-insensitive) or its `tokenName` field (substring, case-insensitive). Pass the exact value from the quote's output. Returns code: 'NO_MATCHING_ROUTE' when nothing matches."
45240
45474
  )
45241
45475
  }),
45242
45476
  invoke: async (_ctx, input2) => {
45243
- if (!this.agentWallet) {
45244
- throw new WalletError("WALLET_NOT_FOUND", "Agent wallet required for x402 payments");
45245
- }
45477
+ const provider = this.walletContext.activeWallet();
45246
45478
  try {
45247
45479
  const result = await payAndFetch({
45248
45480
  url: input2.url,
@@ -45252,8 +45484,13 @@ var X402ActionProvider = class extends ActionProvider {
45252
45484
  preferNetwork: input2.preferNetwork,
45253
45485
  preferMethod: input2.preferMethod,
45254
45486
  preferAsset: input2.preferAsset,
45255
- provider: this.agentWallet,
45256
- // maxPaymentAtomic is the cap; no per-call prompt for MCP.
45487
+ provider,
45488
+ // maxPaymentAtomic is the cap; no per-call prompt for MCP. The
45489
+ // expectation is the LLM ran x402_quote first, presented the
45490
+ // menu to the user, and is now calling x402_request with the
45491
+ // user-picked (chain, asset). Token-substitution risk is
45492
+ // accepted at the MCP layer — the CLI path has a readline
45493
+ // confirm that surfaces the contract address as the safety net.
45257
45494
  confirm: async () => true,
45258
45495
  // Auto-true when autoApprove=true; omit (rejects) otherwise.
45259
45496
  confirmApproval: input2.autoApprove ? async () => true : void 0
@@ -45276,6 +45513,80 @@ var X402ActionProvider = class extends ActionProvider {
45276
45513
  };
45277
45514
  }
45278
45515
  };
45516
+ var WalletModeActionProvider = class extends ActionProvider {
45517
+ name = "wallet-mode";
45518
+ 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).";
45519
+ walletContext;
45520
+ agentWallet;
45521
+ walletConnectFactory;
45522
+ constructor(options) {
45523
+ super();
45524
+ this.walletContext = options.walletContext;
45525
+ this.agentWallet = options.agentWallet;
45526
+ this.walletConnectFactory = options.walletConnectFactory;
45527
+ }
45528
+ getActions() {
45529
+ return [this.switchWalletMode(), this.getWalletStatus()];
45530
+ }
45531
+ /**
45532
+ * Builds the MCP response for a snapshot. When state is `wc-pairing` and
45533
+ * a QR code is available, emits a text block + an image block so MCP
45534
+ * clients (Claude Desktop etc.) render the QR inline. For other states,
45535
+ * emits a single text block with the JSON snapshot.
45536
+ */
45537
+ snapshotResponse(snapshot) {
45538
+ const textPayload = snapshot.pairing ? { ...snapshot, pairing: { ...snapshot.pairing, qrCodeImageUrl: void 0 } } : snapshot;
45539
+ const content = [{ type: "text", text: JSON.stringify(textPayload, null, 2) }];
45540
+ if (snapshot.pairing?.qrCodeImageUrl) {
45541
+ const match = snapshot.pairing.qrCodeImageUrl.match(/^data:(image\/[^;]+);base64,(.+)$/);
45542
+ if (match) {
45543
+ const [, mimeType, base642] = match;
45544
+ content.push({ type: "image", data: base642, mimeType });
45545
+ }
45546
+ }
45547
+ return new McpResponse(content);
45548
+ }
45549
+ switchWalletMode() {
45550
+ return {
45551
+ name: "switch_wallet_mode",
45552
+ description: 'Switch the active wallet mode for this MCP server session. IMPORTANT: the LOCAL agent wallet is auto-bound at server startup when one is configured, so most users do NOT need to call this \u2014 signing actions just work. Call this ONLY when the user explicitly asks to switch. Two flows: (a) user wants to sign on their phone via Trust Wallet (or any WC-compatible wallet) \u2192 mode="walletconnect"; (b) user wants to switch back from WalletConnect to the local wallet \u2192 mode="local". \n\nDISCOVERABILITY: most users do not know WalletConnect is an option. When the user first asks about wallet operations or signing in a fresh conversation, briefly mention that they can optionally switch to signing via Trust Wallet on their phone (more secure: keys never leave their device) by saying so \u2014 then call switch_wallet_mode if they opt in. Do not push it on every signing call; just surface it once early on or when the user expresses security concerns.\n\nFor mode="walletconnect": returns a pairing payload (URI, mobile deep link, inline QR image). Surface the QR to the user, then poll get_wallet_status until state="wc-connected" before retrying any signing action. Do NOT call switch_wallet_mode while a signing action is in flight: switching disconnects the prior WC relay session, which can break the in-flight signature; wait for the current action to settle before changing modes.',
45553
+ schema: import_zod15.z.object({
45554
+ mode: import_zod15.z.enum(["local", "walletconnect"]).describe(
45555
+ '"local" for the on-disk agent wallet, "walletconnect" to pair a remote wallet'
45556
+ )
45557
+ }),
45558
+ invoke: async (_ctx, input2) => {
45559
+ if (input2.mode === "local") {
45560
+ if (!this.agentWallet) {
45561
+ throw new WalletError(
45562
+ "WALLET_NOT_FOUND",
45563
+ "No local agent wallet found. Create one with `twak init` (CLI), then restart the MCP client (e.g. Claude Desktop)."
45564
+ );
45565
+ }
45566
+ await this.walletContext.bindLocal(this.agentWallet);
45567
+ return this.snapshotResponse(this.walletContext.snapshot());
45568
+ }
45569
+ if (!this.walletConnectFactory) {
45570
+ throw new WalletError(
45571
+ "VALIDATION_ERROR",
45572
+ "WalletConnect is not configured. Set WALLETCONNECT_PROJECT_ID (env var or `twak init --wc-project-id <id>`) and restart the MCP server."
45573
+ );
45574
+ }
45575
+ const wc2 = this.walletConnectFactory();
45576
+ await this.walletContext.beginWalletConnect(wc2);
45577
+ return this.snapshotResponse(this.walletContext.snapshot());
45578
+ }
45579
+ };
45580
+ }
45581
+ getWalletStatus() {
45582
+ return {
45583
+ name: "get_wallet_status",
45584
+ description: 'Returns the current wallet binding for this MCP server session: state ("unbound" | "local" | "wc-pairing" | "wc-connected"), wallet type (when bound), peer name (when wc-connected), and pairing data (URI / deep link / QR code, when state="wc-pairing"). When state="wc-pairing", the response includes an inline QR-code image (MCP image content), a mobile deep link (`pairing.deepLink`) for users on their phone, and the raw `pairing.uri` as a copyable string for advanced users. Poll this after switch_wallet_mode({mode: "walletconnect"}) until state transitions to "wc-connected"; that\'s when signing actions become usable. If the user asks to see the QR code again (e.g. "show me the QR", "I can\'t see it"), call this action again \u2014 every call while state="wc-pairing" re-emits the inline QR image. Do NOT try to render the QR yourself from the URI or the response JSON.',
45585
+ schema: import_zod15.z.object({}),
45586
+ invoke: async () => this.snapshotResponse(this.walletContext.snapshot())
45587
+ };
45588
+ }
45589
+ };
45279
45590
 
45280
45591
  // src/commands/serve.ts
45281
45592
  init_dist2();
@@ -49915,6 +50226,15 @@ var import_http = require("http");
49915
50226
  var import_crypto7 = require("crypto");
49916
50227
  init_dist2();
49917
50228
  init_dist();
50229
+ var SERVER_INSTRUCTIONS = `twak signs blockchain operations through one of two wallet modes:
50230
+
50231
+ LOCAL (default): an HD wallet auto-bound at server startup. In-process signing, no user-side prompts.
50232
+
50233
+ WALLETCONNECT: a remote wallet (e.g. Trust Wallet on phone) paired via WC v2. Each signing action prompts the user on their device. More secure: the private key never leaves the user's device.
50234
+
50235
+ When the user first engages with signing operations in a fresh conversation, briefly mention that they can optionally switch to phone-based signing via \`switch_wallet_mode({mode: "walletconnect"})\` \u2014 many users don't know this is an option. Mention once; don't repeat on every signing call.
50236
+
50237
+ To switch mid-session, call \`switch_wallet_mode\`. The new mode applies to subsequent calls; in-flight signing keeps using its captured wallet. Mode persists for the MCP server's lifetime; restarting the MCP client (e.g. Claude Desktop) resets to local.`;
49918
50238
  var TwakMcpServer = class {
49919
50239
  server;
49920
50240
  registry;
@@ -49922,7 +50242,10 @@ var TwakMcpServer = class {
49922
50242
  constructor(registry, context) {
49923
50243
  this.registry = registry;
49924
50244
  this.context = context;
49925
- this.server = new Server({ name: "twak", version: "0.1.0" }, { capabilities: { tools: {} } });
50245
+ this.server = new Server(
50246
+ { name: "twak", version: "0.1.0" },
50247
+ { capabilities: { tools: {} }, instructions: SERVER_INSTRUCTIONS }
50248
+ );
49926
50249
  this.setupHandlers();
49927
50250
  }
49928
50251
  setupHandlers() {
@@ -49956,6 +50279,9 @@ var TwakMcpServer = class {
49956
50279
  try {
49957
50280
  const parsed = action.schema.parse(args);
49958
50281
  const result = await action.invoke(this.context, parsed);
50282
+ if (result instanceof McpResponse) {
50283
+ return { content: result.content };
50284
+ }
49959
50285
  return {
49960
50286
  content: [
49961
50287
  {
@@ -49999,7 +50325,7 @@ var CAIP2_TO_BC_CHAIN = {
49999
50325
  "eip155:42161": "arbitrum",
50000
50326
  "eip155:8453": "base",
50001
50327
  "eip155:10": "optimism",
50002
- "eip155:43114": "avalanchec"
50328
+ "eip155:43114": "avalanche"
50003
50329
  };
50004
50330
  var RPC_REGISTRY = {
50005
50331
  "eip155:1": {
@@ -50146,7 +50472,13 @@ async function verifyPayment(rawHeader, config, httpClient, seenHashes, blockcha
50146
50472
  if (await seenHashes.hasSeen(txHash)) {
50147
50473
  return { valid: false, error: "Transaction hash already used (replay attack detected)" };
50148
50474
  }
50149
- const chainId = (typeof proof.chainId === "string" ? proof.chainId : config.chainId).toLowerCase();
50475
+ if (typeof proof.chainId === "string" && proof.chainId.toLowerCase() !== config.chainId.toLowerCase()) {
50476
+ return {
50477
+ valid: false,
50478
+ error: `Payment chain mismatch \u2014 merchant configured ${config.chainId}, proof claims ${proof.chainId}`
50479
+ };
50480
+ }
50481
+ const chainId = config.chainId.toLowerCase();
50150
50482
  const chainKey = CAIP2_TO_CHAIN[chainId];
50151
50483
  if (!chainKey) {
50152
50484
  return { valid: false, error: `Unsupported chain: ${chainId}` };
@@ -50160,7 +50492,10 @@ async function verifyPayment(rawHeader, config, httpClient, seenHashes, blockcha
50160
50492
  txHash
50161
50493
  );
50162
50494
  if (status.failed) {
50163
- return { valid: false, error: `Transaction reverted on-chain${status.error ? `: ${status.error}` : ""}` };
50495
+ return {
50496
+ valid: false,
50497
+ error: `Transaction reverted on-chain${status.error ? `: ${status.error}` : ""}`
50498
+ };
50164
50499
  }
50165
50500
  if (status.confirmed) {
50166
50501
  const rpcResult2 = await verifyViaRPC(txHash, config, chainId);
@@ -50189,14 +50524,20 @@ async function verifyPayment(rawHeader, config, httpClient, seenHashes, blockcha
50189
50524
  const decimals = isNative ? chain2.nativeDecimals : tokenInfo?.decimals ?? 18;
50190
50525
  const expectedAtomic = parseTokenAmount(config.amount, decimals);
50191
50526
  if (BigInt(tx.amount) < expectedAtomic) {
50192
- return { valid: false, error: `Insufficient payment: received ${tx.amount}, expected ${config.amount}` };
50527
+ return {
50528
+ valid: false,
50529
+ error: `Insufficient payment: received ${tx.amount}, expected ${config.amount}`
50530
+ };
50193
50531
  }
50194
50532
  await seenHashes.markSeen(txHash, chainId);
50195
50533
  return { valid: true, method: "txhub" };
50196
50534
  } catch (err) {
50197
50535
  const is403 = err instanceof Error && err.message.includes("403");
50198
50536
  if (!is403) {
50199
- return { valid: false, error: `Failed to fetch transaction: ${err instanceof Error ? err.message : String(err)}` };
50537
+ return {
50538
+ valid: false,
50539
+ error: `Failed to fetch transaction: ${err instanceof Error ? err.message : String(err)}`
50540
+ };
50200
50541
  }
50201
50542
  }
50202
50543
  const rpcResult = await verifyViaRPC(txHash, config, chainId);
@@ -50414,7 +50755,7 @@ function registerServeCommand(program3) {
50414
50755
  "--payment-recipient <address>",
50415
50756
  "Address to receive payments (required for on-chain verification)",
50416
50757
  ""
50417
- ).option("--password <password>", "Wallet password (falls back to keychain)").option("--auto-lock <minutes>", "Auto-lock wallet after N minutes of inactivity").option("--walletconnect", "Enable WalletConnect for interactive wallet approval").option("--wc-project-id <id>", "WalletConnect project ID (or set WALLETCONNECT_PROJECT_ID)").action(async (opts) => {
50758
+ ).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)").action(async (opts) => {
50418
50759
  await ensureTrustSDK();
50419
50760
  const client = await getClient();
50420
50761
  const swapClient = new SwapClient(client);
@@ -50427,15 +50768,6 @@ function registerServeCommand(program3) {
50427
50768
  if (password3) agentWallet = await AgentWalletProvider.fromStorage(password3);
50428
50769
  } catch {
50429
50770
  }
50430
- let wallet;
50431
- if (opts.walletconnect) {
50432
- const projectId = opts.wcProjectId || process.env.WALLETCONNECT_PROJECT_ID;
50433
- if (!projectId) {
50434
- error("--walletconnect requires --wc-project-id or WALLETCONNECT_PROJECT_ID");
50435
- process.exit(1);
50436
- }
50437
- wallet = new WalletConnectProvider({ projectId });
50438
- }
50439
50771
  if (opts.autoLock && agentWallet) {
50440
50772
  const minutes = parseInt(opts.autoLock, 10);
50441
50773
  if (isNaN(minutes) || minutes <= 0) {
@@ -50447,33 +50779,37 @@ function registerServeCommand(program3) {
50447
50779
  passwordResolver: () => resolvePassword(opts)
50448
50780
  });
50449
50781
  }
50450
- const useWalletConnect = Boolean(opts.walletconnect);
50451
- let walletProvider;
50452
- if (useWalletConnect) {
50453
- if (!wallet) {
50454
- error("--walletconnect was passed but WalletConnect provider failed to initialize");
50455
- process.exit(1);
50782
+ let wcProjectId = opts.wcProjectId || process.env.WALLETCONNECT_PROJECT_ID;
50783
+ if (!wcProjectId) {
50784
+ try {
50785
+ const creds = await new CredentialManager().load();
50786
+ wcProjectId = creds?.walletConnectProjectId;
50787
+ } catch {
50456
50788
  }
50457
- walletProvider = wallet;
50458
- } else {
50459
- walletProvider = agentWallet;
50789
+ }
50790
+ const walletConnectFactory = wcProjectId ? () => new WalletConnectProvider({ projectId: wcProjectId }) : void 0;
50791
+ const walletContext = new WalletContext();
50792
+ if (agentWallet) {
50793
+ await walletContext.bindLocal(agentWallet);
50460
50794
  }
50461
50795
  const registry = new ActionRegistry();
50462
50796
  registry.register(new BalanceActionProvider());
50463
- registry.register(new WalletActionProvider(wallet, agentWallet));
50464
- registry.register(new SwapActionProvider(swapClient, walletProvider, blockchainClient));
50797
+ registry.register(
50798
+ new WalletModeActionProvider({ walletContext, agentWallet, walletConnectFactory })
50799
+ );
50800
+ registry.register(new SwapActionProvider(swapClient, walletContext, blockchainClient));
50465
50801
  registry.register(new SecurityActionProvider());
50466
50802
  registry.register(new HistoryActionProvider());
50467
- registry.register(new TransferActionProvider(walletProvider, swapClient));
50803
+ registry.register(new TransferActionProvider(walletContext, swapClient));
50468
50804
  registry.register(new AlertActionProvider(alertStore, swapClient));
50469
- registry.register(new AgentWalletActionProvider(blockchainClient, agentWallet));
50470
- registry.register(new TokenActionProvider(blockchainClient, agentWallet));
50805
+ registry.register(new AgentWalletActionProvider(blockchainClient, walletContext));
50806
+ registry.register(new TokenActionProvider(blockchainClient, walletContext));
50471
50807
  registry.register(new MarketActionProvider());
50472
- registry.register(new OnrampActionProvider(walletProvider, swapClient));
50808
+ registry.register(new OnrampActionProvider(walletContext, swapClient));
50473
50809
  registry.register(
50474
- new AutomateActionProvider(automationStore, swapClient, walletProvider, blockchainClient)
50810
+ new AutomateActionProvider(automationStore, swapClient, walletContext, blockchainClient)
50475
50811
  );
50476
- registry.register(new X402ActionProvider(agentWallet));
50812
+ registry.register(new X402ActionProvider(walletContext));
50477
50813
  const context = {
50478
50814
  httpClient: client
50479
50815
  };
@@ -50489,7 +50825,10 @@ function registerServeCommand(program3) {
50489
50825
  paymentRecipient: opts.paymentRecipient,
50490
50826
  blockchainClient
50491
50827
  });
50492
- registerShutdownHandlers([agentWallet, wallet], () => restServer.stop());
50828
+ registerShutdownHandlers([agentWallet], async () => {
50829
+ await walletContext.reset();
50830
+ restServer.stop();
50831
+ });
50493
50832
  try {
50494
50833
  await restServer.start();
50495
50834
  } catch (err) {
@@ -50497,7 +50836,7 @@ function registerServeCommand(program3) {
50497
50836
  process.exit(1);
50498
50837
  }
50499
50838
  } else {
50500
- registerShutdownHandlers([agentWallet, wallet]);
50839
+ registerShutdownHandlers([agentWallet], () => walletContext.reset());
50501
50840
  const mcpServer = new TwakMcpServer(registry, context);
50502
50841
  console.error("TW Agent MCP server starting...");
50503
50842
  await mcpServer.start();
@@ -51149,6 +51488,7 @@ function registerAutomateCommand(program3) {
51149
51488
  }
51150
51489
 
51151
51490
  // src/commands/x402.ts
51491
+ init_dist();
51152
51492
  function makeReadlineConfirm() {
51153
51493
  return async (k4) => {
51154
51494
  const readline = await import("readline");
@@ -51205,6 +51545,54 @@ async function autoApproveConfirm(c3) {
51205
51545
  warn(`x402: auto-approving Permit2 unlimited allowance for ${c3.token} on ${c3.chain}`);
51206
51546
  return true;
51207
51547
  }
51548
+ function supportedChainName(network) {
51549
+ if (!network.startsWith("eip155:")) return null;
51550
+ const chain2 = getChainByEvmId(network.slice("eip155:".length));
51551
+ return chain2 ? chain2.name : null;
51552
+ }
51553
+ function filterAndSortAccepts(accepts) {
51554
+ const supported = accepts.flatMap((a3) => {
51555
+ const chainName = supportedChainName(a3.network);
51556
+ return chainName ? [{ ...a3, chainName }] : [];
51557
+ });
51558
+ return supported.sort((a3, b3) => Number(b3.preferred) - Number(a3.preferred));
51559
+ }
51560
+ function safeTokenName(s2) {
51561
+ return s2.replace(/[\x00-\x1f\x7f]/g, "");
51562
+ }
51563
+ function renderAcceptsTable(accepts) {
51564
+ const rows = accepts.map((a3, i2) => ({
51565
+ idx: String(i2 + 1),
51566
+ token: safeTokenName(a3.tokenName) || a3.asset,
51567
+ chain: a3.chainName,
51568
+ amount: a3.amount,
51569
+ method: a3.transferMethod + (a3.requiresApproval ? " (Permit2)" : " (gasless)"),
51570
+ asset: a3.asset
51571
+ }));
51572
+ const headers = {
51573
+ idx: "#",
51574
+ token: "Token",
51575
+ chain: "Chain",
51576
+ amount: "Amount",
51577
+ method: "Method",
51578
+ asset: "Asset"
51579
+ };
51580
+ const widths = {
51581
+ idx: Math.max(headers.idx.length, ...rows.map((r3) => r3.idx.length)),
51582
+ token: Math.max(headers.token.length, ...rows.map((r3) => r3.token.length)),
51583
+ chain: Math.max(headers.chain.length, ...rows.map((r3) => r3.chain.length)),
51584
+ amount: Math.max(headers.amount.length, ...rows.map((r3) => r3.amount.length)),
51585
+ method: Math.max(headers.method.length, ...rows.map((r3) => r3.method.length)),
51586
+ asset: Math.max(headers.asset.length, ...rows.map((r3) => r3.asset.length))
51587
+ };
51588
+ const pad3 = (s2, w4) => s2 + " ".repeat(Math.max(0, w4 - s2.length));
51589
+ const line = (r3) => `${pad3(r3.idx, widths.idx)} ${pad3(r3.token, widths.token)} ${pad3(r3.chain, widths.chain)} ${pad3(r3.amount, widths.amount)} ${pad3(r3.method, widths.method)} ${pad3(r3.asset, widths.asset)}`;
51590
+ const totalWidth = Object.values(widths).reduce((a3, b3) => a3 + b3, 0) + (Object.keys(widths).length - 1) * 2;
51591
+ console.log(line(headers));
51592
+ console.log("\u2500".repeat(totalWidth));
51593
+ rows.forEach((r3) => console.log(line(r3)));
51594
+ console.log("\u2500".repeat(totalWidth));
51595
+ }
51208
51596
  function registerX402Command(program3) {
51209
51597
  const x402 = program3.command("x402").description("x402 micropayment protocol \u2014 pay HTTP endpoints that require on-chain payment");
51210
51598
  x402.command("request <url>").description(
@@ -51258,13 +51646,84 @@ function registerX402Command(program3) {
51258
51646
  handleCommandError(err, opts.json ?? false, "x402 request failed");
51259
51647
  }
51260
51648
  });
51649
+ x402.command("quote <url>").description(
51650
+ "Preview an x402 endpoint's payment options without signing. Read-only; no wallet needed."
51651
+ ).option("--method <method>", "HTTP method", "GET").option("--body <json>", "Request body (JSON string)").option("--json", "JSON output").action(async (url, opts) => {
51652
+ try {
51653
+ const result = await quoteX402({ url, method: opts.method, body: opts.body });
51654
+ if (!result.success) {
51655
+ if (opts.json) {
51656
+ output({ ...result, error: result.message, errorCode: result.code }, true);
51657
+ } else {
51658
+ error(result.message);
51659
+ }
51660
+ process.exitCode = 1;
51661
+ return;
51662
+ }
51663
+ const sorted = filterAndSortAccepts(result.accepts);
51664
+ const shQuote = (s2) => `'${s2.replace(/'/g, `'\\''`)}'`;
51665
+ const cliCommand = (amount) => {
51666
+ const parts = ["twak x402 request", shQuote(url)];
51667
+ if (opts.method && opts.method.toUpperCase() !== "GET") {
51668
+ parts.push(`--method ${shQuote(opts.method)}`);
51669
+ }
51670
+ if (opts.body) parts.push(`--body ${shQuote(opts.body)}`);
51671
+ parts.push(`--max-payment ${amount}`);
51672
+ return parts.join(" ");
51673
+ };
51674
+ if (opts.json) {
51675
+ if (sorted.length === 0) {
51676
+ output(
51677
+ {
51678
+ ...result,
51679
+ nextAction: `Endpoint offered ${result.accepts.length} option(s) but none on chains this client supports.`
51680
+ },
51681
+ true
51682
+ );
51683
+ process.exitCode = 1;
51684
+ return;
51685
+ }
51686
+ output(
51687
+ {
51688
+ ...result,
51689
+ nextAction: `Show these payment options to the user. Default route is ${sorted[0].amount} ${safeTokenName(sorted[0].tokenName) || sorted[0].asset} on ${sorted[0].chainName}. Pick one and run: ${cliCommand(sorted[0].amount)} --prefer-asset <picked asset>. Omit --prefer-asset to use the default.`
51690
+ },
51691
+ true
51692
+ );
51693
+ return;
51694
+ }
51695
+ if (result.resource?.url) {
51696
+ info(`Resource: ${result.resource.url}`);
51697
+ }
51698
+ info(result.summary);
51699
+ const dropped = result.accepts.length - sorted.length;
51700
+ if (dropped > 0) {
51701
+ warn(`Filtered ${dropped} route(s) on chains this client does not support.`);
51702
+ }
51703
+ console.log("");
51704
+ if (sorted.length === 0) {
51705
+ error("No supported route from this endpoint.");
51706
+ process.exitCode = 1;
51707
+ return;
51708
+ }
51709
+ renderAcceptsTable(sorted);
51710
+ console.log(
51711
+ "If you don't pass --prefer-asset/--prefer-method/--prefer-network, the first row is used."
51712
+ );
51713
+ console.log("");
51714
+ info(`Next: ${cliCommand(sorted[0].amount)}`);
51715
+ } catch (err) {
51716
+ handleCommandError(err, opts.json ?? false, "x402 quote failed");
51717
+ }
51718
+ });
51261
51719
  x402.command("info").description("Show x402 client information and usage").action(() => {
51262
51720
  info("twak x402 client \u2014 speaks canonical x402 (V1 + V2) with EIP-3009 + Permit2 signing");
51263
51721
  console.log("");
51264
51722
  console.log("Usage:");
51723
+ console.log(" twak x402 quote <url> Preview payment options (read-only)");
51265
51724
  console.log(" twak x402 request <url> Pay an x402-gated endpoint");
51266
51725
  console.log("");
51267
- console.log("Options:");
51726
+ console.log("Options (request):");
51268
51727
  console.log(" --method <method> HTTP method (default: GET)");
51269
51728
  console.log(" --body <json> Request body");
51270
51729
  console.log(" --max-payment <atomic> Cap auto-approval; integer atomic units");
@@ -51292,7 +51751,7 @@ var crypto7 = __toESM(require("crypto"));
51292
51751
  // package.json
51293
51752
  var package_default = {
51294
51753
  name: "@trustwallet/cli",
51295
- version: "0.13.0",
51754
+ version: "0.14.0",
51296
51755
  description: "TWAK CLI \u2014 command-line interface for Trust Wallet Agent Kit",
51297
51756
  main: "dist/index.js",
51298
51757
  types: "dist/index.d.ts",