@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,343 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Host-side helper for managing a local OpenClaw EVM wallet binding."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from getpass import getpass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from urllib.error import URLError
|
|
12
|
+
from urllib.request import urlopen
|
|
13
|
+
|
|
14
|
+
PACKAGE_ROOT = Path(__file__).resolve().parents[1]
|
|
15
|
+
if str(PACKAGE_ROOT) not in sys.path:
|
|
16
|
+
sys.path.insert(0, str(PACKAGE_ROOT))
|
|
17
|
+
|
|
18
|
+
from agent_wallet.config import settings # noqa: E402
|
|
19
|
+
from agent_wallet.evm_user_wallets import ( # noqa: E402
|
|
20
|
+
bind_user_evm_wallet,
|
|
21
|
+
create_user_evm_wallet,
|
|
22
|
+
get_user_evm_wallet_binding,
|
|
23
|
+
import_user_evm_wallet,
|
|
24
|
+
list_user_evm_wallet_bindings,
|
|
25
|
+
lock_user_evm_wallet,
|
|
26
|
+
unlock_user_evm_wallet,
|
|
27
|
+
)
|
|
28
|
+
from agent_wallet.providers.wdk_evm_local import WdkEvmLocalClient # noqa: E402
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _normalize_network(value: str) -> str:
|
|
32
|
+
network = str(value or "").strip().lower()
|
|
33
|
+
aliases = {
|
|
34
|
+
"mainnet": "ethereum",
|
|
35
|
+
"eth": "ethereum",
|
|
36
|
+
"eth-mainnet": "ethereum",
|
|
37
|
+
"base-mainnet": "base",
|
|
38
|
+
"base_sepolia": "base-sepolia",
|
|
39
|
+
}
|
|
40
|
+
network = aliases.get(network, network)
|
|
41
|
+
if network not in {"ethereum", "sepolia", "base", "base-sepolia"}:
|
|
42
|
+
return "ethereum"
|
|
43
|
+
return network
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _paired_network(network: str) -> str | None:
|
|
47
|
+
mapping = {
|
|
48
|
+
"ethereum": "base",
|
|
49
|
+
"base": "ethereum",
|
|
50
|
+
"sepolia": "base-sepolia",
|
|
51
|
+
"base-sepolia": "sepolia",
|
|
52
|
+
}
|
|
53
|
+
return mapping.get(_normalize_network(network))
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _read_secret(
|
|
57
|
+
*,
|
|
58
|
+
prompt: str,
|
|
59
|
+
confirm_prompt: str | None = None,
|
|
60
|
+
stdin_mode: bool = False,
|
|
61
|
+
) -> str:
|
|
62
|
+
if stdin_mode:
|
|
63
|
+
value = sys.stdin.read().strip()
|
|
64
|
+
if not value:
|
|
65
|
+
raise SystemExit(f"{prompt.rstrip(':')} is required on stdin.")
|
|
66
|
+
return value
|
|
67
|
+
value = getpass(prompt)
|
|
68
|
+
if confirm_prompt is not None:
|
|
69
|
+
confirmed = getpass(confirm_prompt)
|
|
70
|
+
if value != confirmed:
|
|
71
|
+
raise SystemExit("Secrets did not match.")
|
|
72
|
+
if not value.strip():
|
|
73
|
+
raise SystemExit(f"{prompt.rstrip(':')} is required.")
|
|
74
|
+
return value.strip()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _read_password_and_seed_from_stdin() -> tuple[str, str]:
|
|
78
|
+
raw = sys.stdin.read().strip()
|
|
79
|
+
if not raw:
|
|
80
|
+
raise SystemExit("Password and seed phrase payload is required on stdin.")
|
|
81
|
+
lines = raw.splitlines()
|
|
82
|
+
if len(lines) < 2:
|
|
83
|
+
raise SystemExit(
|
|
84
|
+
"For import via stdin, provide password on the first line and the seed phrase on the remaining lines."
|
|
85
|
+
)
|
|
86
|
+
password = lines[0].strip()
|
|
87
|
+
seed_phrase = " ".join(line.strip() for line in lines[1:] if line.strip())
|
|
88
|
+
if not password or not seed_phrase:
|
|
89
|
+
raise SystemExit("Both password and seed phrase are required.")
|
|
90
|
+
return password, seed_phrase
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _service_health(service_url: str | None) -> dict[str, object]:
|
|
94
|
+
target = str(service_url or "").strip()
|
|
95
|
+
if not target:
|
|
96
|
+
return {"service_url": None, "healthy": False, "error": "service_url is not configured"}
|
|
97
|
+
health_url = f"{target.rstrip('/')}/health"
|
|
98
|
+
try:
|
|
99
|
+
with urlopen(health_url, timeout=1.5) as response:
|
|
100
|
+
payload = json.loads(response.read().decode("utf-8"))
|
|
101
|
+
return {
|
|
102
|
+
"service_url": target,
|
|
103
|
+
"healthy": int(getattr(response, "status", 0) or 0) == 200,
|
|
104
|
+
"health": payload,
|
|
105
|
+
}
|
|
106
|
+
except (URLError, TimeoutError, OSError, ValueError) as exc:
|
|
107
|
+
return {"service_url": target, "healthy": False, "error": str(exc)}
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _status_payload(user_id: str | None, network: str | None, service_url: str | None) -> dict[str, object]:
|
|
111
|
+
target_service_url = str(service_url or settings.wdk_evm_service_url).strip() or None
|
|
112
|
+
payload: dict[str, object] = {
|
|
113
|
+
"ok": True,
|
|
114
|
+
"network": _normalize_network(network or "ethereum"),
|
|
115
|
+
"service": _service_health(target_service_url),
|
|
116
|
+
}
|
|
117
|
+
target_network = _normalize_network(network or "ethereum")
|
|
118
|
+
if target_service_url:
|
|
119
|
+
try:
|
|
120
|
+
payload["network_info"] = WdkEvmLocalClient(target_service_url).get_sync("/v1/evm/network")
|
|
121
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
122
|
+
payload["network_info_error"] = str(exc)
|
|
123
|
+
if user_id:
|
|
124
|
+
payload["bindings"] = list_user_evm_wallet_bindings(user_id)
|
|
125
|
+
try:
|
|
126
|
+
payload["binding"] = get_user_evm_wallet_binding(user_id, network=target_network)
|
|
127
|
+
except Exception as exc:
|
|
128
|
+
payload["binding_error"] = str(exc)
|
|
129
|
+
return payload
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main() -> int:
|
|
133
|
+
parser = argparse.ArgumentParser(description="Manage a local OpenClaw EVM wallet binding")
|
|
134
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
135
|
+
|
|
136
|
+
common_parent = argparse.ArgumentParser(add_help=False)
|
|
137
|
+
common_parent.add_argument("--network", default="ethereum")
|
|
138
|
+
common_parent.add_argument("--service-url")
|
|
139
|
+
|
|
140
|
+
get_parser = subparsers.add_parser("get", parents=[common_parent])
|
|
141
|
+
get_parser.add_argument("--user-id", required=True)
|
|
142
|
+
|
|
143
|
+
list_parser = subparsers.add_parser("list", parents=[common_parent])
|
|
144
|
+
list_parser.add_argument("--user-id", required=True)
|
|
145
|
+
|
|
146
|
+
status_parser = subparsers.add_parser("status", parents=[common_parent])
|
|
147
|
+
status_parser.add_argument("--user-id", default="")
|
|
148
|
+
|
|
149
|
+
setup_parser = subparsers.add_parser("setup", parents=[common_parent])
|
|
150
|
+
setup_parser.add_argument("--user-id", required=True)
|
|
151
|
+
setup_parser.add_argument("--label")
|
|
152
|
+
setup_parser.add_argument("--account-index", type=int)
|
|
153
|
+
setup_parser.add_argument("--password-stdin", action="store_true")
|
|
154
|
+
setup_parser.add_argument("--bind-network-pair", action=argparse.BooleanOptionalAction, default=True)
|
|
155
|
+
|
|
156
|
+
create_parser = subparsers.add_parser("create", parents=[common_parent])
|
|
157
|
+
create_parser.add_argument("--user-id", required=True)
|
|
158
|
+
create_parser.add_argument("--label")
|
|
159
|
+
create_parser.add_argument("--account-index", type=int)
|
|
160
|
+
create_parser.add_argument("--reveal-seed", action="store_true")
|
|
161
|
+
create_parser.add_argument("--password-stdin", action="store_true")
|
|
162
|
+
create_parser.add_argument("--bind-network-pair", action=argparse.BooleanOptionalAction, default=True)
|
|
163
|
+
|
|
164
|
+
import_parser = subparsers.add_parser("import", parents=[common_parent])
|
|
165
|
+
import_parser.add_argument("--user-id", required=True)
|
|
166
|
+
import_parser.add_argument("--label")
|
|
167
|
+
import_parser.add_argument("--account-index", type=int)
|
|
168
|
+
import_parser.add_argument("--password-stdin", action="store_true")
|
|
169
|
+
import_parser.add_argument("--seed-stdin", action="store_true")
|
|
170
|
+
import_parser.add_argument("--bind-network-pair", action=argparse.BooleanOptionalAction, default=True)
|
|
171
|
+
|
|
172
|
+
unlock_parser = subparsers.add_parser("unlock", parents=[common_parent])
|
|
173
|
+
unlock_parser.add_argument("--user-id", required=True)
|
|
174
|
+
unlock_parser.add_argument("--password-stdin", action="store_true")
|
|
175
|
+
unlock_parser.add_argument("--wallet-id", default="")
|
|
176
|
+
unlock_parser.add_argument("--account-index", type=int)
|
|
177
|
+
unlock_parser.add_argument("--bind-network-pair", action=argparse.BooleanOptionalAction, default=True)
|
|
178
|
+
|
|
179
|
+
lock_parser = subparsers.add_parser("lock", parents=[common_parent])
|
|
180
|
+
lock_parser.add_argument("--user-id", required=True)
|
|
181
|
+
lock_parser.add_argument("--wallet-id", default="")
|
|
182
|
+
lock_parser.add_argument("--account-index", type=int)
|
|
183
|
+
|
|
184
|
+
args = parser.parse_args()
|
|
185
|
+
effective_network = _normalize_network(args.network)
|
|
186
|
+
|
|
187
|
+
def _config_hint(wallet: dict[str, object]) -> dict[str, object]:
|
|
188
|
+
return {
|
|
189
|
+
"backend": "wdk_evm_local",
|
|
190
|
+
"network": effective_network,
|
|
191
|
+
"wdkEvmServiceUrl": args.service_url,
|
|
192
|
+
"wdkEvmWalletId": wallet.get("wallet_id"),
|
|
193
|
+
"wdkEvmAccountIndex": wallet.get("account_index"),
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
def _bind_pair(wallet: dict[str, object]) -> dict[str, object] | None:
|
|
197
|
+
if not getattr(args, "bind_network_pair", False):
|
|
198
|
+
return None
|
|
199
|
+
paired = _paired_network(effective_network)
|
|
200
|
+
if not paired:
|
|
201
|
+
return None
|
|
202
|
+
return bind_user_evm_wallet(
|
|
203
|
+
args.user_id,
|
|
204
|
+
wallet_id=str(wallet.get("wallet_id") or ""),
|
|
205
|
+
network=paired,
|
|
206
|
+
service_url=args.service_url,
|
|
207
|
+
account_index=wallet.get("account_index"),
|
|
208
|
+
tolerate_locked=True,
|
|
209
|
+
fallback_address=str(wallet.get("address") or "").strip() or None,
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if args.command == "status":
|
|
213
|
+
payload = _status_payload(args.user_id or None, effective_network, args.service_url)
|
|
214
|
+
elif args.command == "list":
|
|
215
|
+
payload = {"ok": True, "wallets": list_user_evm_wallet_bindings(args.user_id)}
|
|
216
|
+
elif args.command == "get":
|
|
217
|
+
wallet = get_user_evm_wallet_binding(args.user_id, network=effective_network)
|
|
218
|
+
payload = {"ok": True, "wallet": wallet, "openclaw_config_hint": _config_hint(wallet)}
|
|
219
|
+
elif args.command == "setup":
|
|
220
|
+
password = _read_secret(
|
|
221
|
+
prompt="EVM wallet password: ",
|
|
222
|
+
confirm_prompt=None,
|
|
223
|
+
stdin_mode=bool(args.password_stdin),
|
|
224
|
+
)
|
|
225
|
+
try:
|
|
226
|
+
existing = get_user_evm_wallet_binding(args.user_id, network=effective_network)
|
|
227
|
+
except Exception:
|
|
228
|
+
existing = None
|
|
229
|
+
|
|
230
|
+
if existing is None:
|
|
231
|
+
wallet = create_user_evm_wallet(
|
|
232
|
+
args.user_id,
|
|
233
|
+
password=password,
|
|
234
|
+
label=args.label,
|
|
235
|
+
network=effective_network,
|
|
236
|
+
service_url=args.service_url,
|
|
237
|
+
account_index=args.account_index,
|
|
238
|
+
)
|
|
239
|
+
paired_binding = _bind_pair(wallet)
|
|
240
|
+
payload = {
|
|
241
|
+
"ok": True,
|
|
242
|
+
"action": "created",
|
|
243
|
+
"wallet": wallet,
|
|
244
|
+
"paired_binding": paired_binding,
|
|
245
|
+
"openclaw_config_hint": _config_hint(wallet),
|
|
246
|
+
}
|
|
247
|
+
else:
|
|
248
|
+
wallet = unlock_user_evm_wallet(
|
|
249
|
+
args.user_id,
|
|
250
|
+
password=password,
|
|
251
|
+
network=effective_network,
|
|
252
|
+
service_url=args.service_url,
|
|
253
|
+
account_index=args.account_index,
|
|
254
|
+
)
|
|
255
|
+
paired_binding = _bind_pair(wallet)
|
|
256
|
+
payload = {
|
|
257
|
+
"ok": True,
|
|
258
|
+
"action": "unlocked",
|
|
259
|
+
"wallet": wallet,
|
|
260
|
+
"paired_binding": paired_binding,
|
|
261
|
+
"openclaw_config_hint": _config_hint(wallet),
|
|
262
|
+
}
|
|
263
|
+
elif args.command == "create":
|
|
264
|
+
wallet = create_user_evm_wallet(
|
|
265
|
+
args.user_id,
|
|
266
|
+
password=_read_secret(
|
|
267
|
+
prompt="EVM wallet password: ",
|
|
268
|
+
confirm_prompt="Confirm EVM wallet password: ",
|
|
269
|
+
stdin_mode=bool(args.password_stdin),
|
|
270
|
+
),
|
|
271
|
+
label=args.label,
|
|
272
|
+
network=effective_network,
|
|
273
|
+
service_url=args.service_url,
|
|
274
|
+
reveal_seed_phrase=bool(args.reveal_seed),
|
|
275
|
+
account_index=args.account_index,
|
|
276
|
+
)
|
|
277
|
+
payload = {
|
|
278
|
+
"ok": True,
|
|
279
|
+
"wallet": wallet,
|
|
280
|
+
"paired_binding": _bind_pair(wallet),
|
|
281
|
+
}
|
|
282
|
+
elif args.command == "import":
|
|
283
|
+
if args.password_stdin and args.seed_stdin:
|
|
284
|
+
password, seed_phrase = _read_password_and_seed_from_stdin()
|
|
285
|
+
else:
|
|
286
|
+
password = _read_secret(
|
|
287
|
+
prompt="EVM wallet password: ",
|
|
288
|
+
confirm_prompt="Confirm EVM wallet password: ",
|
|
289
|
+
stdin_mode=bool(args.password_stdin),
|
|
290
|
+
)
|
|
291
|
+
seed_phrase = _read_secret(
|
|
292
|
+
prompt="EVM seed phrase: ",
|
|
293
|
+
stdin_mode=bool(args.seed_stdin),
|
|
294
|
+
)
|
|
295
|
+
wallet = import_user_evm_wallet(
|
|
296
|
+
args.user_id,
|
|
297
|
+
password=password,
|
|
298
|
+
seed_phrase=seed_phrase,
|
|
299
|
+
label=args.label,
|
|
300
|
+
network=effective_network,
|
|
301
|
+
service_url=args.service_url,
|
|
302
|
+
account_index=args.account_index,
|
|
303
|
+
)
|
|
304
|
+
payload = {
|
|
305
|
+
"ok": True,
|
|
306
|
+
"wallet": wallet,
|
|
307
|
+
"paired_binding": _bind_pair(wallet),
|
|
308
|
+
}
|
|
309
|
+
elif args.command == "unlock":
|
|
310
|
+
wallet = unlock_user_evm_wallet(
|
|
311
|
+
args.user_id,
|
|
312
|
+
password=_read_secret(
|
|
313
|
+
prompt="EVM wallet password: ",
|
|
314
|
+
stdin_mode=bool(args.password_stdin),
|
|
315
|
+
),
|
|
316
|
+
network=effective_network,
|
|
317
|
+
service_url=args.service_url,
|
|
318
|
+
wallet_id=args.wallet_id or None,
|
|
319
|
+
account_index=args.account_index,
|
|
320
|
+
)
|
|
321
|
+
payload = {
|
|
322
|
+
"ok": True,
|
|
323
|
+
"wallet": wallet,
|
|
324
|
+
"paired_binding": _bind_pair(wallet),
|
|
325
|
+
}
|
|
326
|
+
else:
|
|
327
|
+
payload = {
|
|
328
|
+
"ok": True,
|
|
329
|
+
"wallet": lock_user_evm_wallet(
|
|
330
|
+
args.user_id,
|
|
331
|
+
network=effective_network,
|
|
332
|
+
service_url=args.service_url,
|
|
333
|
+
wallet_id=args.wallet_id or None,
|
|
334
|
+
account_index=args.account_index,
|
|
335
|
+
),
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
print(json.dumps(payload))
|
|
339
|
+
return 0
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
if __name__ == "__main__":
|
|
343
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
|
|
5
|
+
PACKAGE_ROOT=$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)
|
|
6
|
+
|
|
7
|
+
resolve_python_bin() {
|
|
8
|
+
if [ -n "${OPENCLAW_AGENT_WALLET_PYTHON:-}" ] && [ -x "${OPENCLAW_AGENT_WALLET_PYTHON}" ]; then
|
|
9
|
+
printf "%s" "${OPENCLAW_AGENT_WALLET_PYTHON}"
|
|
10
|
+
return 0
|
|
11
|
+
fi
|
|
12
|
+
|
|
13
|
+
if [ -x "/tmp/agent-wallet-venv/bin/python" ]; then
|
|
14
|
+
printf "%s" "/tmp/agent-wallet-venv/bin/python"
|
|
15
|
+
return 0
|
|
16
|
+
fi
|
|
17
|
+
|
|
18
|
+
if [ -x "$PACKAGE_ROOT/.venv/bin/python" ]; then
|
|
19
|
+
printf "%s" "$PACKAGE_ROOT/.venv/bin/python"
|
|
20
|
+
return 0
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
24
|
+
command -v python3
|
|
25
|
+
return 0
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
command -v python
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
PYTHON_BIN=$(resolve_python_bin)
|
|
32
|
+
export OPENCLAW_AGENT_WALLET_PYTHON="$PYTHON_BIN"
|
|
33
|
+
|
|
34
|
+
has_flag() {
|
|
35
|
+
flag=$1
|
|
36
|
+
shift
|
|
37
|
+
for arg in "$@"; do
|
|
38
|
+
case "$arg" in
|
|
39
|
+
"$flag"|"$flag"=*)
|
|
40
|
+
return 0
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
done
|
|
44
|
+
return 1
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
prompt_with_default() {
|
|
48
|
+
label=$1
|
|
49
|
+
default_value=$2
|
|
50
|
+
if [ -t 0 ]; then
|
|
51
|
+
printf "%s [%s]: " "$label" "$default_value" >&2
|
|
52
|
+
read -r value
|
|
53
|
+
if [ -z "${value:-}" ]; then
|
|
54
|
+
printf "%s" "$default_value"
|
|
55
|
+
else
|
|
56
|
+
printf "%s" "$value"
|
|
57
|
+
fi
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
printf "%s" "$default_value"
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
normalize_network_value() {
|
|
64
|
+
case $(printf "%s" "$1" | tr '[:upper:]' '[:lower:]') in
|
|
65
|
+
1|ethereum|eth|mainnet)
|
|
66
|
+
printf "ethereum"
|
|
67
|
+
;;
|
|
68
|
+
2|base)
|
|
69
|
+
printf "base"
|
|
70
|
+
;;
|
|
71
|
+
3|sepolia)
|
|
72
|
+
printf "sepolia"
|
|
73
|
+
;;
|
|
74
|
+
4|base-sepolia|base_sepolia)
|
|
75
|
+
printf "base-sepolia"
|
|
76
|
+
;;
|
|
77
|
+
*)
|
|
78
|
+
return 1
|
|
79
|
+
;;
|
|
80
|
+
esac
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
prompt_network_choice() {
|
|
84
|
+
default_value=$1
|
|
85
|
+
if ! [ -t 0 ]; then
|
|
86
|
+
printf "%s" "$default_value"
|
|
87
|
+
return 0
|
|
88
|
+
fi
|
|
89
|
+
|
|
90
|
+
case "$default_value" in
|
|
91
|
+
ethereum) default_hint="1" ;;
|
|
92
|
+
base) default_hint="2" ;;
|
|
93
|
+
sepolia) default_hint="3" ;;
|
|
94
|
+
base-sepolia) default_hint="4" ;;
|
|
95
|
+
*) default_hint="2" ;;
|
|
96
|
+
esac
|
|
97
|
+
|
|
98
|
+
while true; do
|
|
99
|
+
printf "EVM network:\n" >&2
|
|
100
|
+
printf " 1) ethereum\n" >&2
|
|
101
|
+
printf " 2) base\n" >&2
|
|
102
|
+
printf " 3) sepolia\n" >&2
|
|
103
|
+
printf " 4) base-sepolia\n" >&2
|
|
104
|
+
printf "Choose network [%s]: " "$default_hint" >&2
|
|
105
|
+
read -r choice
|
|
106
|
+
if [ -z "${choice:-}" ]; then
|
|
107
|
+
choice=$default_hint
|
|
108
|
+
fi
|
|
109
|
+
if network=$(normalize_network_value "$choice"); then
|
|
110
|
+
printf "%s" "$network"
|
|
111
|
+
return 0
|
|
112
|
+
fi
|
|
113
|
+
printf "Invalid choice. Enter 1, 2, 3, 4, ethereum, base, sepolia, or base-sepolia.\n" >&2
|
|
114
|
+
done
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
DEFAULT_USER_ID=${OPENCLAW_EVM_USER_ID:-${USER:-openclaw-user}-local}
|
|
118
|
+
DEFAULT_NETWORK=${OPENCLAW_EVM_NETWORK:-base}
|
|
119
|
+
DEFAULT_SERVICE_URL=${OPENCLAW_EVM_SERVICE_URL:-http://127.0.0.1:8081}
|
|
120
|
+
|
|
121
|
+
if ! has_flag --user-id "$@"; then
|
|
122
|
+
USER_ID=$(prompt_with_default "OpenClaw user id" "$DEFAULT_USER_ID")
|
|
123
|
+
set -- "$@" --user-id "$USER_ID"
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
if ! has_flag --network "$@"; then
|
|
127
|
+
NETWORK=$(prompt_network_choice "$DEFAULT_NETWORK")
|
|
128
|
+
set -- "$@" --network "$NETWORK"
|
|
129
|
+
fi
|
|
130
|
+
|
|
131
|
+
if ! has_flag --service-url "$@"; then
|
|
132
|
+
set -- "$@" --service-url "$DEFAULT_SERVICE_URL"
|
|
133
|
+
fi
|
|
134
|
+
|
|
135
|
+
if ! has_flag --config-path "$@" && [ -n "${OPENCLAW_EVM_CONFIG_PATH:-}" ]; then
|
|
136
|
+
set -- "$@" --config-path "$OPENCLAW_EVM_CONFIG_PATH"
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
if ! has_flag --wdk-wallet-root "$@" && [ -n "${OPENCLAW_EVM_WDK_WALLET_ROOT:-}" ]; then
|
|
140
|
+
set -- "$@" --wdk-wallet-root "$OPENCLAW_EVM_WDK_WALLET_ROOT"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
if ! has_flag --python-bin "$@"; then
|
|
144
|
+
set -- "$@" --python-bin "$PYTHON_BIN"
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
if ! has_flag --package-root "$@"; then
|
|
148
|
+
set -- "$@" --package-root "$PACKAGE_ROOT"
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
exec "$PYTHON_BIN" "$SCRIPT_DIR/bootstrap_openclaw_evm.py" "$@"
|
|
@@ -1,7 +1,19 @@
|
|
|
1
1
|
"""Hermes Agent plugin bridge for AgentLayer wallet tools."""
|
|
2
2
|
|
|
3
|
-
from .schemas import
|
|
4
|
-
|
|
3
|
+
from .schemas import (
|
|
4
|
+
AGENT_WALLET_APPROVE,
|
|
5
|
+
AGENT_WALLET_EVM_SETUP,
|
|
6
|
+
AGENT_WALLET_EVM_STATUS,
|
|
7
|
+
AGENT_WALLET_INVOKE,
|
|
8
|
+
AGENT_WALLET_TOOLS,
|
|
9
|
+
)
|
|
10
|
+
from .tools import (
|
|
11
|
+
agent_wallet_approve,
|
|
12
|
+
agent_wallet_evm_setup,
|
|
13
|
+
agent_wallet_evm_status,
|
|
14
|
+
agent_wallet_invoke,
|
|
15
|
+
agent_wallet_tools,
|
|
16
|
+
)
|
|
5
17
|
|
|
6
18
|
|
|
7
19
|
def register(ctx):
|
|
@@ -27,3 +39,17 @@ def register(ctx):
|
|
|
27
39
|
handler=agent_wallet_approve,
|
|
28
40
|
description=AGENT_WALLET_APPROVE["description"],
|
|
29
41
|
)
|
|
42
|
+
ctx.register_tool(
|
|
43
|
+
name=AGENT_WALLET_EVM_STATUS["name"],
|
|
44
|
+
toolset="agent_wallet",
|
|
45
|
+
schema=AGENT_WALLET_EVM_STATUS,
|
|
46
|
+
handler=agent_wallet_evm_status,
|
|
47
|
+
description=AGENT_WALLET_EVM_STATUS["description"],
|
|
48
|
+
)
|
|
49
|
+
ctx.register_tool(
|
|
50
|
+
name=AGENT_WALLET_EVM_SETUP["name"],
|
|
51
|
+
toolset="agent_wallet",
|
|
52
|
+
schema=AGENT_WALLET_EVM_SETUP,
|
|
53
|
+
handler=agent_wallet_evm_setup,
|
|
54
|
+
description=AGENT_WALLET_EVM_SETUP["description"],
|
|
55
|
+
)
|
|
@@ -132,3 +132,75 @@ AGENT_WALLET_APPROVE = {
|
|
|
132
132
|
"additionalProperties": False,
|
|
133
133
|
},
|
|
134
134
|
}
|
|
135
|
+
|
|
136
|
+
AGENT_WALLET_EVM_STATUS = {
|
|
137
|
+
"name": "agent_wallet_evm_status",
|
|
138
|
+
"description": (
|
|
139
|
+
"Inspect the local EVM wallet runtime used by AgentLayer/OpenClaw. "
|
|
140
|
+
"Returns wdk-evm-wallet health, network info, and existing user wallet "
|
|
141
|
+
"bindings without changing wallet state."
|
|
142
|
+
),
|
|
143
|
+
"parameters": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"properties": {
|
|
146
|
+
"user_id": {
|
|
147
|
+
"type": "string",
|
|
148
|
+
"description": "Optional local wallet owner id to inspect.",
|
|
149
|
+
},
|
|
150
|
+
"network": {
|
|
151
|
+
"type": "string",
|
|
152
|
+
"description": "Optional EVM network hint, such as ethereum, base, sepolia, or base-sepolia.",
|
|
153
|
+
},
|
|
154
|
+
"service_url": {
|
|
155
|
+
"type": "string",
|
|
156
|
+
"description": "Optional localhost override for the wdk-evm-wallet service.",
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
"additionalProperties": False,
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
AGENT_WALLET_EVM_SETUP = {
|
|
164
|
+
"name": "agent_wallet_evm_setup",
|
|
165
|
+
"description": (
|
|
166
|
+
"Create or unlock the local EVM wallet binding used by AgentLayer/OpenClaw "
|
|
167
|
+
"for Hermes. This can auto-start the localhost-only wdk-evm-wallet service, "
|
|
168
|
+
"set up the selected network, and bind the same wallet to the paired EVM network "
|
|
169
|
+
"such as ethereum/base."
|
|
170
|
+
),
|
|
171
|
+
"parameters": {
|
|
172
|
+
"type": "object",
|
|
173
|
+
"properties": {
|
|
174
|
+
"password": {
|
|
175
|
+
"type": "string",
|
|
176
|
+
"description": "Local EVM wallet password used to create or unlock the vault wallet.",
|
|
177
|
+
},
|
|
178
|
+
"user_id": {
|
|
179
|
+
"type": "string",
|
|
180
|
+
"description": "Optional local wallet owner id. Defaults to AGENT_WALLET_USER_ID, USER, or hermes-local-user.",
|
|
181
|
+
},
|
|
182
|
+
"network": {
|
|
183
|
+
"type": "string",
|
|
184
|
+
"description": "Selected EVM network, typically ethereum or base.",
|
|
185
|
+
},
|
|
186
|
+
"label": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"description": "Optional wallet label used when creating a new local EVM wallet.",
|
|
189
|
+
},
|
|
190
|
+
"service_url": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"description": "Optional localhost override for the wdk-evm-wallet service.",
|
|
193
|
+
},
|
|
194
|
+
"auto_start_service": {
|
|
195
|
+
"type": "boolean",
|
|
196
|
+
"description": "Whether to auto-start the local wdk-evm-wallet service when it is not healthy. Defaults to true.",
|
|
197
|
+
},
|
|
198
|
+
"bind_network_pair": {
|
|
199
|
+
"type": "boolean",
|
|
200
|
+
"description": "Whether to also bind the paired EVM network such as ethereum/base. Defaults to true.",
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
"required": ["password"],
|
|
204
|
+
"additionalProperties": False,
|
|
205
|
+
},
|
|
206
|
+
}
|