@agentlayer.tech/wallet 0.1.11 → 0.1.13

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 (29) hide show
  1. package/.openclaw/extensions/agent-wallet/index.ts +454 -18
  2. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +96 -0
  3. package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +2 -0
  4. package/CHANGELOG.md +25 -0
  5. package/README.md +43 -51
  6. package/agent-wallet/.env.example +11 -0
  7. package/agent-wallet/README.md +53 -0
  8. package/agent-wallet/agent_wallet/approval.py +4 -0
  9. package/agent-wallet/agent_wallet/config.py +6 -0
  10. package/agent-wallet/agent_wallet/exceptions.py +2 -1
  11. package/agent-wallet/agent_wallet/openclaw_adapter.py +361 -2
  12. package/agent-wallet/agent_wallet/openclaw_cli.py +13 -1
  13. package/agent-wallet/agent_wallet/openclaw_runtime.py +2 -5
  14. package/agent-wallet/agent_wallet/providers/houdini.py +539 -0
  15. package/agent-wallet/agent_wallet/transaction_policy.py +251 -0
  16. package/agent-wallet/agent_wallet/user_wallets.py +83 -0
  17. package/agent-wallet/agent_wallet/wallet_layer/base.py +40 -0
  18. package/agent-wallet/agent_wallet/wallet_layer/solana.py +885 -16
  19. package/agent-wallet/pyproject.toml +1 -1
  20. package/agent-wallet/scripts/bootstrap_openclaw_evm.py +291 -0
  21. package/agent-wallet/scripts/install_agent_wallet.py +54 -2
  22. package/agent-wallet/scripts/install_openclaw_local_config.py +84 -4
  23. package/agent-wallet/scripts/manage_openclaw_evm_wallet.py +343 -0
  24. package/agent-wallet/scripts/setup_evm_wallet.sh +151 -0
  25. package/hermes/plugins/agent_wallet/__init__.py +28 -2
  26. package/hermes/plugins/agent_wallet/plugin.yaml +2 -0
  27. package/hermes/plugins/agent_wallet/schemas.py +72 -0
  28. package/hermes/plugins/agent_wallet/tools.py +193 -9
  29. package/package.json +2 -2
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openclaw-agent-wallet"
7
- version = "0.1.11"
7
+ version = "0.1.13"
8
8
  description = "Plugin-friendly wallet backend for OpenClaw agents"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env python3
2
+ """One-command host bootstrap for the local OpenClaw EVM wallet flow."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import json
8
+ import os
9
+ import subprocess
10
+ import sys
11
+ import time
12
+ from pathlib import Path
13
+ from urllib.error import URLError
14
+ from urllib.parse import urlparse
15
+ from urllib.request import urlopen
16
+
17
+ sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
18
+
19
+ from agent_wallet.file_ops import atomic_write_text, chmod_if_exists
20
+
21
+
22
+ def _default_config_path() -> Path:
23
+ return Path(os.path.expanduser("~/.openclaw/openclaw.json"))
24
+
25
+
26
+ def _default_user_id() -> str:
27
+ return f"{os.getenv('USER', 'openclaw-user')}-local"
28
+
29
+
30
+ def _default_python_bin() -> str:
31
+ return os.getenv("OPENCLAW_AGENT_WALLET_PYTHON", sys.executable)
32
+
33
+
34
+ def _package_root() -> Path:
35
+ return Path(__file__).resolve().parents[1]
36
+
37
+
38
+ def _repo_root() -> Path:
39
+ return Path(__file__).resolve().parents[2]
40
+
41
+
42
+ def _script_path(name: str) -> Path:
43
+ return _package_root() / "scripts" / name
44
+
45
+
46
+ def _normalize_network(value: str) -> str:
47
+ network = str(value or "").strip().lower()
48
+ aliases = {
49
+ "mainnet": "ethereum",
50
+ "eth": "ethereum",
51
+ "eth-mainnet": "ethereum",
52
+ "base-mainnet": "base",
53
+ "base_sepolia": "base-sepolia",
54
+ }
55
+ network = aliases.get(network, network)
56
+ if network not in {"ethereum", "sepolia", "base", "base-sepolia"}:
57
+ return "ethereum"
58
+ return network
59
+
60
+
61
+ def build_parser() -> argparse.ArgumentParser:
62
+ parser = argparse.ArgumentParser(description=__doc__)
63
+ parser.add_argument("--config-path", default=str(_default_config_path()))
64
+ parser.add_argument("--plugin-id", default="agent-wallet")
65
+ parser.add_argument("--user-id", default=_default_user_id())
66
+ parser.add_argument("--network", default="base")
67
+ parser.add_argument("--service-url", default="http://127.0.0.1:8081")
68
+ parser.add_argument("--wdk-wallet-root", default=str(_repo_root() / "wdk-evm-wallet"))
69
+ parser.add_argument("--label", default="Agent EVM Wallet")
70
+ parser.add_argument("--account-index", type=int, default=0)
71
+ parser.add_argument("--python-bin", default=_default_python_bin())
72
+ parser.add_argument("--package-root", default=str(_package_root()))
73
+ parser.add_argument("--password-stdin", action=argparse.BooleanOptionalAction, default=False)
74
+ parser.add_argument("--sign-only", action=argparse.BooleanOptionalAction, default=False)
75
+ parser.add_argument("--auto-start-service", action=argparse.BooleanOptionalAction, default=True)
76
+ parser.add_argument("--bind-network-pair", action=argparse.BooleanOptionalAction, default=True)
77
+ return parser
78
+
79
+
80
+ def _ensure_openclaw_config(config_path: Path) -> bool:
81
+ if config_path.exists():
82
+ return False
83
+ config_path.parent.mkdir(parents=True, exist_ok=True)
84
+ atomic_write_text(
85
+ config_path,
86
+ json.dumps({"plugins": {"entries": {}}, "tools": {"alsoAllow": []}}, indent=2) + "\n",
87
+ mode=0o600,
88
+ )
89
+ chmod_if_exists(config_path, 0o600)
90
+ return True
91
+
92
+
93
+ def _run_script(
94
+ python_bin: str,
95
+ script_name: str,
96
+ args: list[str],
97
+ *,
98
+ stdin_text: str | None = None,
99
+ ) -> dict:
100
+ completed = subprocess.run(
101
+ [python_bin, str(_script_path(script_name)), *args],
102
+ check=True,
103
+ capture_output=True,
104
+ text=True,
105
+ input=stdin_text,
106
+ env=os.environ.copy(),
107
+ )
108
+ return json.loads(completed.stdout)
109
+
110
+
111
+ def _health_url(service_url: str) -> str:
112
+ return f"{service_url.rstrip('/')}/health"
113
+
114
+
115
+ def _service_is_healthy(service_url: str) -> bool:
116
+ try:
117
+ with urlopen(_health_url(service_url), timeout=1.5) as response:
118
+ return int(getattr(response, "status", 0) or 0) == 200
119
+ except (URLError, TimeoutError, OSError):
120
+ return False
121
+
122
+
123
+ def _is_local_service_url(service_url: str) -> bool:
124
+ parsed = urlparse(service_url)
125
+ return parsed.scheme in {"http", "https"} and parsed.hostname in {"127.0.0.1", "localhost", "::1"}
126
+
127
+
128
+ def _require_local_service_url(service_url: str) -> None:
129
+ if not _is_local_service_url(service_url):
130
+ raise SystemExit(
131
+ f"EVM bootstrap only supports a localhost service URL. Refusing non-local endpoint: {service_url}"
132
+ )
133
+
134
+
135
+ def _service_log_dir(config_path: Path) -> Path:
136
+ return config_path.expanduser().parent / "logs"
137
+
138
+
139
+ def _service_log_path(config_path: Path) -> Path:
140
+ return _service_log_dir(config_path) / "wdk-evm-wallet.log"
141
+
142
+
143
+ def _auto_start_local_service(
144
+ *,
145
+ service_url: str,
146
+ network: str,
147
+ wdk_wallet_root: Path,
148
+ config_path: Path,
149
+ ) -> dict[str, object]:
150
+ if _service_is_healthy(service_url):
151
+ return {"started": False, "already_healthy": True}
152
+
153
+ if not _is_local_service_url(service_url):
154
+ raise SystemExit(
155
+ f"EVM service at {service_url} is unreachable and auto-start is only supported for localhost URLs."
156
+ )
157
+
158
+ run_local = wdk_wallet_root / "run-local.sh"
159
+ if not run_local.exists():
160
+ raise SystemExit(f"Could not find wdk-evm-wallet launcher: {run_local}")
161
+
162
+ parsed = urlparse(service_url)
163
+ host = parsed.hostname or "127.0.0.1"
164
+ port = parsed.port or 8081
165
+ log_dir = _service_log_dir(config_path)
166
+ log_dir.mkdir(parents=True, exist_ok=True)
167
+ log_path = _service_log_path(config_path)
168
+
169
+ env = os.environ.copy()
170
+ env["HOST"] = host
171
+ env["PORT"] = str(port)
172
+ env["WDK_EVM_NETWORK"] = network
173
+
174
+ with log_path.open("a", encoding="utf-8") as log_file:
175
+ process = subprocess.Popen( # noqa: S603
176
+ ["sh", str(run_local)],
177
+ cwd=str(wdk_wallet_root),
178
+ env=env,
179
+ stdin=subprocess.DEVNULL,
180
+ stdout=log_file,
181
+ stderr=log_file,
182
+ start_new_session=True,
183
+ )
184
+
185
+ deadline = time.time() + 30.0
186
+ while time.time() < deadline:
187
+ if _service_is_healthy(service_url):
188
+ return {
189
+ "started": True,
190
+ "already_healthy": False,
191
+ "pid": process.pid,
192
+ "log_path": str(log_path),
193
+ }
194
+ if process.poll() is not None:
195
+ raise SystemExit(
196
+ f"wdk-evm-wallet exited before becoming healthy. Check log: {log_path}"
197
+ )
198
+ time.sleep(0.5)
199
+
200
+ raise SystemExit(
201
+ f"Timed out waiting for wdk-evm-wallet health at {_health_url(service_url)}. Check log: {log_path}"
202
+ )
203
+
204
+
205
+ def main() -> int:
206
+ args = build_parser().parse_args()
207
+ effective_network = _normalize_network(args.network)
208
+ _require_local_service_url(args.service_url)
209
+ config_path = Path(args.config_path).expanduser()
210
+ config_created = _ensure_openclaw_config(config_path)
211
+ service_bootstrap: dict[str, object] | None = None
212
+ if args.auto_start_service:
213
+ service_bootstrap = _auto_start_local_service(
214
+ service_url=args.service_url,
215
+ network=effective_network,
216
+ wdk_wallet_root=Path(args.wdk_wallet_root).expanduser(),
217
+ config_path=config_path,
218
+ )
219
+ elif not _service_is_healthy(args.service_url):
220
+ raise SystemExit(
221
+ f"EVM service is not healthy at {_health_url(args.service_url)} and --no-auto-start-service was set."
222
+ )
223
+
224
+ stdin_text = sys.stdin.read() if args.password_stdin else None
225
+ setup_payload = _run_script(
226
+ args.python_bin,
227
+ "manage_openclaw_evm_wallet.py",
228
+ [
229
+ "setup",
230
+ "--user-id",
231
+ args.user_id,
232
+ "--network",
233
+ effective_network,
234
+ "--service-url",
235
+ args.service_url,
236
+ "--label",
237
+ args.label,
238
+ "--account-index",
239
+ str(args.account_index),
240
+ *([] if not args.password_stdin else ["--password-stdin"]),
241
+ *(["--bind-network-pair"] if args.bind_network_pair else ["--no-bind-network-pair"]),
242
+ ],
243
+ stdin_text=stdin_text,
244
+ )
245
+
246
+ wallet = dict(setup_payload.get("wallet") or {})
247
+ install_args = [
248
+ "--config-path",
249
+ str(config_path),
250
+ "--plugin-id",
251
+ args.plugin_id,
252
+ "--user-id",
253
+ args.user_id,
254
+ "--backend",
255
+ "wdk_evm_local",
256
+ "--network",
257
+ effective_network,
258
+ "--wdk-evm-service-url",
259
+ args.service_url,
260
+ "--wdk-evm-wallet-id",
261
+ str(wallet.get("wallet_id") or ""),
262
+ "--wdk-evm-account-index",
263
+ str(wallet.get("account_index") or args.account_index),
264
+ "--package-root",
265
+ args.package_root,
266
+ "--python-bin",
267
+ args.python_bin,
268
+ "--sign-only" if args.sign_only else "--no-sign-only",
269
+ ]
270
+ install_payload = _run_script(
271
+ args.python_bin,
272
+ "install_openclaw_local_config.py",
273
+ install_args,
274
+ )
275
+
276
+ print(
277
+ json.dumps(
278
+ {
279
+ "ok": True,
280
+ "config_created": config_created,
281
+ "service_bootstrap": service_bootstrap,
282
+ "evm_setup": setup_payload,
283
+ "openclaw_install": install_payload,
284
+ }
285
+ )
286
+ )
287
+ return 0
288
+
289
+
290
+ if __name__ == "__main__":
291
+ raise SystemExit(main())
@@ -44,6 +44,7 @@ EXCLUDED_RUNTIME_DIR_NAMES = {
44
44
  }
45
45
  EXCLUDED_RUNTIME_FILE_NAMES = {
46
46
  ".DS_Store",
47
+ ".env",
47
48
  }
48
49
  EXCLUDED_RUNTIME_SUFFIXES = {
49
50
  ".pyc",
@@ -270,6 +271,39 @@ def _ensure_env_file(env_path: Path, env_example_path: Path) -> bool:
270
271
  return True
271
272
 
272
273
 
274
+ def _upsert_env_value(env_path: Path, key: str, value: str) -> bool:
275
+ if not env_path.exists():
276
+ return False
277
+ lines = env_path.read_text(encoding="utf-8").splitlines()
278
+ updated = False
279
+ replaced = False
280
+ prefix = f"{key}="
281
+ new_line = f"{key}={value}"
282
+ for index, line in enumerate(lines):
283
+ if line.startswith(prefix):
284
+ replaced = True
285
+ if line != new_line:
286
+ lines[index] = new_line
287
+ updated = True
288
+ break
289
+ if not replaced:
290
+ if lines and lines[-1] != "":
291
+ lines.append("")
292
+ lines.append(new_line)
293
+ updated = True
294
+ if updated:
295
+ _atomic_write_text(env_path, "\n".join(lines) + "\n", mode=0o600)
296
+ _chmod_if_exists(env_path, 0o600)
297
+ return updated
298
+
299
+
300
+ def _ensure_runtime_boot_key_file_env(env_path: Path) -> bool:
301
+ boot_key_file = _resolve_openclaw_home() / "agent-wallet-runtime" / "boot-key"
302
+ if not boot_key_file.exists():
303
+ return False
304
+ return _upsert_env_value(env_path, "AGENT_WALLET_BOOT_KEY_FILE", str(boot_key_file))
305
+
306
+
273
307
  def _ensure_openclaw_config(config_path: Path) -> bool:
274
308
  if config_path.exists():
275
309
  return False
@@ -288,6 +322,22 @@ def _venv_python(venv_path: Path) -> Path:
288
322
  return venv_path / "bin" / "python"
289
323
 
290
324
 
325
+ def _venv_python_wrapper(venv_path: Path) -> Path:
326
+ if os.name == "nt":
327
+ return _venv_python(venv_path)
328
+ return venv_path / "bin" / "openclaw-agent-wallet-python"
329
+
330
+
331
+ def _ensure_python_wrapper(venv_path: Path) -> Path:
332
+ if os.name == "nt":
333
+ return _venv_python(venv_path)
334
+ wrapper = _venv_python_wrapper(venv_path)
335
+ wrapper.parent.mkdir(parents=True, exist_ok=True)
336
+ wrapper.write_text('#!/bin/sh\nexec "$(dirname "$0")/python" "$@"\n', encoding="utf-8")
337
+ wrapper.chmod(0o755)
338
+ return wrapper
339
+
340
+
291
341
  def _ensure_python_runtime(venv_path: Path, package_root: Path) -> tuple[Path, bool]:
292
342
  created = False
293
343
  python_bin = _venv_python(venv_path)
@@ -299,7 +349,7 @@ def _ensure_python_runtime(venv_path: Path, package_root: Path) -> tuple[Path, b
299
349
  [str(python_bin), "-m", "pip", "install", "-e", str(package_root)],
300
350
  check=True,
301
351
  )
302
- return python_bin, created
352
+ return _ensure_python_wrapper(venv_path), created
303
353
 
304
354
 
305
355
  def _ensure_node_runtime(npm_bin: str, project_root: Path) -> dict[str, object]:
@@ -441,6 +491,7 @@ def main() -> None:
441
491
  env_example_path = package_root / ".env.example"
442
492
 
443
493
  env_created = _ensure_env_file(env_path, env_example_path)
494
+ boot_key_file_env_updated = _ensure_runtime_boot_key_file_env(env_path)
444
495
  config_created = _ensure_openclaw_config(config_path)
445
496
 
446
497
  python_bin = Path(sys.executable)
@@ -449,7 +500,7 @@ def main() -> None:
449
500
  if not args.dry_run:
450
501
  python_bin, venv_created = _ensure_python_runtime(venv_path, package_root)
451
502
  else:
452
- python_bin = _venv_python(venv_path)
503
+ python_bin = _venv_python_wrapper(venv_path)
453
504
 
454
505
  node_runtime = {
455
506
  "skipped": bool(args.skip_node_setup),
@@ -500,6 +551,7 @@ def main() -> None:
500
551
  "ok": True,
501
552
  "env_path": str(env_path),
502
553
  "env_created": env_created,
554
+ "boot_key_file_env_updated": boot_key_file_env_updated,
503
555
  "config_path": str(config_path),
504
556
  "config_created": config_created,
505
557
  "package_root": str(package_root),
@@ -16,6 +16,25 @@ from agent_wallet.sealed_keys import resolve_sealed_keys_path, seal_keys, unseal
16
16
  from security_utils import write_redacted_backup
17
17
 
18
18
  OPTIONAL_TOOLS = [
19
+ "get_wallet_capabilities",
20
+ "get_wallet_address",
21
+ "get_wallet_balance",
22
+ "get_active_wallet_backend",
23
+ "set_wallet_backend",
24
+ "get_wallet_portfolio",
25
+ "get_solana_token_prices",
26
+ "swap_solana_privately",
27
+ "continue_solana_private_swap",
28
+ "list_pending_solana_private_swaps",
29
+ "get_solana_private_swap_status",
30
+ "get_kamino_lend_markets",
31
+ "get_kamino_lend_market_reserves",
32
+ "get_kamino_lend_user_obligations",
33
+ "get_kamino_lend_user_rewards",
34
+ "kamino_lend_deposit",
35
+ "kamino_lend_withdraw",
36
+ "kamino_lend_borrow",
37
+ "kamino_lend_repay",
19
38
  "sign_wallet_message",
20
39
  "transfer_sol",
21
40
  "transfer_btc",
@@ -30,20 +49,60 @@ def _default_config_path() -> Path:
30
49
  return Path(os.path.expanduser("~/.openclaw/openclaw.json"))
31
50
 
32
51
 
52
+ def _resolve_openclaw_home() -> Path:
53
+ return Path(os.path.expanduser(os.getenv("OPENCLAW_HOME", "~/.openclaw")))
54
+
55
+
56
+ def _default_runtime_root() -> Path:
57
+ explicit_target = os.getenv("OPENCLAW_INSTALL_TARGET", "").strip()
58
+ if explicit_target:
59
+ return Path(explicit_target).expanduser()
60
+ explicit_root = os.getenv("OPENCLAW_INSTALL_ROOT", "").strip()
61
+ if explicit_root:
62
+ return Path(explicit_root).expanduser() / "current"
63
+ return _resolve_openclaw_home() / "agent-wallet-runtime" / "current"
64
+
65
+
33
66
  def _repo_root() -> Path:
34
67
  return Path(__file__).resolve().parents[2]
35
68
 
36
69
 
70
+ def _trusted_runtime_root() -> Path | None:
71
+ runtime_root = _default_runtime_root().resolve()
72
+ plugin_manifest = runtime_root / ".openclaw" / "extensions" / "agent-wallet" / "openclaw.plugin.json"
73
+ package_root = runtime_root / "agent-wallet"
74
+ if plugin_manifest.exists() and package_root.exists():
75
+ return runtime_root
76
+ return None
77
+
78
+
37
79
  def _default_extension_path() -> Path:
80
+ runtime_root = _trusted_runtime_root()
81
+ if runtime_root is not None:
82
+ return runtime_root / ".openclaw" / "extensions" / "agent-wallet"
38
83
  return _repo_root() / ".openclaw" / "extensions" / "agent-wallet"
39
84
 
40
85
 
41
86
  def _default_package_root() -> Path:
87
+ runtime_root = _trusted_runtime_root()
88
+ if runtime_root is not None:
89
+ return runtime_root / "agent-wallet"
42
90
  return Path(__file__).resolve().parents[1]
43
91
 
44
92
 
45
93
  def _default_python_bin() -> str:
46
- return os.getenv("OPENCLAW_AGENT_WALLET_PYTHON", sys.executable)
94
+ explicit = os.getenv("OPENCLAW_AGENT_WALLET_PYTHON", "").strip()
95
+ if explicit:
96
+ return explicit
97
+ runtime_root = _trusted_runtime_root()
98
+ if runtime_root is not None:
99
+ wrapper = runtime_root / "agent-wallet" / ".runtime-venv" / "bin" / "openclaw-agent-wallet-python"
100
+ if wrapper.exists():
101
+ return str(wrapper)
102
+ runtime_python = runtime_root / "agent-wallet" / ".runtime-venv" / "bin" / "python"
103
+ if runtime_python.exists():
104
+ return str(runtime_python)
105
+ return sys.executable
47
106
 
48
107
 
49
108
  def _default_user_id() -> str:
@@ -64,7 +123,7 @@ def build_parser() -> argparse.ArgumentParser:
64
123
  parser = argparse.ArgumentParser(description=__doc__)
65
124
  parser.add_argument("--config-path", default=str(_default_config_path()))
66
125
  parser.add_argument("--plugin-id", default="agent-wallet")
67
- parser.add_argument("--user-id", default=_default_user_id())
126
+ parser.add_argument("--user-id", default="")
68
127
  parser.add_argument("--backend", default="solana_local")
69
128
  parser.add_argument("--network", default="devnet")
70
129
  parser.add_argument("--rpc-url", default="")
@@ -72,6 +131,9 @@ def build_parser() -> argparse.ArgumentParser:
72
131
  parser.add_argument("--wdk-btc-service-url", default="")
73
132
  parser.add_argument("--wdk-btc-wallet-id", default="")
74
133
  parser.add_argument("--wdk-btc-account-index", type=int, default=0)
134
+ parser.add_argument("--wdk-evm-service-url", default="")
135
+ parser.add_argument("--wdk-evm-wallet-id", default="")
136
+ parser.add_argument("--wdk-evm-account-index", type=int, default=0)
75
137
  parser.add_argument("--sign-only", action=argparse.BooleanOptionalAction, default=False)
76
138
  parser.add_argument("--encrypt-user-wallets", action=argparse.BooleanOptionalAction, default=True)
77
139
  parser.add_argument(
@@ -161,8 +223,20 @@ def main() -> None:
161
223
 
162
224
  entries = plugins.setdefault("entries", {})
163
225
  effective_network = _normalize_network(args.backend, args.network)
226
+ existing_entry = entries.get(args.plugin_id) if isinstance(entries.get(args.plugin_id), dict) else {}
227
+ existing_config = (
228
+ dict(existing_entry.get("config"))
229
+ if isinstance(existing_entry.get("config"), dict)
230
+ else {}
231
+ )
232
+ resolved_user_id = (
233
+ args.user_id.strip()
234
+ or str(existing_config.get("userId") or "").strip()
235
+ or _default_user_id()
236
+ )
164
237
  plugin_config = {
165
- "userId": args.user_id,
238
+ **existing_config,
239
+ "userId": resolved_user_id,
166
240
  "backend": args.backend,
167
241
  "network": effective_network,
168
242
  "signOnly": args.sign_only,
@@ -183,6 +257,12 @@ def main() -> None:
183
257
  plugin_config["wdkBtcWalletId"] = args.wdk_btc_wallet_id.strip()
184
258
  if args.wdk_btc_account_index is not None:
185
259
  plugin_config["wdkBtcAccountIndex"] = int(args.wdk_btc_account_index)
260
+ if args.wdk_evm_service_url.strip():
261
+ plugin_config["wdkEvmServiceUrl"] = args.wdk_evm_service_url.strip()
262
+ if args.wdk_evm_wallet_id.strip():
263
+ plugin_config["wdkEvmWalletId"] = args.wdk_evm_wallet_id.strip()
264
+ if args.wdk_evm_account_index is not None:
265
+ plugin_config["wdkEvmAccountIndex"] = int(args.wdk_evm_account_index)
186
266
  if args.write_master_key:
187
267
  raise SystemExit(
188
268
  "Refusing to write masterKey into config. Runtime secrets must live in sealed_keys.json."
@@ -214,7 +294,7 @@ def main() -> None:
214
294
  "python_bin": args.python_bin,
215
295
  "package_root": plugin_config["packageRoot"],
216
296
  "plugin_id": args.plugin_id,
217
- "user_id": args.user_id,
297
+ "user_id": resolved_user_id,
218
298
  "sealed_keys_path": sealed_keys_path,
219
299
  },
220
300
  indent=2,