@agentlayer.tech/wallet 0.1.12 → 0.1.14

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 (31) hide show
  1. package/.openclaw/AGENTS.md +10 -1
  2. package/.openclaw/extensions/agent-wallet/index.ts +454 -18
  3. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +96 -0
  4. package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +2 -0
  5. package/.openclaw/extensions/pay-bridge/README.md +32 -0
  6. package/.openclaw/extensions/pay-bridge/core.mjs +287 -0
  7. package/.openclaw/extensions/pay-bridge/index.ts +196 -0
  8. package/.openclaw/extensions/pay-bridge/openclaw.plugin.json +34 -0
  9. package/.openclaw/extensions/pay-bridge/package.json +11 -0
  10. package/.openclaw/extensions/pay-bridge/skills/pay-operator/SKILL.md +20 -0
  11. package/.openclaw/extensions/pay-bridge/smoke_pay_bridge.mjs +38 -0
  12. package/CHANGELOG.md +10 -0
  13. package/README.md +16 -2
  14. package/agent-wallet/.env.example +11 -0
  15. package/agent-wallet/README.md +29 -0
  16. package/agent-wallet/agent_wallet/approval.py +4 -0
  17. package/agent-wallet/agent_wallet/config.py +6 -0
  18. package/agent-wallet/agent_wallet/exceptions.py +2 -1
  19. package/agent-wallet/agent_wallet/openclaw_adapter.py +361 -2
  20. package/agent-wallet/agent_wallet/openclaw_cli.py +13 -1
  21. package/agent-wallet/agent_wallet/openclaw_runtime.py +2 -5
  22. package/agent-wallet/agent_wallet/providers/houdini.py +539 -0
  23. package/agent-wallet/agent_wallet/transaction_policy.py +251 -0
  24. package/agent-wallet/agent_wallet/user_wallets.py +83 -0
  25. package/agent-wallet/agent_wallet/wallet_layer/base.py +40 -0
  26. package/agent-wallet/agent_wallet/wallet_layer/solana.py +885 -16
  27. package/agent-wallet/pyproject.toml +1 -1
  28. package/agent-wallet/scripts/install_agent_wallet.py +54 -2
  29. package/agent-wallet/scripts/install_openclaw_local_config.py +128 -6
  30. package/hermes/plugins/agent_wallet/tools.py +93 -9
  31. package/package.json +2 -1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "openclaw-agent-wallet"
7
- version = "0.1.12"
7
+ version = "0.1.14"
8
8
  description = "Plugin-friendly wallet backend for OpenClaw agents"
9
9
  requires-python = ">=3.10"
10
10
  dependencies = [
@@ -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),
@@ -1,10 +1,11 @@
1
- """Patch an OpenClaw config file for the agent-wallet plugin."""
1
+ """Patch an OpenClaw config file for the AgentLayer OpenClaw plugins."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
5
  import argparse
6
6
  import json
7
7
  import os
8
+ import shutil
8
9
  import sys
9
10
  from datetime import datetime, timezone
10
11
  from pathlib import Path
@@ -16,6 +17,25 @@ from agent_wallet.sealed_keys import resolve_sealed_keys_path, seal_keys, unseal
16
17
  from security_utils import write_redacted_backup
17
18
 
18
19
  OPTIONAL_TOOLS = [
20
+ "get_wallet_capabilities",
21
+ "get_wallet_address",
22
+ "get_wallet_balance",
23
+ "get_active_wallet_backend",
24
+ "set_wallet_backend",
25
+ "get_wallet_portfolio",
26
+ "get_solana_token_prices",
27
+ "swap_solana_privately",
28
+ "continue_solana_private_swap",
29
+ "list_pending_solana_private_swaps",
30
+ "get_solana_private_swap_status",
31
+ "get_kamino_lend_markets",
32
+ "get_kamino_lend_market_reserves",
33
+ "get_kamino_lend_user_obligations",
34
+ "get_kamino_lend_user_rewards",
35
+ "kamino_lend_deposit",
36
+ "kamino_lend_withdraw",
37
+ "kamino_lend_borrow",
38
+ "kamino_lend_repay",
19
39
  "sign_wallet_message",
20
40
  "transfer_sol",
21
41
  "transfer_btc",
@@ -25,25 +45,89 @@ OPTIONAL_TOOLS = [
25
45
  "request_devnet_airdrop",
26
46
  ]
27
47
 
48
+ PAY_BRIDGE_PLUGIN_ID = "pay-bridge"
49
+ PAY_BRIDGE_TOOLS = [
50
+ "pay_status",
51
+ "pay_wallet_info",
52
+ "pay_search_services",
53
+ "pay_get_service_endpoints",
54
+ "pay_api_request",
55
+ ]
56
+
28
57
 
29
58
  def _default_config_path() -> Path:
30
59
  return Path(os.path.expanduser("~/.openclaw/openclaw.json"))
31
60
 
32
61
 
62
+ def _resolve_openclaw_home() -> Path:
63
+ return Path(os.path.expanduser(os.getenv("OPENCLAW_HOME", "~/.openclaw")))
64
+
65
+
66
+ def _default_runtime_root() -> Path:
67
+ explicit_target = os.getenv("OPENCLAW_INSTALL_TARGET", "").strip()
68
+ if explicit_target:
69
+ return Path(explicit_target).expanduser()
70
+ explicit_root = os.getenv("OPENCLAW_INSTALL_ROOT", "").strip()
71
+ if explicit_root:
72
+ return Path(explicit_root).expanduser() / "current"
73
+ return _resolve_openclaw_home() / "agent-wallet-runtime" / "current"
74
+
75
+
33
76
  def _repo_root() -> Path:
34
77
  return Path(__file__).resolve().parents[2]
35
78
 
36
79
 
80
+ def _trusted_runtime_root() -> Path | None:
81
+ runtime_root = _default_runtime_root().resolve()
82
+ plugin_manifest = runtime_root / ".openclaw" / "extensions" / "agent-wallet" / "openclaw.plugin.json"
83
+ package_root = runtime_root / "agent-wallet"
84
+ if plugin_manifest.exists() and package_root.exists():
85
+ return runtime_root
86
+ return None
87
+
88
+
37
89
  def _default_extension_path() -> Path:
90
+ runtime_root = _trusted_runtime_root()
91
+ if runtime_root is not None:
92
+ return runtime_root / ".openclaw" / "extensions" / "agent-wallet"
38
93
  return _repo_root() / ".openclaw" / "extensions" / "agent-wallet"
39
94
 
40
95
 
96
+ def _default_pay_bridge_extension_path() -> Path:
97
+ runtime_root = _trusted_runtime_root()
98
+ if runtime_root is not None:
99
+ return runtime_root / ".openclaw" / "extensions" / PAY_BRIDGE_PLUGIN_ID
100
+ return _repo_root() / ".openclaw" / "extensions" / PAY_BRIDGE_PLUGIN_ID
101
+
102
+
41
103
  def _default_package_root() -> Path:
104
+ runtime_root = _trusted_runtime_root()
105
+ if runtime_root is not None:
106
+ return runtime_root / "agent-wallet"
42
107
  return Path(__file__).resolve().parents[1]
43
108
 
44
109
 
45
110
  def _default_python_bin() -> str:
46
- return os.getenv("OPENCLAW_AGENT_WALLET_PYTHON", sys.executable)
111
+ explicit = os.getenv("OPENCLAW_AGENT_WALLET_PYTHON", "").strip()
112
+ if explicit:
113
+ return explicit
114
+ runtime_root = _trusted_runtime_root()
115
+ if runtime_root is not None:
116
+ wrapper = runtime_root / "agent-wallet" / ".runtime-venv" / "bin" / "openclaw-agent-wallet-python"
117
+ if wrapper.exists():
118
+ return str(wrapper)
119
+ runtime_python = runtime_root / "agent-wallet" / ".runtime-venv" / "bin" / "python"
120
+ if runtime_python.exists():
121
+ return str(runtime_python)
122
+ return sys.executable
123
+
124
+
125
+ def _default_pay_binary() -> str:
126
+ explicit = os.getenv("OPENCLAW_PAY_BINARY", "").strip()
127
+ if explicit:
128
+ return explicit
129
+ resolved = shutil.which("pay")
130
+ return resolved or "pay"
47
131
 
48
132
 
49
133
  def _default_user_id() -> str:
@@ -64,7 +148,7 @@ def build_parser() -> argparse.ArgumentParser:
64
148
  parser = argparse.ArgumentParser(description=__doc__)
65
149
  parser.add_argument("--config-path", default=str(_default_config_path()))
66
150
  parser.add_argument("--plugin-id", default="agent-wallet")
67
- parser.add_argument("--user-id", default=_default_user_id())
151
+ parser.add_argument("--user-id", default="")
68
152
  parser.add_argument("--backend", default="solana_local")
69
153
  parser.add_argument("--network", default="devnet")
70
154
  parser.add_argument("--rpc-url", default="")
@@ -83,8 +167,10 @@ def build_parser() -> argparse.ArgumentParser:
83
167
  default=True,
84
168
  )
85
169
  parser.add_argument("--extension-path", default=str(_default_extension_path()))
170
+ parser.add_argument("--pay-bridge-extension-path", default=str(_default_pay_bridge_extension_path()))
86
171
  parser.add_argument("--package-root", default=str(_default_package_root()))
87
172
  parser.add_argument("--python-bin", default=_default_python_bin())
173
+ parser.add_argument("--pay-binary", default=_default_pay_binary())
88
174
  parser.add_argument("--write-master-key", action=argparse.BooleanOptionalAction, default=False)
89
175
  return parser
90
176
 
@@ -155,17 +241,37 @@ def main() -> None:
155
241
 
156
242
  plugins = data.setdefault("plugins", {})
157
243
  plugins["enabled"] = True
244
+ allow = plugins.setdefault("allow", [])
245
+ if args.plugin_id not in allow:
246
+ allow.append(args.plugin_id)
247
+ if PAY_BRIDGE_PLUGIN_ID not in allow:
248
+ allow.append(PAY_BRIDGE_PLUGIN_ID)
158
249
 
159
250
  load = plugins.setdefault("load", {})
160
251
  paths = load.setdefault("paths", [])
161
252
  extension_path_text = str(Path(args.extension_path).expanduser().resolve())
162
253
  if extension_path_text not in paths:
163
254
  paths.append(extension_path_text)
255
+ pay_bridge_extension_path_text = str(Path(args.pay_bridge_extension_path).expanduser().resolve())
256
+ if pay_bridge_extension_path_text not in paths:
257
+ paths.append(pay_bridge_extension_path_text)
164
258
 
165
259
  entries = plugins.setdefault("entries", {})
166
260
  effective_network = _normalize_network(args.backend, args.network)
261
+ existing_entry = entries.get(args.plugin_id) if isinstance(entries.get(args.plugin_id), dict) else {}
262
+ existing_config = (
263
+ dict(existing_entry.get("config"))
264
+ if isinstance(existing_entry.get("config"), dict)
265
+ else {}
266
+ )
267
+ resolved_user_id = (
268
+ args.user_id.strip()
269
+ or str(existing_config.get("userId") or "").strip()
270
+ or _default_user_id()
271
+ )
167
272
  plugin_config = {
168
- "userId": args.user_id,
273
+ **existing_config,
274
+ "userId": resolved_user_id,
169
275
  "backend": args.backend,
170
276
  "network": effective_network,
171
277
  "signOnly": args.sign_only,
@@ -201,10 +307,25 @@ def main() -> None:
201
307
  "enabled": True,
202
308
  "config": plugin_config,
203
309
  }
310
+ existing_pay_entry = entries.get(PAY_BRIDGE_PLUGIN_ID) if isinstance(entries.get(PAY_BRIDGE_PLUGIN_ID), dict) else {}
311
+ existing_pay_config = (
312
+ dict(existing_pay_entry.get("config"))
313
+ if isinstance(existing_pay_entry.get("config"), dict)
314
+ else {}
315
+ )
316
+ pay_bridge_config = {
317
+ **existing_pay_config,
318
+ "payBinary": args.pay_binary.strip() or _default_pay_binary(),
319
+ "requireHttps": bool(existing_pay_config.get("requireHttps", True)),
320
+ }
321
+ entries[PAY_BRIDGE_PLUGIN_ID] = {
322
+ "enabled": True,
323
+ "config": pay_bridge_config,
324
+ }
204
325
 
205
326
  tools = data.setdefault("tools", {})
206
327
  also_allow = tools.setdefault("alsoAllow", [])
207
- for tool_name in OPTIONAL_TOOLS:
328
+ for tool_name in OPTIONAL_TOOLS + PAY_BRIDGE_TOOLS:
208
329
  if tool_name not in also_allow:
209
330
  also_allow.append(tool_name)
210
331
 
@@ -220,10 +341,11 @@ def main() -> None:
220
341
  "config_path": str(config_path),
221
342
  "backup_path": str(backup_path),
222
343
  "extension_path": extension_path_text,
344
+ "pay_bridge_extension_path": pay_bridge_extension_path_text,
223
345
  "python_bin": args.python_bin,
224
346
  "package_root": plugin_config["packageRoot"],
225
347
  "plugin_id": args.plugin_id,
226
- "user_id": args.user_id,
348
+ "user_id": resolved_user_id,
227
349
  "sealed_keys_path": sealed_keys_path,
228
350
  },
229
351
  indent=2,
@@ -15,6 +15,7 @@ from typing import Any
15
15
 
16
16
  SECRET_CONFIG_KEYS = {"privateKey", "masterKey", "approvalSecret"}
17
17
  BACKENDS = ("solana_local", "wdk_btc_local", "wdk_evm_local")
18
+ PREVIEW_BOUND_SWAP_TOOLS = {"swap_solana_tokens", "swap_solana_privately"}
18
19
 
19
20
 
20
21
  def _json(data: dict[str, Any]) -> str:
@@ -75,12 +76,12 @@ def _prune_preview_cache(cache: dict[str, Any]) -> dict[str, Any]:
75
76
 
76
77
 
77
78
  def _cache_swap_preview(tool_name: str, result: dict[str, Any], ttl_seconds: int = 900) -> None:
78
- if tool_name != "swap_solana_tokens" or result.get("ok") is not True:
79
+ if tool_name not in PREVIEW_BOUND_SWAP_TOOLS or result.get("ok") is not True:
79
80
  return
80
81
  preview = result.get("data")
81
82
  if not isinstance(preview, dict):
82
83
  return
83
- if preview.get("mode") != "preview" or preview.get("asset_type") != "swap":
84
+ if preview.get("mode") != "preview" or preview.get("asset_type") not in {"swap", "solana-private-swap"}:
84
85
  return
85
86
  summary = preview.get("confirmation_summary")
86
87
  if not isinstance(summary, dict):
@@ -192,6 +193,85 @@ def _user_id(args: dict[str, Any]) -> str:
192
193
  return str(value).strip() or "hermes-local-user"
193
194
 
194
195
 
196
+ def _normalize_backend(value: Any) -> str:
197
+ normalized = str(value or "").strip().lower()
198
+ aliases = {
199
+ "sol": "solana_local",
200
+ "solana": "solana_local",
201
+ "solana_local": "solana_local",
202
+ "solana-local": "solana_local",
203
+ "evm": "wdk_evm_local",
204
+ "ethereum": "wdk_evm_local",
205
+ "eth": "wdk_evm_local",
206
+ "base": "wdk_evm_local",
207
+ "wdk_evm_local": "wdk_evm_local",
208
+ "wdk-evm-local": "wdk_evm_local",
209
+ "btc": "wdk_btc_local",
210
+ "bitcoin": "wdk_btc_local",
211
+ "wdk_btc_local": "wdk_btc_local",
212
+ "wdk-btc-local": "wdk_btc_local",
213
+ }
214
+ backend = aliases.get(normalized, normalized)
215
+ if backend not in BACKENDS:
216
+ raise RuntimeError(
217
+ "Wallet backend must be one of solana_local, wdk_btc_local, or wdk_evm_local."
218
+ )
219
+ return backend
220
+
221
+
222
+ def _infer_backend_for_tool(tool_name: str) -> str | None:
223
+ if (
224
+ tool_name.startswith("get_evm_")
225
+ or tool_name.startswith("manage_evm_")
226
+ or tool_name.startswith("swap_evm_")
227
+ or tool_name.startswith("transfer_evm_")
228
+ or tool_name == "agent_wallet_evm_status"
229
+ or tool_name == "agent_wallet_evm_setup"
230
+ ):
231
+ return "wdk_evm_local"
232
+ if tool_name.startswith("get_btc_") or tool_name == "transfer_btc":
233
+ return "wdk_btc_local"
234
+ if (
235
+ "solana" in tool_name
236
+ or "jupiter" in tool_name
237
+ or "kamino" in tool_name
238
+ or "bags" in tool_name
239
+ or tool_name in {"transfer_sol", "transfer_spl_token", "sign_wallet_message", "close_empty_token_accounts", "request_devnet_airdrop", "get_wallet_portfolio", "get_solana_token_prices"}
240
+ ):
241
+ return "solana_local"
242
+ return None
243
+
244
+
245
+ def _normalize_network_for_backend(backend: str, raw_network: Any) -> str:
246
+ network = str(raw_network or "").strip().lower()
247
+ if backend == "wdk_evm_local":
248
+ aliases = {
249
+ "mainnet": "ethereum",
250
+ "eth": "ethereum",
251
+ "eth-mainnet": "ethereum",
252
+ "base-mainnet": "base",
253
+ }
254
+ normalized = aliases.get(network, network)
255
+ return normalized if normalized in {"ethereum", "base"} else "ethereum"
256
+ if backend == "wdk_btc_local":
257
+ aliases = {
258
+ "btc": "bitcoin",
259
+ "bitcoin_mainnet": "bitcoin",
260
+ "bitcoin-mainnet": "bitcoin",
261
+ "mainnet": "bitcoin",
262
+ }
263
+ normalized = aliases.get(network, network)
264
+ return normalized if normalized in {"bitcoin", "testnet", "regtest"} else "bitcoin"
265
+ aliases = {
266
+ "solana": "mainnet",
267
+ "solana-mainnet": "mainnet",
268
+ "mainnet_beta": "mainnet",
269
+ "mainnet-beta": "mainnet",
270
+ }
271
+ normalized = aliases.get(network, network)
272
+ return normalized if normalized in {"mainnet", "devnet", "testnet"} else "mainnet"
273
+
274
+
195
275
  def _reject_secret_config(config: dict[str, Any]) -> None:
196
276
  present = sorted(key for key in SECRET_CONFIG_KEYS if str(config.get(key) or "").strip())
197
277
  if present:
@@ -202,16 +282,20 @@ def _reject_secret_config(config: dict[str, Any]) -> None:
202
282
  )
203
283
 
204
284
 
205
- def _base_config(args: dict[str, Any]) -> dict[str, Any]:
285
+ def _base_config(args: dict[str, Any], *, tool_name: str | None = None) -> dict[str, Any]:
206
286
  raw = args.get("config") or {}
207
287
  if not isinstance(raw, dict):
208
288
  raise RuntimeError("config must be a JSON object when provided.")
209
289
  config = dict(raw)
210
290
  backend = args.get("backend") or os.getenv("AGENT_WALLET_BACKEND")
211
- network = args.get("network") or os.getenv("AGENT_WALLET_NETWORK")
291
+ if not backend and tool_name:
292
+ backend = _infer_backend_for_tool(tool_name)
212
293
  if backend:
213
- config["backend"] = str(backend).strip()
214
- if network:
294
+ config["backend"] = _normalize_backend(backend)
295
+ network = args.get("network") or os.getenv("AGENT_WALLET_NETWORK") or config.get("network")
296
+ if backend:
297
+ config["network"] = _normalize_network_for_backend(config["backend"], network)
298
+ elif network:
215
299
  config["network"] = str(network).strip()
216
300
  _reject_secret_config(config)
217
301
  return config
@@ -235,15 +319,15 @@ def _cli_env(package_root: Path) -> dict[str, str]:
235
319
 
236
320
  def _call_wallet_cli(args: dict[str, Any]) -> dict[str, Any]:
237
321
  package_root = _resolve_package_root()
238
- config = _base_config(args)
239
322
  tool_name = str(args.get("tool_name") or "").strip()
240
323
  if not tool_name:
241
324
  raise RuntimeError("tool_name is required.")
325
+ config = _base_config(args, tool_name=tool_name)
242
326
 
243
327
  tool_args = args.get("arguments") or {}
244
328
  if not isinstance(tool_args, dict):
245
329
  raise RuntimeError("arguments must be a JSON object when provided.")
246
- if tool_name == "swap_solana_tokens" and str(tool_args.get("mode") or "") == "execute":
330
+ if tool_name in PREVIEW_BOUND_SWAP_TOOLS and str(tool_args.get("mode") or "") == "execute":
247
331
  approval_token = str(tool_args.get("approval_token") or "").strip()
248
332
  cached_preview = _lookup_preview_for_token(approval_token)
249
333
  if cached_preview is not None and "_approved_preview" not in tool_args:
@@ -321,10 +405,10 @@ def _call_issue_approval(args: dict[str, Any]) -> dict[str, Any]:
321
405
  "user_confirmed=true is required after explicit user approval of the exact confirmation_summary."
322
406
  )
323
407
  package_root = _resolve_package_root()
324
- config = _base_config(args)
325
408
  tool_name = str(args.get("tool_name") or "").strip()
326
409
  if not tool_name:
327
410
  raise RuntimeError("tool_name is required.")
411
+ config = _base_config(args, tool_name=tool_name)
328
412
 
329
413
  summary = args.get("confirmation_summary")
330
414
  if not isinstance(summary, dict) or not summary:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentlayer.tech/wallet",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "NPM installer for the OpenClaw Agent Wallet local runtime.",
5
5
  "type": "module",
6
6
  "repository": {
@@ -38,6 +38,7 @@
38
38
  "agent-wallet/pyproject.toml",
39
39
  ".openclaw/AGENTS.md",
40
40
  ".openclaw/extensions/agent-wallet/",
41
+ ".openclaw/extensions/pay-bridge/",
41
42
  "hermes/plugins/agent_wallet/",
42
43
  "wdk-btc-wallet/src/",
43
44
  "wdk-btc-wallet/bootstrap.sh",