@agentlayer.tech/wallet 0.1.102 → 0.1.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/.claude-plugin/marketplace.json +4 -1
  2. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +1 -1
  3. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  4. package/VERSION +1 -1
  5. package/agent-wallet/README.md +7 -2
  6. package/agent-wallet/agent_wallet/__init__.py +1 -1
  7. package/agent-wallet/agent_wallet/config.py +15 -1
  8. package/agent-wallet/agent_wallet/evm_user_wallets.py +101 -244
  9. package/agent-wallet/agent_wallet/openclaw_runtime.py +7 -2
  10. package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +40 -9
  11. package/agent-wallet/agent_wallet/wallet_layer/factory.py +25 -2
  12. package/agent-wallet/openclaw.plugin.json +1 -1
  13. package/agent-wallet/pyproject.toml +1 -1
  14. package/agent-wallet/scripts/bootstrap_openclaw_evm.py +24 -14
  15. package/agent-wallet/scripts/install_agent_wallet.py +18 -6
  16. package/agent-wallet/scripts/manage_openclaw_evm_wallet.py +18 -15
  17. package/agent-wallet/scripts/setup_evm_wallet.sh +2 -3
  18. package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +2 -2
  19. package/claude-code/plugins/agent-wallet/commands/wallet-evm.md +1 -0
  20. package/claude-code/plugins/agent-wallet/commands/wallet-setup.md +1 -0
  21. package/claude-code/plugins/agent-wallet/skills/wallet-operator/SKILL.md +1 -0
  22. package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +1 -1
  23. package/codex/plugins/agent-wallet/server.py +74 -4
  24. package/hermes/plugins/agent_wallet/plugin.yaml +1 -1
  25. package/package.json +1 -1
  26. package/wdk-btc-wallet/package.json +1 -1
  27. package/wdk-evm-wallet/README.md +6 -2
  28. package/wdk-evm-wallet/package.json +3 -2
  29. package/wdk-evm-wallet/src/config.js +9 -0
  30. package/wdk-evm-wallet/src/server.js +768 -692
@@ -65,11 +65,36 @@ def _normalize_base_url(value: str) -> str:
65
65
  if not text:
66
66
  raise WalletBackendError("WDK EVM service URL is not configured.")
67
67
  parsed = urlparse(text)
68
+ if parsed.scheme == "unix":
69
+ if not parsed.path:
70
+ raise WalletBackendError("WDK EVM unix socket URL must include a path.")
71
+ return text
68
72
  if parsed.scheme not in {"http", "https"} or parsed.hostname not in LOCAL_WDK_EVM_HOSTS:
69
73
  raise WalletBackendError("WDK EVM service URL must point to a localhost HTTP endpoint.")
70
74
  return text.rstrip("/")
71
75
 
72
76
 
77
+ def _httpx_target(base_url: str, path: str) -> tuple[str, dict[str, Any]]:
78
+ """Return (url, extra_httpx_client_kwargs) for either transport."""
79
+ parsed = urlparse(base_url)
80
+ if parsed.scheme == "unix":
81
+ return (
82
+ f"http://wdk-evm-wallet.local{path}",
83
+ {"transport": httpx.HTTPTransport(uds=parsed.path)},
84
+ )
85
+ return (f"{base_url}{path}", {})
86
+
87
+
88
+ def _async_httpx_target(base_url: str, path: str) -> tuple[str, dict[str, Any]]:
89
+ parsed = urlparse(base_url)
90
+ if parsed.scheme == "unix":
91
+ return (
92
+ f"http://wdk-evm-wallet.local{path}",
93
+ {"transport": httpx.AsyncHTTPTransport(uds=parsed.path)},
94
+ )
95
+ return (f"{base_url}{path}", {})
96
+
97
+
73
98
  def _resolve_local_token_path() -> Path:
74
99
  configured = os.getenv("WDK_EVM_LOCAL_TOKEN_PATH", "").strip()
75
100
  if configured:
@@ -195,16 +220,16 @@ class WdkEvmLocalClient:
195
220
  return {**payload, "password": password}
196
221
 
197
222
  async def post(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
223
+ url, transport_kwargs = _async_httpx_target(self.base_url, path)
198
224
  try:
199
225
  async with httpx.AsyncClient(
200
226
  timeout=_timeout_for_path(path),
201
227
  headers=self._headers,
202
228
  follow_redirects=False,
203
229
  trust_env=False,
230
+ **transport_kwargs,
204
231
  ) as client:
205
- response = await client.post(
206
- f"{self.base_url}{path}", json=self._with_credentials(payload)
207
- )
232
+ response = await client.post(url, json=self._with_credentials(payload))
208
233
  except httpx.TimeoutException as exc:
209
234
  raise WalletBackendError(
210
235
  "wdk-evm-wallet request timed out.",
@@ -220,14 +245,16 @@ class WdkEvmLocalClient:
220
245
  return _unwrap_payload(response)
221
246
 
222
247
  async def get(self, path: str) -> dict[str, Any]:
248
+ url, transport_kwargs = _async_httpx_target(self.base_url, path)
223
249
  try:
224
250
  async with httpx.AsyncClient(
225
251
  timeout=_timeout_for_path(path),
226
252
  headers=self._headers,
227
253
  follow_redirects=False,
228
254
  trust_env=False,
255
+ **transport_kwargs,
229
256
  ) as client:
230
- response = await client.get(f"{self.base_url}{path}")
257
+ response = await client.get(url)
231
258
  except httpx.TimeoutException as exc:
232
259
  raise WalletBackendError(
233
260
  "wdk-evm-wallet request timed out.",
@@ -243,16 +270,16 @@ class WdkEvmLocalClient:
243
270
  return _unwrap_payload(response)
244
271
 
245
272
  def post_sync(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
273
+ url, transport_kwargs = _httpx_target(self.base_url, path)
246
274
  try:
247
275
  with httpx.Client(
248
276
  timeout=_timeout_for_path(path),
249
277
  headers=self._headers,
250
278
  follow_redirects=False,
251
279
  trust_env=False,
280
+ **transport_kwargs,
252
281
  ) as client:
253
- response = client.post(
254
- f"{self.base_url}{path}", json=self._with_credentials(payload)
255
- )
282
+ response = client.post(url, json=self._with_credentials(payload))
256
283
  except httpx.TimeoutException as exc:
257
284
  raise WalletBackendError(
258
285
  "wdk-evm-wallet request timed out.",
@@ -268,14 +295,16 @@ class WdkEvmLocalClient:
268
295
  return _unwrap_payload(response)
269
296
 
270
297
  def get_sync(self, path: str) -> dict[str, Any]:
298
+ url, transport_kwargs = _httpx_target(self.base_url, path)
271
299
  try:
272
300
  with httpx.Client(
273
301
  timeout=_timeout_for_path(path),
274
302
  headers=self._headers,
275
303
  follow_redirects=False,
276
304
  trust_env=False,
305
+ **transport_kwargs,
277
306
  ) as client:
278
- response = client.get(f"{self.base_url}{path}")
307
+ response = client.get(url)
279
308
  except httpx.TimeoutException as exc:
280
309
  raise WalletBackendError(
281
310
  "wdk-evm-wallet request timed out.",
@@ -291,14 +320,16 @@ class WdkEvmLocalClient:
291
320
  return _unwrap_payload(response)
292
321
 
293
322
  def list_wallets_sync(self) -> list[dict[str, Any]]:
323
+ url, transport_kwargs = _httpx_target(self.base_url, "/v1/evm/wallets")
294
324
  try:
295
325
  with httpx.Client(
296
326
  timeout=_timeout_for_path("/v1/evm/wallets"),
297
327
  headers=self._headers,
298
328
  follow_redirects=False,
299
329
  trust_env=False,
330
+ **transport_kwargs,
300
331
  ) as client:
301
- response = client.get(f"{self.base_url}/v1/evm/wallets")
332
+ response = client.get(url)
302
333
  except httpx.TimeoutException as exc:
303
334
  raise WalletBackendError(
304
335
  "wdk-evm-wallet request timed out.",
@@ -2,16 +2,19 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import os
5
6
  from pathlib import Path
6
7
 
7
8
  from agent_wallet.bootstrap import ensure_solana_wallet_ready, ensure_wallet_pin
8
9
  from agent_wallet.encrypted_storage import load_wallet_secret_material
9
10
  from agent_wallet.config import (
10
11
  normalize_btc_network,
12
+ normalize_evm_network,
11
13
  normalize_solana_network,
12
14
  resolve_runtime_solana_rpc_config,
13
15
  resolve_runtime_solana_swap_config,
14
16
  resolve_solana_private_key,
17
+ resolve_wdk_evm_service_url,
15
18
  settings,
16
19
  )
17
20
  from agent_wallet.wallet_layer.base import AgentWalletBackend, WalletBackendError
@@ -20,6 +23,15 @@ from agent_wallet.wallet_layer.solana import SolanaLocalKeypairSigner, SolanaWal
20
23
  from agent_wallet.wallet_layer.wdk_btc import WdkBtcLocalWalletBackend
21
24
 
22
25
 
26
+ def _evm_autostart_disabled() -> bool:
27
+ return os.getenv("AGENT_WALLET_EVM_DISABLE_AUTOSTART", "").strip().lower() in {
28
+ "1",
29
+ "true",
30
+ "yes",
31
+ "on",
32
+ }
33
+
34
+
23
35
  def _load_keypair_material() -> str | None:
24
36
  secret = resolve_solana_private_key()
25
37
  if secret:
@@ -91,10 +103,21 @@ def create_wallet_backend() -> AgentWalletBackend | None:
91
103
  )
92
104
 
93
105
  if backend in {"wdk_evm_local", "wdk-evm-local", "evm_local", "evm-local"}:
106
+ evm_network = normalize_evm_network(settings.solana_network)
107
+ service_url = resolve_wdk_evm_service_url()
108
+ if not _evm_autostart_disabled():
109
+ # Single-agent hosts (Claude Code/Codex) talked to the daemon over
110
+ # a bare HTTP client with no health check, so an unreachable or
111
+ # stale daemon surfaced as an opaque connection error instead of
112
+ # self-healing the way the multi-user OpenClaw path already does.
113
+ # Reuse that same recovery here instead of duplicating it.
114
+ from agent_wallet.evm_user_wallets import ensure_local_evm_service_ready
115
+
116
+ ensure_local_evm_service_ready(service_url, evm_network)
94
117
  return WdkEvmLocalWalletBackend(
95
- service_url=settings.wdk_evm_service_url,
118
+ service_url=service_url,
96
119
  wallet_id=settings.wdk_evm_wallet_id,
97
- network=settings.solana_network,
120
+ network=evm_network,
98
121
  account_index=settings.wdk_evm_account_index,
99
122
  sign_only=settings.agent_wallet_sign_only,
100
123
  )
@@ -2,7 +2,7 @@
2
2
  "id": "agent-wallet",
3
3
  "name": "Agent Wallet",
4
4
  "description": "Plugin-friendly wallet backend for OpenClaw agents with safe wallet tools and runtime instructions across Solana, local BTC, and local EVM.",
5
- "version": "0.1.102",
5
+ "version": "0.1.106",
6
6
  "skills": ["skills/wallet-operator"],
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openclaw-agent-wallet"
7
- version = "0.1.102"
7
+ version = "0.1.106"
8
8
  description = "Plugin-friendly wallet backend for OpenClaw agents"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -10,9 +10,7 @@ import subprocess
10
10
  import sys
11
11
  import time
12
12
  from pathlib import Path
13
- from urllib.error import URLError
14
13
  from urllib.parse import urlparse
15
- from urllib.request import urlopen
16
14
 
17
15
  sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
18
16
 
@@ -24,6 +22,12 @@ def _default_config_path() -> Path:
24
22
  return Path(os.path.expanduser("~/.openclaw/openclaw.json"))
25
23
 
26
24
 
25
+ def _default_service_url() -> str:
26
+ from agent_wallet.config import resolve_wdk_evm_service_url
27
+
28
+ return resolve_wdk_evm_service_url()
29
+
30
+
27
31
  def _default_user_id() -> str:
28
32
  return f"{os.getenv('USER', 'openclaw-user')}-local"
29
33
 
@@ -54,7 +58,7 @@ def build_parser() -> argparse.ArgumentParser:
54
58
  parser.add_argument("--plugin-id", default="agent-wallet")
55
59
  parser.add_argument("--user-id", default=_default_user_id())
56
60
  parser.add_argument("--network", default="base")
57
- parser.add_argument("--service-url", default="http://127.0.0.1:8081")
61
+ parser.add_argument("--service-url", default=_default_service_url())
58
62
  parser.add_argument("--wdk-wallet-root", default=str(_repo_root() / "wdk-evm-wallet"))
59
63
  parser.add_argument("--label", default="Agent EVM Wallet")
60
64
  parser.add_argument("--account-index", type=int, default=0)
@@ -103,15 +107,19 @@ def _health_url(service_url: str) -> str:
103
107
 
104
108
 
105
109
  def _service_is_healthy(service_url: str) -> bool:
106
- try:
107
- with urlopen(_health_url(service_url), timeout=1.5) as response:
108
- return int(getattr(response, "status", 0) or 0) == 200
109
- except (URLError, TimeoutError, OSError):
110
- return False
110
+ # Delegate to the shared probe: it dispatches on scheme, so a unix:// URL
111
+ # (the default since resolve_wdk_evm_service_url moved to a per-home socket)
112
+ # is fetched over AF_UNIX. urlopen alone raises "unknown url type: unix",
113
+ # which would make --no-auto-start-service reject a healthy daemon.
114
+ from agent_wallet.evm_user_wallets import _service_health
115
+
116
+ return _service_health(service_url) is not None
111
117
 
112
118
 
113
119
  def _is_local_service_url(service_url: str) -> bool:
114
120
  parsed = urlparse(service_url)
121
+ if parsed.scheme == "unix":
122
+ return bool(parsed.path)
115
123
  return parsed.scheme in {"http", "https"} and parsed.hostname in {"127.0.0.1", "localhost", "::1"}
116
124
 
117
125
 
@@ -146,7 +154,6 @@ def _auto_start_local_service(
146
154
  _service_health,
147
155
  _should_restart_local_service,
148
156
  _stop_local_service,
149
- _write_service_owner,
150
157
  )
151
158
 
152
159
  restarted = False
@@ -167,15 +174,18 @@ def _auto_start_local_service(
167
174
  raise SystemExit(f"Could not find wdk-evm-wallet launcher: {run_local}")
168
175
 
169
176
  parsed = urlparse(service_url)
170
- host = parsed.hostname or "127.0.0.1"
171
- port = parsed.port or 8081
172
177
  log_dir = _service_log_dir(config_path)
173
178
  log_dir.mkdir(parents=True, exist_ok=True)
174
179
  log_path = _service_log_path(config_path)
175
180
 
176
181
  env = os.environ.copy()
177
- env["HOST"] = host
178
- env["PORT"] = str(port)
182
+ if parsed.scheme == "unix":
183
+ env["WDK_EVM_TRANSPORT"] = "socket"
184
+ env["WDK_EVM_SOCKET_PATH"] = parsed.path
185
+ else:
186
+ env["WDK_EVM_TRANSPORT"] = "tcp"
187
+ env["HOST"] = parsed.hostname or "127.0.0.1"
188
+ env["PORT"] = str(parsed.port or 8081)
179
189
  env["WDK_EVM_NETWORK"] = network
180
190
  env["WDK_EVM_INSTANCE_ID"] = _expected_local_service_instance_id()
181
191
 
@@ -194,7 +204,7 @@ def _auto_start_local_service(
194
204
  while time.time() < deadline:
195
205
  health = _service_health(service_url)
196
206
  if health is not None:
197
- pid = _write_service_owner(health, service_url)
207
+ pid = int(health.get("pid") or 0)
198
208
  return {
199
209
  "started": True,
200
210
  "already_healthy": False,
@@ -190,7 +190,7 @@ def build_parser() -> argparse.ArgumentParser:
190
190
  parser.add_argument("--extension-path", default=str(_extension_path()))
191
191
  parser.add_argument("--wdk-btc-root", default=str(_default_wdk_btc_root()))
192
192
  parser.add_argument("--wdk-evm-root", default=str(_default_wdk_evm_root()))
193
- parser.add_argument("--wdk-evm-service-url", default=EVM_DEFAULT_SERVICE_URL)
193
+ parser.add_argument("--wdk-evm-service-url", default=_default_evm_service_url())
194
194
  parser.add_argument("--runtime-root", default=str(_default_runtime_root()))
195
195
  parser.add_argument("--npm-bin", default=_default_npm_bin())
196
196
  parser.add_argument("--plugin-id", default="agent-wallet")
@@ -735,8 +735,8 @@ def _build_next_steps(
735
735
  command.extend(["--package-root", str(effective_package_root)])
736
736
  command.extend(["--python-bin", str(python_bin)])
737
737
  if _is_evm_backend(args.backend):
738
- service_url = str(getattr(args, "wdk_evm_service_url", "") or EVM_DEFAULT_SERVICE_URL).strip()
739
- command.extend(["--wdk-evm-service-url", service_url or EVM_DEFAULT_SERVICE_URL])
738
+ service_url = str(getattr(args, "wdk_evm_service_url", "") or _default_evm_service_url()).strip()
739
+ command.extend(["--wdk-evm-service-url", service_url or _default_evm_service_url()])
740
740
  return command
741
741
 
742
742
 
@@ -753,7 +753,19 @@ def _is_evm_backend(backend: str) -> bool:
753
753
  }
754
754
 
755
755
 
756
- EVM_DEFAULT_SERVICE_URL = "http://127.0.0.1:8081"
756
+ def _default_evm_service_url() -> str:
757
+ # This installer bootstraps a brand-new install, so it runs before
758
+ # agent_wallet's *dependencies* (e.g. pydantic-settings) are guaranteed to
759
+ # be installed -- putting the package root on sys.path (as other scripts
760
+ # in this family do) isn't enough, since `import agent_wallet.config`
761
+ # itself pulls in pydantic_settings. Mirror
762
+ # agent_wallet.config.resolve_wdk_evm_service_url()'s stdlib-only logic
763
+ # directly instead of importing it.
764
+ explicit = os.environ.get("WDK_EVM_SERVICE_URL", "").strip()
765
+ if explicit:
766
+ return explicit
767
+ openclaw_home = Path(os.environ.get("OPENCLAW_HOME", "~/.openclaw")).expanduser()
768
+ return f"unix://{openclaw_home / 'wdk-evm-wallet' / 'daemon.sock'}"
757
769
 
758
770
 
759
771
  def _build_evm_onboard_config(args: argparse.Namespace) -> dict[str, object]:
@@ -764,12 +776,12 @@ def _build_evm_onboard_config(args: argparse.Namespace) -> dict[str, object]:
764
776
  network = args.network.strip().lower() if _is_evm_backend(args.backend) else "base"
765
777
  if network not in {"base", "ethereum"}:
766
778
  network = "base"
767
- service_url = str(getattr(args, "wdk_evm_service_url", "") or EVM_DEFAULT_SERVICE_URL).strip()
779
+ service_url = str(getattr(args, "wdk_evm_service_url", "") or _default_evm_service_url()).strip()
768
780
  return {
769
781
  "backend": "wdk_evm_local",
770
782
  "network": network,
771
783
  "signOnly": bool(args.sign_only),
772
- "wdkEvmServiceUrl": service_url or EVM_DEFAULT_SERVICE_URL,
784
+ "wdkEvmServiceUrl": service_url or _default_evm_service_url(),
773
785
  }
774
786
 
775
787
 
@@ -8,15 +8,17 @@ import json
8
8
  import sys
9
9
  from getpass import getpass
10
10
  from pathlib import Path
11
- from urllib.error import URLError
12
- from urllib.request import urlopen
13
11
 
14
12
  PACKAGE_ROOT = Path(__file__).resolve().parents[1]
15
13
  if str(PACKAGE_ROOT) not in sys.path:
16
14
  sys.path.insert(0, str(PACKAGE_ROOT))
17
15
 
18
- from agent_wallet.config import normalize_evm_network, settings # noqa: E402
16
+ from agent_wallet.config import ( # noqa: E402
17
+ normalize_evm_network,
18
+ resolve_wdk_evm_service_url,
19
+ )
19
20
  from agent_wallet.evm_user_wallets import ( # noqa: E402
21
+ _service_health as probe_service_health,
20
22
  bind_user_evm_wallet,
21
23
  create_user_evm_wallet,
22
24
  get_user_evm_wallet_binding,
@@ -81,21 +83,22 @@ def _service_health(service_url: str | None) -> dict[str, object]:
81
83
  target = str(service_url or "").strip()
82
84
  if not target:
83
85
  return {"service_url": None, "healthy": False, "error": "service_url is not configured"}
84
- health_url = f"{target.rstrip('/')}/health"
85
- try:
86
- with urlopen(health_url, timeout=1.5) as response:
87
- payload = json.loads(response.read().decode("utf-8"))
88
- return {
89
- "service_url": target,
90
- "healthy": int(getattr(response, "status", 0) or 0) == 200,
91
- "health": payload,
92
- }
93
- except (URLError, TimeoutError, OSError, ValueError) as exc:
94
- return {"service_url": target, "healthy": False, "error": str(exc)}
86
+ # Delegate to the shared probe: it dispatches on scheme, so a unix:// URL
87
+ # (the default since resolve_wdk_evm_service_url moved to a per-home socket)
88
+ # is fetched over AF_UNIX. urlopen alone raises "unknown url type: unix" and
89
+ # would report a perfectly healthy socket daemon as down.
90
+ payload = probe_service_health(target)
91
+ if payload is None:
92
+ return {
93
+ "service_url": target,
94
+ "healthy": False,
95
+ "error": f"no healthy /health response from {target}",
96
+ }
97
+ return {"service_url": target, "healthy": True, "health": payload}
95
98
 
96
99
 
97
100
  def _status_payload(user_id: str | None, network: str | None, service_url: str | None) -> dict[str, object]:
98
- target_service_url = str(service_url or settings.wdk_evm_service_url).strip() or None
101
+ target_service_url = str(service_url or resolve_wdk_evm_service_url()).strip() or None
99
102
  payload: dict[str, object] = {
100
103
  "ok": True,
101
104
  "network": _normalize_network(network or "ethereum"),
@@ -106,7 +106,6 @@ prompt_network_choice() {
106
106
 
107
107
  DEFAULT_USER_ID=${OPENCLAW_EVM_USER_ID:-${USER:-openclaw-user}-local}
108
108
  DEFAULT_NETWORK=${OPENCLAW_EVM_NETWORK:-base}
109
- DEFAULT_SERVICE_URL=${OPENCLAW_EVM_SERVICE_URL:-http://127.0.0.1:8081}
110
109
 
111
110
  if ! has_flag --user-id "$@"; then
112
111
  USER_ID=$(prompt_with_default "OpenClaw user id" "$DEFAULT_USER_ID")
@@ -118,8 +117,8 @@ if ! has_flag --network "$@"; then
118
117
  set -- "$@" --network "$NETWORK"
119
118
  fi
120
119
 
121
- if ! has_flag --service-url "$@"; then
122
- set -- "$@" --service-url "$DEFAULT_SERVICE_URL"
120
+ if ! has_flag --service-url "$@" && [ -n "${OPENCLAW_EVM_SERVICE_URL:-}" ]; then
121
+ set -- "$@" --service-url "$OPENCLAW_EVM_SERVICE_URL"
123
122
  fi
124
123
 
125
124
  if ! has_flag --config-path "$@" && [ -n "${OPENCLAW_EVM_CONFIG_PATH:-}" ]; then
@@ -1,7 +1,7 @@
1
1
  {
2
- "name": "agent-wallet",
2
+ "name": "wallet",
3
3
  "displayName": "Agent Wallet",
4
- "version": "0.1.102",
4
+ "version": "0.1.106",
5
5
  "description": "Claude Code bridge for the existing AgentLayer wallet runtime. Connects to Solana, Bitcoin, and EVM wallets without creating a new one.",
6
6
  "author": {
7
7
  "name": "AgentLayer"
@@ -2,6 +2,7 @@
2
2
  description: Show the connected EVM wallet overview for the current/default EVM network directly in chat.
3
3
  allowed-tools: mcp__agent_wallet__get_wallet_overview
4
4
  disable-model-invocation: true
5
+ user-invocable: false
5
6
  ---
6
7
 
7
8
  Show the connected EVM wallet overview directly in chat.
@@ -1,6 +1,7 @@
1
1
  ---
2
2
  description: Install or repair the AgentLayer wallet backend runtime without leaving Claude Code.
3
3
  allowed-tools: Bash(sh:*), Bash(npx:*)
4
+ user-invocable: false
4
5
  ---
5
6
 
6
7
  Install (or repair) the AgentLayer wallet backend that this plugin bridges to.
@@ -1,5 +1,6 @@
1
1
  ---
2
2
  description: "Use whenever the user's own funds or wallet are involved — balances, portfolio, transfers, swaps, bridging, staking, lending, or x402 payments — even if the user never says \"wallet\". Trigger phrases include \"my balance\", \"how much SOL/ETH/BTC/USDC do I have\", \"send/transfer X to\", \"swap\", \"bridge\", \"stake\", \"my portfolio\", \"pay for this API\", \"x402\". Prefer agent-wallet MCP tools over shell commands, raw RPC, or crypto data servers. Preview writes first and execute only after explicit user confirmation."
3
+ user-invocable: false
3
4
  ---
4
5
 
5
6
  # Agent Wallet Operator
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-wallet",
3
- "version": "0.1.102",
3
+ "version": "0.1.106",
4
4
  "description": "Codex plugin bridge for the AgentLayer wallet runtime.",
5
5
  "author": {
6
6
  "name": "AgentLayer"
@@ -13,8 +13,10 @@ import selectors
13
13
  import signal
14
14
  import subprocess
15
15
  import sys
16
+ import tempfile
16
17
  import threading
17
18
  import time
19
+ from contextlib import suppress
18
20
  from functools import lru_cache
19
21
  from pathlib import Path
20
22
  from typing import Any
@@ -122,6 +124,50 @@ def _openclaw_home() -> Path:
122
124
  return Path(os.getenv("OPENCLAW_HOME", "~/.openclaw")).expanduser().resolve()
123
125
 
124
126
 
127
+ def _session_defaults_path() -> Path:
128
+ return _openclaw_home() / "agent-wallet" / "session-defaults.json"
129
+
130
+
131
+ def _read_session_defaults() -> dict[str, Any]:
132
+ try:
133
+ payload = json.loads(_session_defaults_path().read_text(encoding="utf-8"))
134
+ except (FileNotFoundError, json.JSONDecodeError, OSError):
135
+ return {}
136
+ return payload if isinstance(payload, dict) else {}
137
+
138
+
139
+ def _write_session_default(key: str, value: str) -> None:
140
+ """Remember a wallet/network switch so the next MCP session starts there.
141
+
142
+ set_wallet_backend/set_evm_network previously only mutated this process's
143
+ module globals ("session_override_active"/"config_file_changed: False" by
144
+ design) — every new session went back to the static openclaw.json/env
145
+ default. This is the one persisted side effect: a small, agent-wallet-
146
+ owned file, separate from the shared openclaw.json so a write here can
147
+ never clobber unrelated host config. Best-effort — a write failure must
148
+ never break the tool call that triggered it.
149
+ """
150
+ path = _session_defaults_path()
151
+ try:
152
+ data = _read_session_defaults()
153
+ data[key] = value
154
+ path.parent.mkdir(parents=True, exist_ok=True)
155
+ fd, temp_path = tempfile.mkstemp(prefix=f".{path.name}.", dir=str(path.parent))
156
+ try:
157
+ with os.fdopen(fd, "w", encoding="utf-8") as handle:
158
+ handle.write(json.dumps(data, indent=2, sort_keys=True) + "\n")
159
+ handle.flush()
160
+ os.fsync(handle.fileno())
161
+ os.chmod(temp_path, 0o600)
162
+ os.replace(temp_path, path)
163
+ except Exception:
164
+ with suppress(FileNotFoundError):
165
+ os.unlink(temp_path)
166
+ raise
167
+ except OSError:
168
+ pass
169
+
170
+
125
171
  @lru_cache(maxsize=1)
126
172
  def _openclaw_plugin_config() -> dict[str, Any]:
127
173
  # Cached for the process lifetime: openclaw.json is read once per MCP server
@@ -469,9 +515,18 @@ def _normalize_btc_network(value: Any) -> str | None:
469
515
 
470
516
 
471
517
  def _default_backend() -> str:
518
+ # Precedence: explicit env override > last backend the user actually
519
+ # picked via set_wallet_backend/set_evm_network in some previous session
520
+ # > the host's static openclaw.json config > the hardcoded fallback.
521
+ session_default = _read_session_defaults().get("backend")
522
+ try:
523
+ normalized_session_default = _normalize_wallet_backend(session_default) if session_default else None
524
+ except RuntimeError:
525
+ normalized_session_default = None
472
526
  return _normalize_wallet_backend(
473
527
  os.getenv("AGENT_WALLET_BACKEND")
474
528
  or os.getenv("OPENCLAW_AGENT_WALLET_BACKEND")
529
+ or normalized_session_default
475
530
  or _configured_backend()
476
531
  or "solana_local"
477
532
  )
@@ -481,6 +536,12 @@ def _default_evm_network() -> str | None:
481
536
  configured = _normalize_evm_network(os.getenv("WDK_EVM_NETWORK"))
482
537
  if configured in {"ethereum", "base", "robinhood", "goat"}:
483
538
  return configured
539
+ session_default = _read_session_defaults().get("evm_network")
540
+ if session_default:
541
+ try:
542
+ return _normalize_selectable_evm_network(session_default)
543
+ except RuntimeError:
544
+ pass
484
545
  return _configured_network_for_backend("wdk_evm_local")
485
546
 
486
547
 
@@ -1381,6 +1442,9 @@ async def _handle_set_wallet_backend(params: dict[str, Any]) -> dict[str, Any]:
1381
1442
  else:
1382
1443
  selected_solana_network = resolved_network
1383
1444
  selected_wallet_backend = backend
1445
+ _write_session_default("backend", backend)
1446
+ if backend == "wdk_evm_local":
1447
+ _write_session_default("evm_network", resolved_network)
1384
1448
  return {
1385
1449
  "selected_backend": backend,
1386
1450
  "selected_wallet": _backend_label(backend),
@@ -1388,9 +1452,11 @@ async def _handle_set_wallet_backend(params: dict[str, Any]) -> dict[str, Any]:
1388
1452
  "configured_backend": _default_backend(),
1389
1453
  "session_override_active": True,
1390
1454
  "config_file_changed": False,
1455
+ "remembered_as_default": True,
1391
1456
  "usage": (
1392
- "Subsequent wallet calls in this Codex MCP session use this wallet backend by "
1393
- "default. The runtime startup config remains unchanged."
1457
+ "Wallet calls in this session use this backend by default, and the next MCP "
1458
+ "session starts here too. openclaw.json/env config is unchanged; use those for "
1459
+ "a fixed deployment-wide default instead."
1394
1460
  ),
1395
1461
  "data": payload.get("data", {}),
1396
1462
  }
@@ -1407,15 +1473,19 @@ async def _handle_set_evm_network(params: dict[str, Any]) -> dict[str, Any]:
1407
1473
  raise RuntimeError(str(payload.get("error") or "set_evm_network failed"))
1408
1474
  selected_wallet_backend = "wdk_evm_local"
1409
1475
  selected_evm_network = network
1476
+ _write_session_default("backend", "wdk_evm_local")
1477
+ _write_session_default("evm_network", network)
1410
1478
  return {
1411
1479
  "selected_backend": "wdk_evm_local",
1412
1480
  "selected_wallet": "evm",
1413
1481
  "selected_network": network,
1414
1482
  "session_active_network": network,
1415
1483
  "session_override_active": True,
1484
+ "remembered_as_default": True,
1416
1485
  "usage": (
1417
- "Subsequent EVM wallet calls in this Codex MCP session use this network by default. "
1418
- "You can still override a single EVM call with its network parameter."
1486
+ "EVM wallet calls in this session use this network by default, and the next MCP "
1487
+ "session starts here too. You can still override a single call with its network "
1488
+ "parameter."
1419
1489
  ),
1420
1490
  "data": payload.get("data", {}),
1421
1491
  }
@@ -1,5 +1,5 @@
1
1
  name: agent-wallet
2
- version: 0.1.102
2
+ version: 0.1.106
3
3
  description: Thin Hermes Agent bridge to the existing AgentLayer/OpenClaw wallet backend
4
4
  provides_tools:
5
5
  - agent_wallet_tools
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayer.tech/wallet",
3
- "version": "0.1.102",
3
+ "version": "0.1.106",
4
4
  "description": "Universal AgentLayer wallet installer for OpenClaw, Codex, Claude Code, and Hermes.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wdk-btc-wallet",
3
- "version": "0.1.102",
3
+ "version": "0.1.106",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Separate BTC-only wallet service built on Tether WDK.",