@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
|
@@ -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
|
|
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")
|
|
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):
|
|
@@ -174,6 +175,13 @@ def _python_bin(package_root: Path) -> str:
|
|
|
174
175
|
return "python3"
|
|
175
176
|
|
|
176
177
|
|
|
178
|
+
def _script_path(package_root: Path, name: str) -> Path:
|
|
179
|
+
script = package_root / "scripts" / name
|
|
180
|
+
if not script.exists():
|
|
181
|
+
raise RuntimeError(f"Required host script is missing: {script}")
|
|
182
|
+
return script
|
|
183
|
+
|
|
184
|
+
|
|
177
185
|
def _user_id(args: dict[str, Any]) -> str:
|
|
178
186
|
value = (
|
|
179
187
|
args.get("user_id")
|
|
@@ -185,6 +193,85 @@ def _user_id(args: dict[str, Any]) -> str:
|
|
|
185
193
|
return str(value).strip() or "hermes-local-user"
|
|
186
194
|
|
|
187
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
|
+
|
|
188
275
|
def _reject_secret_config(config: dict[str, Any]) -> None:
|
|
189
276
|
present = sorted(key for key in SECRET_CONFIG_KEYS if str(config.get(key) or "").strip())
|
|
190
277
|
if present:
|
|
@@ -195,16 +282,20 @@ def _reject_secret_config(config: dict[str, Any]) -> None:
|
|
|
195
282
|
)
|
|
196
283
|
|
|
197
284
|
|
|
198
|
-
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]:
|
|
199
286
|
raw = args.get("config") or {}
|
|
200
287
|
if not isinstance(raw, dict):
|
|
201
288
|
raise RuntimeError("config must be a JSON object when provided.")
|
|
202
289
|
config = dict(raw)
|
|
203
290
|
backend = args.get("backend") or os.getenv("AGENT_WALLET_BACKEND")
|
|
204
|
-
|
|
291
|
+
if not backend and tool_name:
|
|
292
|
+
backend = _infer_backend_for_tool(tool_name)
|
|
205
293
|
if backend:
|
|
206
|
-
config["backend"] =
|
|
207
|
-
|
|
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:
|
|
208
299
|
config["network"] = str(network).strip()
|
|
209
300
|
_reject_secret_config(config)
|
|
210
301
|
return config
|
|
@@ -228,15 +319,15 @@ def _cli_env(package_root: Path) -> dict[str, str]:
|
|
|
228
319
|
|
|
229
320
|
def _call_wallet_cli(args: dict[str, Any]) -> dict[str, Any]:
|
|
230
321
|
package_root = _resolve_package_root()
|
|
231
|
-
config = _base_config(args)
|
|
232
322
|
tool_name = str(args.get("tool_name") or "").strip()
|
|
233
323
|
if not tool_name:
|
|
234
324
|
raise RuntimeError("tool_name is required.")
|
|
325
|
+
config = _base_config(args, tool_name=tool_name)
|
|
235
326
|
|
|
236
327
|
tool_args = args.get("arguments") or {}
|
|
237
328
|
if not isinstance(tool_args, dict):
|
|
238
329
|
raise RuntimeError("arguments must be a JSON object when provided.")
|
|
239
|
-
if tool_name
|
|
330
|
+
if tool_name in PREVIEW_BOUND_SWAP_TOOLS and str(tool_args.get("mode") or "") == "execute":
|
|
240
331
|
approval_token = str(tool_args.get("approval_token") or "").strip()
|
|
241
332
|
cached_preview = _lookup_preview_for_token(approval_token)
|
|
242
333
|
if cached_preview is not None and "_approved_preview" not in tool_args:
|
|
@@ -277,16 +368,47 @@ def _call_wallet_cli(args: dict[str, Any]) -> dict[str, Any]:
|
|
|
277
368
|
return {"ok": False, "error": f"wallet CLI returned invalid JSON: {exc}"}
|
|
278
369
|
|
|
279
370
|
|
|
371
|
+
def _run_host_script(
|
|
372
|
+
package_root: Path,
|
|
373
|
+
script_name: str,
|
|
374
|
+
script_args: list[str],
|
|
375
|
+
*,
|
|
376
|
+
stdin_text: str | None = None,
|
|
377
|
+
) -> dict[str, Any]:
|
|
378
|
+
command = [
|
|
379
|
+
_python_bin(package_root),
|
|
380
|
+
str(_script_path(package_root, script_name)),
|
|
381
|
+
*script_args,
|
|
382
|
+
]
|
|
383
|
+
completed = subprocess.run(
|
|
384
|
+
command,
|
|
385
|
+
cwd=str(package_root),
|
|
386
|
+
env=_cli_env(package_root),
|
|
387
|
+
text=True,
|
|
388
|
+
input=stdin_text,
|
|
389
|
+
capture_output=True,
|
|
390
|
+
timeout=float(os.getenv("AGENT_WALLET_HERMES_TIMEOUT", "120")),
|
|
391
|
+
check=False,
|
|
392
|
+
)
|
|
393
|
+
if completed.returncode != 0:
|
|
394
|
+
detail = completed.stderr.strip() or completed.stdout.strip()
|
|
395
|
+
return {"ok": False, "error": detail or f"host script exited {completed.returncode}"}
|
|
396
|
+
try:
|
|
397
|
+
return json.loads(completed.stdout.strip() or "{}")
|
|
398
|
+
except json.JSONDecodeError as exc:
|
|
399
|
+
return {"ok": False, "error": f"host script returned invalid JSON: {exc}"}
|
|
400
|
+
|
|
401
|
+
|
|
280
402
|
def _call_issue_approval(args: dict[str, Any]) -> dict[str, Any]:
|
|
281
403
|
if args.get("user_confirmed") is not True:
|
|
282
404
|
raise RuntimeError(
|
|
283
405
|
"user_confirmed=true is required after explicit user approval of the exact confirmation_summary."
|
|
284
406
|
)
|
|
285
407
|
package_root = _resolve_package_root()
|
|
286
|
-
config = _base_config(args)
|
|
287
408
|
tool_name = str(args.get("tool_name") or "").strip()
|
|
288
409
|
if not tool_name:
|
|
289
410
|
raise RuntimeError("tool_name is required.")
|
|
411
|
+
config = _base_config(args, tool_name=tool_name)
|
|
290
412
|
|
|
291
413
|
summary = args.get("confirmation_summary")
|
|
292
414
|
if not isinstance(summary, dict) or not summary:
|
|
@@ -337,6 +459,54 @@ def _call_issue_approval(args: dict[str, Any]) -> dict[str, Any]:
|
|
|
337
459
|
return {"ok": False, "error": f"wallet CLI returned invalid JSON: {exc}"}
|
|
338
460
|
|
|
339
461
|
|
|
462
|
+
def _call_evm_status(args: dict[str, Any]) -> dict[str, Any]:
|
|
463
|
+
package_root = _resolve_package_root()
|
|
464
|
+
command = ["status"]
|
|
465
|
+
user_id = str(args.get("user_id") or "").strip()
|
|
466
|
+
network = str(args.get("network") or "").strip()
|
|
467
|
+
service_url = str(args.get("service_url") or "").strip()
|
|
468
|
+
if user_id:
|
|
469
|
+
command.extend(["--user-id", user_id])
|
|
470
|
+
if network:
|
|
471
|
+
command.extend(["--network", network])
|
|
472
|
+
if service_url:
|
|
473
|
+
command.extend(["--service-url", service_url])
|
|
474
|
+
return _run_host_script(package_root, "manage_openclaw_evm_wallet.py", command)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def _call_evm_setup(args: dict[str, Any]) -> dict[str, Any]:
|
|
478
|
+
package_root = _resolve_package_root()
|
|
479
|
+
password = str(args.get("password") or "").strip()
|
|
480
|
+
if not password:
|
|
481
|
+
raise RuntimeError("password is required.")
|
|
482
|
+
command = [
|
|
483
|
+
"--user-id",
|
|
484
|
+
_user_id(args),
|
|
485
|
+
"--password-stdin",
|
|
486
|
+
]
|
|
487
|
+
network = str(args.get("network") or "").strip()
|
|
488
|
+
label = str(args.get("label") or "").strip()
|
|
489
|
+
service_url = str(args.get("service_url") or "").strip()
|
|
490
|
+
auto_start_service = args.get("auto_start_service")
|
|
491
|
+
bind_network_pair = args.get("bind_network_pair")
|
|
492
|
+
if network:
|
|
493
|
+
command.extend(["--network", network])
|
|
494
|
+
if label:
|
|
495
|
+
command.extend(["--label", label])
|
|
496
|
+
if service_url:
|
|
497
|
+
command.extend(["--service-url", service_url])
|
|
498
|
+
if auto_start_service is False:
|
|
499
|
+
command.append("--no-auto-start-service")
|
|
500
|
+
if bind_network_pair is False:
|
|
501
|
+
command.append("--no-bind-network-pair")
|
|
502
|
+
return _run_host_script(
|
|
503
|
+
package_root,
|
|
504
|
+
"bootstrap_openclaw_evm.py",
|
|
505
|
+
command,
|
|
506
|
+
stdin_text=f"{password}\n",
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
|
|
340
510
|
class _SchemaOnlyBackend:
|
|
341
511
|
def __init__(self, *, name: str, chain: str, network: str):
|
|
342
512
|
self.name = name
|
|
@@ -431,3 +601,17 @@ def agent_wallet_approve(args: dict, **kwargs) -> str:
|
|
|
431
601
|
return _json(_call_issue_approval(args or {}))
|
|
432
602
|
except Exception as exc:
|
|
433
603
|
return _json({"ok": False, "error": str(exc)})
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def agent_wallet_evm_status(args: dict, **kwargs) -> str:
|
|
607
|
+
try:
|
|
608
|
+
return _json(_call_evm_status(args or {}))
|
|
609
|
+
except Exception as exc:
|
|
610
|
+
return _json({"ok": False, "error": str(exc)})
|
|
611
|
+
|
|
612
|
+
|
|
613
|
+
def agent_wallet_evm_setup(args: dict, **kwargs) -> str:
|
|
614
|
+
try:
|
|
615
|
+
return _json(_call_evm_setup(args or {}))
|
|
616
|
+
except Exception as exc:
|
|
617
|
+
return _json({"ok": False, "error": str(exc)})
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agentlayer.tech/wallet",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "NPM installer for the OpenClaw Agent Wallet local runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -69,4 +69,4 @@
|
|
|
69
69
|
"evm"
|
|
70
70
|
],
|
|
71
71
|
"license": "SEE LICENSE IN LICENSE"
|
|
72
|
-
}
|
|
72
|
+
}
|