@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.
- package/.openclaw/extensions/agent-wallet/index.ts +454 -18
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +96 -0
- package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +2 -0
- package/CHANGELOG.md +25 -0
- package/README.md +43 -51
- package/agent-wallet/.env.example +11 -0
- package/agent-wallet/README.md +53 -0
- package/agent-wallet/agent_wallet/approval.py +4 -0
- package/agent-wallet/agent_wallet/config.py +6 -0
- package/agent-wallet/agent_wallet/exceptions.py +2 -1
- package/agent-wallet/agent_wallet/openclaw_adapter.py +361 -2
- package/agent-wallet/agent_wallet/openclaw_cli.py +13 -1
- package/agent-wallet/agent_wallet/openclaw_runtime.py +2 -5
- package/agent-wallet/agent_wallet/providers/houdini.py +539 -0
- package/agent-wallet/agent_wallet/transaction_policy.py +251 -0
- package/agent-wallet/agent_wallet/user_wallets.py +83 -0
- package/agent-wallet/agent_wallet/wallet_layer/base.py +40 -0
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +885 -16
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/bootstrap_openclaw_evm.py +291 -0
- package/agent-wallet/scripts/install_agent_wallet.py +54 -2
- package/agent-wallet/scripts/install_openclaw_local_config.py +84 -4
- package/agent-wallet/scripts/manage_openclaw_evm_wallet.py +343 -0
- package/agent-wallet/scripts/setup_evm_wallet.sh +151 -0
- package/hermes/plugins/agent_wallet/__init__.py +28 -2
- package/hermes/plugins/agent_wallet/plugin.yaml +2 -0
- package/hermes/plugins/agent_wallet/schemas.py +72 -0
- package/hermes/plugins/agent_wallet/tools.py +193 -9
- package/package.json +2 -2
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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":
|
|
297
|
+
"user_id": resolved_user_id,
|
|
218
298
|
"sealed_keys_path": sealed_keys_path,
|
|
219
299
|
},
|
|
220
300
|
indent=2,
|