@agentlayer.tech/wallet 0.1.18 → 0.1.20
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/AGENTS.md +0 -7
- package/.openclaw/extensions/agent-wallet/README.md +3 -2
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/README.md +111 -3
- package/RELEASING.md +5 -15
- package/agent-wallet/README.md +3 -0
- package/agent-wallet/agent_wallet/config.py +11 -0
- package/agent-wallet/agent_wallet/evm_user_wallets.py +310 -2
- package/agent-wallet/agent_wallet/openclaw_runtime.py +10 -41
- package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +52 -0
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/build_release_bundle.py +1 -0
- package/agent-wallet/scripts/flash-sdk-bridge/bridge.mjs +21 -11
- package/agent-wallet/scripts/install_agent_wallet.py +250 -14
- package/agent-wallet/scripts/install_openclaw_local_config.py +20 -51
- package/agent-wallet/scripts/install_openclaw_sealed_keys.py +9 -1
- package/bin/openclaw-agent-wallet.mjs +282 -24
- package/package.json +1 -2
- package/.openclaw/extensions/pay-bridge/README.md +0 -38
- package/.openclaw/extensions/pay-bridge/core.mjs +0 -287
- package/.openclaw/extensions/pay-bridge/dist/core.mjs +0 -287
- package/.openclaw/extensions/pay-bridge/dist/index.js +0 -196
- package/.openclaw/extensions/pay-bridge/index.ts +0 -196
- package/.openclaw/extensions/pay-bridge/openclaw.plugin.json +0 -34
- package/.openclaw/extensions/pay-bridge/package.json +0 -49
- package/.openclaw/extensions/pay-bridge/skills/pay-operator/SKILL.md +0 -20
- package/.openclaw/extensions/pay-bridge/smoke_pay_bridge.mjs +0 -38
|
@@ -3,14 +3,28 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import os
|
|
7
|
+
import secrets
|
|
8
|
+
import subprocess
|
|
9
|
+
import time
|
|
6
10
|
from pathlib import Path
|
|
7
11
|
from typing import Any
|
|
8
|
-
|
|
9
|
-
from
|
|
12
|
+
from urllib.error import URLError
|
|
13
|
+
from urllib.parse import urlparse
|
|
14
|
+
from urllib.request import urlopen
|
|
15
|
+
|
|
16
|
+
from agent_wallet.config import (
|
|
17
|
+
resolve_boot_key,
|
|
18
|
+
resolve_evm_wallet_password,
|
|
19
|
+
resolve_openclaw_home,
|
|
20
|
+
settings,
|
|
21
|
+
)
|
|
10
22
|
from agent_wallet.providers.wdk_evm_local import WdkEvmLocalClient
|
|
11
23
|
from agent_wallet.user_wallets import normalize_user_id
|
|
12
24
|
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
13
25
|
|
|
26
|
+
LOCAL_WDK_EVM_HOSTS = {"127.0.0.1", "localhost", "::1"}
|
|
27
|
+
|
|
14
28
|
|
|
15
29
|
def _normalize_evm_network(value: str | None) -> str:
|
|
16
30
|
network = str(value or "").strip().lower()
|
|
@@ -34,6 +48,87 @@ def _resolve_service_url(service_url: str | None = None) -> str:
|
|
|
34
48
|
return effective
|
|
35
49
|
|
|
36
50
|
|
|
51
|
+
def _paired_network(network: str) -> str | None:
|
|
52
|
+
mapping = {
|
|
53
|
+
"ethereum": "base",
|
|
54
|
+
"base": "ethereum",
|
|
55
|
+
"sepolia": "base-sepolia",
|
|
56
|
+
"base-sepolia": "sepolia",
|
|
57
|
+
}
|
|
58
|
+
return mapping.get(_normalize_evm_network(network))
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _health_url(service_url: str) -> str:
|
|
62
|
+
return f"{service_url.rstrip('/')}/health"
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _service_is_healthy(service_url: str) -> bool:
|
|
66
|
+
try:
|
|
67
|
+
with urlopen(_health_url(service_url), timeout=1.5) as response:
|
|
68
|
+
return int(getattr(response, "status", 0) or 0) == 200
|
|
69
|
+
except (URLError, TimeoutError, OSError):
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _is_local_service_url(service_url: str) -> bool:
|
|
74
|
+
parsed = urlparse(service_url)
|
|
75
|
+
return parsed.scheme in {"http", "https"} and parsed.hostname in LOCAL_WDK_EVM_HOSTS
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _resolve_local_wdk_evm_root() -> Path | None:
|
|
79
|
+
configured = os.getenv("OPENCLAW_EVM_WDK_WALLET_ROOT", "").strip()
|
|
80
|
+
candidates = [configured] if configured else []
|
|
81
|
+
candidates.extend(
|
|
82
|
+
[
|
|
83
|
+
str(Path(__file__).resolve().parents[2] / "wdk-evm-wallet"),
|
|
84
|
+
str(resolve_openclaw_home() / "agent-wallet-runtime" / "current" / "wdk-evm-wallet"),
|
|
85
|
+
]
|
|
86
|
+
)
|
|
87
|
+
for candidate in candidates:
|
|
88
|
+
root = Path(candidate).expanduser()
|
|
89
|
+
if (root / "run-local.sh").exists():
|
|
90
|
+
return root
|
|
91
|
+
return None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _auto_start_local_service(service_url: str, network: str) -> None:
|
|
95
|
+
if _service_is_healthy(service_url):
|
|
96
|
+
return
|
|
97
|
+
if not _is_local_service_url(service_url):
|
|
98
|
+
raise WalletBackendError(
|
|
99
|
+
f"wdk-evm-wallet is unreachable at {_health_url(service_url)} and auto-start only supports localhost URLs."
|
|
100
|
+
)
|
|
101
|
+
wallet_root = _resolve_local_wdk_evm_root()
|
|
102
|
+
if wallet_root is None:
|
|
103
|
+
raise WalletBackendError(
|
|
104
|
+
"wdk-evm-wallet is not healthy and the local launcher could not be found."
|
|
105
|
+
)
|
|
106
|
+
parsed = urlparse(service_url)
|
|
107
|
+
env = os.environ.copy()
|
|
108
|
+
env["HOST"] = parsed.hostname or "127.0.0.1"
|
|
109
|
+
env["PORT"] = str(parsed.port or 8081)
|
|
110
|
+
env["WDK_EVM_NETWORK"] = _normalize_evm_network(network)
|
|
111
|
+
process = subprocess.Popen( # noqa: S603
|
|
112
|
+
["sh", str(wallet_root / "run-local.sh")],
|
|
113
|
+
cwd=str(wallet_root),
|
|
114
|
+
env=env,
|
|
115
|
+
stdin=subprocess.DEVNULL,
|
|
116
|
+
stdout=subprocess.DEVNULL,
|
|
117
|
+
stderr=subprocess.DEVNULL,
|
|
118
|
+
start_new_session=True,
|
|
119
|
+
)
|
|
120
|
+
deadline = time.time() + 30.0
|
|
121
|
+
while time.time() < deadline:
|
|
122
|
+
if _service_is_healthy(service_url):
|
|
123
|
+
return
|
|
124
|
+
if process.poll() is not None:
|
|
125
|
+
raise WalletBackendError("wdk-evm-wallet exited before becoming healthy.")
|
|
126
|
+
time.sleep(0.5)
|
|
127
|
+
raise WalletBackendError(
|
|
128
|
+
f"Timed out waiting for wdk-evm-wallet health at {_health_url(service_url)}."
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
37
132
|
def _resolve_user_evm_wallet_dir(user_id: str) -> Path:
|
|
38
133
|
return resolve_openclaw_home() / "users" / normalize_user_id(user_id) / "wallets"
|
|
39
134
|
|
|
@@ -98,6 +193,58 @@ def list_user_evm_wallet_bindings(user_id: str) -> list[dict[str, Any]]:
|
|
|
98
193
|
return bindings
|
|
99
194
|
|
|
100
195
|
|
|
196
|
+
def _maybe_store_evm_wallet_password(password: str) -> bool:
|
|
197
|
+
value = str(password or "").strip()
|
|
198
|
+
if not value:
|
|
199
|
+
return False
|
|
200
|
+
boot_key = resolve_boot_key()
|
|
201
|
+
if not boot_key:
|
|
202
|
+
return False
|
|
203
|
+
from agent_wallet.sealed_keys import resolve_sealed_keys_path, seal_keys, unseal_keys
|
|
204
|
+
|
|
205
|
+
sealed_path = resolve_sealed_keys_path()
|
|
206
|
+
existing = unseal_keys(boot_key) if sealed_path.exists() else {}
|
|
207
|
+
if existing.get("wdk_evm_wallet_password") == value:
|
|
208
|
+
return False
|
|
209
|
+
seal_keys(boot_key, {**existing, "wdk_evm_wallet_password": value})
|
|
210
|
+
return True
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _ensure_evm_wallet_password() -> str:
|
|
214
|
+
existing = resolve_evm_wallet_password()
|
|
215
|
+
if existing:
|
|
216
|
+
return existing
|
|
217
|
+
boot_key = resolve_boot_key()
|
|
218
|
+
if not boot_key:
|
|
219
|
+
return ""
|
|
220
|
+
generated = secrets.token_urlsafe(24)
|
|
221
|
+
_maybe_store_evm_wallet_password(generated)
|
|
222
|
+
return generated
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _bind_network_pair(
|
|
226
|
+
user_id: str,
|
|
227
|
+
*,
|
|
228
|
+
wallet_id: str,
|
|
229
|
+
network: str,
|
|
230
|
+
service_url: str,
|
|
231
|
+
account_index: int,
|
|
232
|
+
address: str | None,
|
|
233
|
+
) -> None:
|
|
234
|
+
paired = _paired_network(network)
|
|
235
|
+
if not paired:
|
|
236
|
+
return
|
|
237
|
+
bind_user_evm_wallet(
|
|
238
|
+
user_id,
|
|
239
|
+
wallet_id=wallet_id,
|
|
240
|
+
network=paired,
|
|
241
|
+
service_url=service_url,
|
|
242
|
+
account_index=account_index,
|
|
243
|
+
tolerate_locked=True,
|
|
244
|
+
fallback_address=address,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
|
|
101
248
|
def bind_user_evm_wallet(
|
|
102
249
|
user_id: str,
|
|
103
250
|
*,
|
|
@@ -208,6 +355,164 @@ def ensure_user_evm_wallet_binding(
|
|
|
208
355
|
)
|
|
209
356
|
|
|
210
357
|
|
|
358
|
+
def ensure_user_evm_wallet_ready(
|
|
359
|
+
user_id: str,
|
|
360
|
+
*,
|
|
361
|
+
network: str | None = None,
|
|
362
|
+
service_url: str | None = None,
|
|
363
|
+
wallet_id: str | None = None,
|
|
364
|
+
account_index: int | None = None,
|
|
365
|
+
auto_start_service: bool = True,
|
|
366
|
+
) -> dict[str, Any]:
|
|
367
|
+
effective_network = _normalize_evm_network(network or settings.solana_network)
|
|
368
|
+
effective_service_url = _resolve_service_url(service_url)
|
|
369
|
+
effective_account_index = settings.wdk_evm_account_index if account_index is None else int(account_index)
|
|
370
|
+
if auto_start_service:
|
|
371
|
+
_auto_start_local_service(effective_service_url, effective_network)
|
|
372
|
+
elif not _service_is_healthy(effective_service_url):
|
|
373
|
+
raise WalletBackendError(
|
|
374
|
+
f"wdk-evm-wallet is not healthy at {_health_url(effective_service_url)}."
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
client = WdkEvmLocalClient(effective_service_url)
|
|
378
|
+
explicit_wallet_id = str(wallet_id or "").strip()
|
|
379
|
+
binding: dict[str, Any] | None = None
|
|
380
|
+
if explicit_wallet_id:
|
|
381
|
+
binding = ensure_user_evm_wallet_binding(
|
|
382
|
+
user_id,
|
|
383
|
+
network=effective_network,
|
|
384
|
+
service_url=effective_service_url,
|
|
385
|
+
wallet_id=explicit_wallet_id,
|
|
386
|
+
account_index=effective_account_index,
|
|
387
|
+
)
|
|
388
|
+
else:
|
|
389
|
+
try:
|
|
390
|
+
binding = get_user_evm_wallet_binding(user_id, network=effective_network)
|
|
391
|
+
except WalletBackendError:
|
|
392
|
+
binding = None
|
|
393
|
+
|
|
394
|
+
if binding is None:
|
|
395
|
+
existing_bindings = list_user_evm_wallet_bindings(user_id)
|
|
396
|
+
wallet_ids = {
|
|
397
|
+
str(item.get("wallet_id") or "").strip()
|
|
398
|
+
for item in existing_bindings
|
|
399
|
+
if str(item.get("wallet_id") or "").strip()
|
|
400
|
+
}
|
|
401
|
+
if len(wallet_ids) > 1:
|
|
402
|
+
raise WalletBackendError(
|
|
403
|
+
"Multiple EVM wallet bindings exist for this user. Set wdk_evm_wallet_id explicitly to auto-bind a new network."
|
|
404
|
+
)
|
|
405
|
+
if wallet_ids:
|
|
406
|
+
binding = bind_user_evm_wallet(
|
|
407
|
+
user_id,
|
|
408
|
+
wallet_id=next(iter(wallet_ids)),
|
|
409
|
+
network=effective_network,
|
|
410
|
+
service_url=effective_service_url,
|
|
411
|
+
account_index=effective_account_index,
|
|
412
|
+
tolerate_locked=True,
|
|
413
|
+
fallback_address=str(existing_bindings[0].get("address") or "").strip() or None,
|
|
414
|
+
)
|
|
415
|
+
else:
|
|
416
|
+
service_wallets = client.list_wallets_sync()
|
|
417
|
+
service_wallet_ids = {
|
|
418
|
+
str(item.get("walletId") or "").strip()
|
|
419
|
+
for item in service_wallets
|
|
420
|
+
if str(item.get("walletId") or "").strip()
|
|
421
|
+
}
|
|
422
|
+
if len(service_wallet_ids) > 1:
|
|
423
|
+
raise WalletBackendError(
|
|
424
|
+
"Multiple local EVM vault wallets exist. Set wdk_evm_wallet_id explicitly before automatic switching."
|
|
425
|
+
)
|
|
426
|
+
if service_wallet_ids:
|
|
427
|
+
binding = bind_user_evm_wallet(
|
|
428
|
+
user_id,
|
|
429
|
+
wallet_id=next(iter(service_wallet_ids)),
|
|
430
|
+
network=effective_network,
|
|
431
|
+
service_url=effective_service_url,
|
|
432
|
+
account_index=effective_account_index,
|
|
433
|
+
tolerate_locked=True,
|
|
434
|
+
)
|
|
435
|
+
else:
|
|
436
|
+
password = _ensure_evm_wallet_password()
|
|
437
|
+
if not password:
|
|
438
|
+
raise WalletBackendError(
|
|
439
|
+
"EVM wallet is not set up yet and no sealed local EVM wallet password is available for automatic creation."
|
|
440
|
+
)
|
|
441
|
+
created = create_user_evm_wallet(
|
|
442
|
+
user_id,
|
|
443
|
+
password=password,
|
|
444
|
+
network=effective_network,
|
|
445
|
+
service_url=effective_service_url,
|
|
446
|
+
account_index=effective_account_index,
|
|
447
|
+
)
|
|
448
|
+
binding = get_user_evm_wallet_binding(user_id, network=effective_network)
|
|
449
|
+
_bind_network_pair(
|
|
450
|
+
user_id,
|
|
451
|
+
wallet_id=str(created.get("wallet_id") or ""),
|
|
452
|
+
network=effective_network,
|
|
453
|
+
service_url=effective_service_url,
|
|
454
|
+
account_index=effective_account_index,
|
|
455
|
+
address=str(created.get("address") or "").strip() or None,
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
resolved_wallet_id = str(binding.get("wallet_id") or explicit_wallet_id).strip()
|
|
459
|
+
if not resolved_wallet_id:
|
|
460
|
+
raise WalletBackendError("EVM wallet binding is missing wallet_id.")
|
|
461
|
+
|
|
462
|
+
def _resolve_address() -> str:
|
|
463
|
+
payload = client.post_sync(
|
|
464
|
+
"/v1/evm/address/resolve",
|
|
465
|
+
{
|
|
466
|
+
"walletId": resolved_wallet_id,
|
|
467
|
+
"accountIndex": effective_account_index,
|
|
468
|
+
"network": effective_network,
|
|
469
|
+
},
|
|
470
|
+
)
|
|
471
|
+
address = str(payload.get("address") or "").strip()
|
|
472
|
+
if not address:
|
|
473
|
+
raise WalletBackendError("wdk-evm-wallet did not return an address.")
|
|
474
|
+
return address
|
|
475
|
+
|
|
476
|
+
try:
|
|
477
|
+
resolved_address = _resolve_address()
|
|
478
|
+
except WalletBackendError as exc:
|
|
479
|
+
is_locked = exc.code == "wallet_locked" or "wallet is locked" in str(exc).strip().lower()
|
|
480
|
+
if not is_locked:
|
|
481
|
+
raise
|
|
482
|
+
password = resolve_evm_wallet_password()
|
|
483
|
+
if not password:
|
|
484
|
+
raise WalletBackendError(
|
|
485
|
+
"EVM wallet exists but cannot be unlocked automatically because no sealed local EVM wallet password is available."
|
|
486
|
+
) from exc
|
|
487
|
+
unlock_user_evm_wallet(
|
|
488
|
+
user_id,
|
|
489
|
+
password=password,
|
|
490
|
+
network=effective_network,
|
|
491
|
+
service_url=effective_service_url,
|
|
492
|
+
wallet_id=resolved_wallet_id,
|
|
493
|
+
account_index=effective_account_index,
|
|
494
|
+
)
|
|
495
|
+
resolved_address = _resolve_address()
|
|
496
|
+
|
|
497
|
+
binding = bind_user_evm_wallet(
|
|
498
|
+
user_id,
|
|
499
|
+
wallet_id=resolved_wallet_id,
|
|
500
|
+
network=effective_network,
|
|
501
|
+
service_url=effective_service_url,
|
|
502
|
+
account_index=effective_account_index,
|
|
503
|
+
fallback_address=resolved_address,
|
|
504
|
+
)
|
|
505
|
+
_bind_network_pair(
|
|
506
|
+
user_id,
|
|
507
|
+
wallet_id=resolved_wallet_id,
|
|
508
|
+
network=effective_network,
|
|
509
|
+
service_url=effective_service_url,
|
|
510
|
+
account_index=effective_account_index,
|
|
511
|
+
address=resolved_address,
|
|
512
|
+
)
|
|
513
|
+
return binding
|
|
514
|
+
|
|
515
|
+
|
|
211
516
|
def create_user_evm_wallet(
|
|
212
517
|
user_id: str,
|
|
213
518
|
*,
|
|
@@ -251,6 +556,7 @@ def create_user_evm_wallet(
|
|
|
251
556
|
"updated_at": created.get("updatedAt"),
|
|
252
557
|
}
|
|
253
558
|
_write_wallet_binding(resolve_user_evm_wallet_path(user_id, effective_network), binding)
|
|
559
|
+
_maybe_store_evm_wallet_password(password)
|
|
254
560
|
return {
|
|
255
561
|
**binding,
|
|
256
562
|
"unlocked": bool(created.get("unlocked", True)),
|
|
@@ -302,6 +608,7 @@ def import_user_evm_wallet(
|
|
|
302
608
|
"updated_at": created.get("updatedAt"),
|
|
303
609
|
}
|
|
304
610
|
_write_wallet_binding(resolve_user_evm_wallet_path(user_id, effective_network), binding)
|
|
611
|
+
_maybe_store_evm_wallet_password(password)
|
|
305
612
|
return {
|
|
306
613
|
**binding,
|
|
307
614
|
"unlocked": bool(created.get("unlocked", True)),
|
|
@@ -334,6 +641,7 @@ def unlock_user_evm_wallet(
|
|
|
334
641
|
"timeoutSeconds": 0,
|
|
335
642
|
},
|
|
336
643
|
)
|
|
644
|
+
_maybe_store_evm_wallet_password(password)
|
|
337
645
|
return {
|
|
338
646
|
**binding,
|
|
339
647
|
"unlocked": bool(payload.get("unlocked", True)),
|
|
@@ -8,7 +8,7 @@ from typing import Any
|
|
|
8
8
|
from agent_wallet.approval import issue_approval_token
|
|
9
9
|
from agent_wallet.btc_user_wallets import get_user_btc_wallet_binding
|
|
10
10
|
from agent_wallet.config import settings
|
|
11
|
-
from agent_wallet.evm_user_wallets import
|
|
11
|
+
from agent_wallet.evm_user_wallets import ensure_user_evm_wallet_ready
|
|
12
12
|
from agent_wallet.models import OpenClawWalletSessionMetadata
|
|
13
13
|
from agent_wallet.openclaw_adapter import OpenClawWalletAdapter
|
|
14
14
|
from agent_wallet.plugin_bundle import build_openclaw_plugin_bundle
|
|
@@ -173,38 +173,17 @@ def onboard_openclaw_user_wallet(
|
|
|
173
173
|
"base_sepolia": "base-sepolia",
|
|
174
174
|
}
|
|
175
175
|
effective_network = aliases.get(requested_network, requested_network)
|
|
176
|
-
binding: dict[str, Any] | None = None
|
|
177
176
|
wallet_id = str(wdk_evm_wallet_id or settings.wdk_evm_wallet_id).strip()
|
|
178
177
|
if not service_url:
|
|
179
178
|
raise WalletBackendError("wdk_evm_service_url is required for backend=wdk_evm_local.")
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
wallet_id=wallet_id,
|
|
189
|
-
account_index=account_index,
|
|
190
|
-
)
|
|
191
|
-
else:
|
|
192
|
-
if str(binding.get("wallet_id") or "").strip() != wallet_id:
|
|
193
|
-
binding = ensure_user_evm_wallet_binding(
|
|
194
|
-
user_id,
|
|
195
|
-
network=effective_network,
|
|
196
|
-
service_url=service_url,
|
|
197
|
-
wallet_id=wallet_id,
|
|
198
|
-
account_index=account_index,
|
|
199
|
-
)
|
|
200
|
-
else:
|
|
201
|
-
binding = ensure_user_evm_wallet_binding(
|
|
202
|
-
user_id,
|
|
203
|
-
network=effective_network,
|
|
204
|
-
service_url=service_url,
|
|
205
|
-
account_index=account_index,
|
|
206
|
-
)
|
|
207
|
-
wallet_id = str(binding.get("wallet_id") or "").strip()
|
|
179
|
+
binding = ensure_user_evm_wallet_ready(
|
|
180
|
+
user_id,
|
|
181
|
+
network=effective_network,
|
|
182
|
+
service_url=service_url,
|
|
183
|
+
wallet_id=wallet_id or None,
|
|
184
|
+
account_index=account_index,
|
|
185
|
+
)
|
|
186
|
+
wallet_id = str(binding.get("wallet_id") or wallet_id).strip()
|
|
208
187
|
if not wallet_id:
|
|
209
188
|
raise WalletBackendError(
|
|
210
189
|
"wdk_evm_wallet_id is required for backend=wdk_evm_local, or create a bound user EVM wallet first."
|
|
@@ -212,17 +191,7 @@ def onboard_openclaw_user_wallet(
|
|
|
212
191
|
|
|
213
192
|
client = WdkEvmLocalClient(service_url)
|
|
214
193
|
wallet_meta = client.post_sync("/v1/evm/wallets/get", {"walletId": wallet_id})
|
|
215
|
-
resolved_address = str(
|
|
216
|
-
if not resolved_address:
|
|
217
|
-
address_payload = client.post_sync(
|
|
218
|
-
"/v1/evm/address/resolve",
|
|
219
|
-
{
|
|
220
|
-
"walletId": wallet_id,
|
|
221
|
-
"accountIndex": account_index,
|
|
222
|
-
"network": effective_network,
|
|
223
|
-
},
|
|
224
|
-
)
|
|
225
|
-
resolved_address = str(address_payload.get("address") or "").strip()
|
|
194
|
+
resolved_address = str(binding.get("address") or "").strip()
|
|
226
195
|
backend = WdkEvmLocalWalletBackend(
|
|
227
196
|
service_url=service_url,
|
|
228
197
|
wallet_id=wallet_id,
|
|
@@ -102,6 +102,35 @@ def _unwrap_payload(response: httpx.Response) -> dict[str, Any]:
|
|
|
102
102
|
return data
|
|
103
103
|
|
|
104
104
|
|
|
105
|
+
def _unwrap_list_payload(response: httpx.Response) -> list[dict[str, Any]]:
|
|
106
|
+
try:
|
|
107
|
+
payload = response.json()
|
|
108
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
109
|
+
raise WalletBackendError(
|
|
110
|
+
f"wdk-evm-wallet returned a non-JSON response ({response.status_code}).",
|
|
111
|
+
code="network_unavailable",
|
|
112
|
+
details={
|
|
113
|
+
"service": "wdk-evm-wallet",
|
|
114
|
+
"http_status": response.status_code,
|
|
115
|
+
},
|
|
116
|
+
) from exc
|
|
117
|
+
if response.status_code >= 400 or payload.get("ok") is False:
|
|
118
|
+
detail = payload.get("error") or f"HTTP {response.status_code}"
|
|
119
|
+
raise WalletBackendError(
|
|
120
|
+
str(detail),
|
|
121
|
+
code=str(payload.get("error_code") or "").strip() or None,
|
|
122
|
+
details=_error_details_from_payload(payload),
|
|
123
|
+
)
|
|
124
|
+
data = payload.get("data")
|
|
125
|
+
if not isinstance(data, list):
|
|
126
|
+
raise WalletBackendError("wdk-evm-wallet returned an invalid list response payload.")
|
|
127
|
+
wallets: list[dict[str, Any]] = []
|
|
128
|
+
for item in data:
|
|
129
|
+
if isinstance(item, dict):
|
|
130
|
+
wallets.append(dict(item))
|
|
131
|
+
return wallets
|
|
132
|
+
|
|
133
|
+
|
|
105
134
|
class WdkEvmLocalClient:
|
|
106
135
|
"""Small client for the local EVM wallet service."""
|
|
107
136
|
|
|
@@ -203,3 +232,26 @@ class WdkEvmLocalClient:
|
|
|
203
232
|
details={"service": "wdk-evm-wallet", "path": path},
|
|
204
233
|
) from exc
|
|
205
234
|
return _unwrap_payload(response)
|
|
235
|
+
|
|
236
|
+
def list_wallets_sync(self) -> list[dict[str, Any]]:
|
|
237
|
+
try:
|
|
238
|
+
with httpx.Client(
|
|
239
|
+
timeout=float(settings.http_timeout),
|
|
240
|
+
headers=self._headers,
|
|
241
|
+
follow_redirects=False,
|
|
242
|
+
trust_env=False,
|
|
243
|
+
) as client:
|
|
244
|
+
response = client.get(f"{self.base_url}/v1/evm/wallets")
|
|
245
|
+
except httpx.TimeoutException as exc:
|
|
246
|
+
raise WalletBackendError(
|
|
247
|
+
"wdk-evm-wallet request timed out.",
|
|
248
|
+
code="network_unavailable",
|
|
249
|
+
details={"service": "wdk-evm-wallet", "path": "/v1/evm/wallets"},
|
|
250
|
+
) from exc
|
|
251
|
+
except httpx.RequestError as exc:
|
|
252
|
+
raise WalletBackendError(
|
|
253
|
+
f"wdk-evm-wallet request failed: {exc}",
|
|
254
|
+
code="network_unavailable",
|
|
255
|
+
details={"service": "wdk-evm-wallet", "path": "/v1/evm/wallets"},
|
|
256
|
+
) from exc
|
|
257
|
+
return _unwrap_list_payload(response)
|
|
@@ -350,14 +350,22 @@ function variantToSide(sideVariant) {
|
|
|
350
350
|
return String(sideVariant ?? "");
|
|
351
351
|
}
|
|
352
352
|
|
|
353
|
+
function safeTokenSymbol(poolConfig, mintPk) {
|
|
354
|
+
try {
|
|
355
|
+
return poolConfig.getTokenFromMintPk(mintPk)?.symbol ?? null;
|
|
356
|
+
} catch {
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
353
361
|
function buildMarketSnapshot(poolConfig, marketConfig, deprecated = false) {
|
|
354
|
-
const
|
|
355
|
-
const
|
|
362
|
+
const targetSymbol = safeTokenSymbol(poolConfig, marketConfig.targetMint);
|
|
363
|
+
const collateralSymbol = safeTokenSymbol(poolConfig, marketConfig.collateralMint);
|
|
356
364
|
return {
|
|
357
365
|
pool_name: poolConfig.poolName,
|
|
358
|
-
symbol:
|
|
359
|
-
market_symbol:
|
|
360
|
-
collateral_symbol:
|
|
366
|
+
symbol: targetSymbol,
|
|
367
|
+
market_symbol: targetSymbol,
|
|
368
|
+
collateral_symbol: collateralSymbol,
|
|
361
369
|
side: variantToSide(marketConfig.side),
|
|
362
370
|
market_id: marketConfig.marketId,
|
|
363
371
|
market_address: marketConfig.marketAccount.toBase58(),
|
|
@@ -374,14 +382,16 @@ function buildMarketSnapshot(poolConfig, marketConfig, deprecated = false) {
|
|
|
374
382
|
|
|
375
383
|
function buildPositionSnapshot(poolConfig, positionAccount) {
|
|
376
384
|
const marketConfig = poolConfig.getMarketConfigByPk(positionAccount.market);
|
|
377
|
-
const
|
|
378
|
-
const
|
|
385
|
+
const targetSymbol = marketConfig ? safeTokenSymbol(poolConfig, marketConfig.targetMint) : null;
|
|
386
|
+
const collateralSymbol = marketConfig
|
|
387
|
+
? safeTokenSymbol(poolConfig, marketConfig.collateralMint)
|
|
388
|
+
: null;
|
|
379
389
|
return {
|
|
380
390
|
pool_name: poolConfig.poolName,
|
|
381
|
-
symbol:
|
|
382
|
-
market_symbol:
|
|
383
|
-
collateral_symbol:
|
|
384
|
-
side: variantToSide(marketConfig
|
|
391
|
+
symbol: targetSymbol,
|
|
392
|
+
market_symbol: targetSymbol,
|
|
393
|
+
collateral_symbol: collateralSymbol,
|
|
394
|
+
side: variantToSide(marketConfig?.side),
|
|
385
395
|
is_active: Boolean(positionAccount.isActive),
|
|
386
396
|
position_address: positionAccount.pubkey.toBase58(),
|
|
387
397
|
market_address: positionAccount.market.toBase58(),
|