@agentlayer.tech/wallet 0.1.27 → 0.1.30

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 (56) hide show
  1. package/.openclaw/extensions/agent-wallet/README.md +4 -5
  2. package/.openclaw/extensions/agent-wallet/dist/index.js +31 -31
  3. package/.openclaw/extensions/agent-wallet/index.ts +31 -31
  4. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +2 -2
  5. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  6. package/CHANGELOG.md +52 -0
  7. package/README.md +9 -0
  8. package/agent-wallet/README.md +18 -22
  9. package/agent-wallet/agent_wallet/bootstrap.py +28 -12
  10. package/agent-wallet/agent_wallet/btc_user_wallets.py +2 -7
  11. package/agent-wallet/agent_wallet/config.py +99 -22
  12. package/agent-wallet/agent_wallet/evm_user_wallets.py +2 -14
  13. package/agent-wallet/agent_wallet/openclaw_adapter.py +72 -108
  14. package/agent-wallet/agent_wallet/openclaw_runtime.py +3 -12
  15. package/agent-wallet/agent_wallet/providers/kamino.py +21 -4
  16. package/agent-wallet/agent_wallet/providers/solana_rpc.py +0 -23
  17. package/agent-wallet/agent_wallet/providers/x402.py +198 -18
  18. package/agent-wallet/agent_wallet/user_wallets.py +4 -3
  19. package/agent-wallet/agent_wallet/wallet_layer/base.py +3 -3
  20. package/agent-wallet/agent_wallet/wallet_layer/factory.py +8 -5
  21. package/agent-wallet/agent_wallet/wallet_layer/solana.py +437 -44
  22. package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +2 -8
  23. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +13 -13
  24. package/agent-wallet/examples/openclaw_runtime_onboarding.py +1 -1
  25. package/agent-wallet/examples/openclaw_user_wallet_example.py +1 -1
  26. package/agent-wallet/openclaw.plugin.json +1 -1
  27. package/agent-wallet/pyproject.toml +2 -1
  28. package/agent-wallet/scripts/bootstrap_openclaw_btc.py +3 -5
  29. package/agent-wallet/scripts/bootstrap_openclaw_evm.py +2 -12
  30. package/agent-wallet/scripts/build_release_bundle.py +1 -0
  31. package/agent-wallet/scripts/flash-sdk-bridge/bridge.mjs +1 -4
  32. package/agent-wallet/scripts/install_agent_wallet.py +1 -0
  33. package/agent-wallet/scripts/install_openclaw_local_config.py +4 -6
  34. package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +2 -4
  35. package/agent-wallet/scripts/manage_openclaw_evm_wallet.py +2 -15
  36. package/agent-wallet/scripts/reveal_btc_seed.sh +7 -16
  37. package/agent-wallet/scripts/setup_btc_wallet.sh +7 -16
  38. package/agent-wallet/scripts/setup_evm_wallet.sh +1 -11
  39. package/agent-wallet/scripts/switch_openclaw_wallet_network.py +4 -1
  40. package/agent-wallet/skills/wallet-operator/SKILL.md +0 -1
  41. package/bin/openclaw-agent-wallet.mjs +289 -0
  42. package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +20 -0
  43. package/claude-code/plugins/agent-wallet/.mcp.json +14 -0
  44. package/claude-code/plugins/agent-wallet/README.md +65 -0
  45. package/claude-code/plugins/agent-wallet/scripts/run_mcp.sh +34 -0
  46. package/claude-code/plugins/agent-wallet/skills/wallet-operator/SKILL.md +18 -0
  47. package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +38 -0
  48. package/codex/plugins/agent-wallet/.mcp.json +15 -0
  49. package/codex/plugins/agent-wallet/README.md +39 -0
  50. package/codex/plugins/agent-wallet/scripts/run_mcp.sh +21 -0
  51. package/codex/plugins/agent-wallet/server.py +1077 -0
  52. package/codex/plugins/agent-wallet/skills/wallet-operator/SKILL.md +18 -0
  53. package/hermes/plugins/agent_wallet/schemas.py +2 -2
  54. package/hermes/plugins/agent_wallet/tools.py +17 -3
  55. package/package.json +6 -1
  56. package/setup.sh +2 -0
@@ -7,7 +7,7 @@ from typing import Any
7
7
 
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
- from agent_wallet.config import settings
10
+ from agent_wallet.config import normalize_btc_network, normalize_evm_network, settings
11
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
@@ -105,8 +105,7 @@ def onboard_openclaw_user_wallet(
105
105
  if wdk_btc_account_index is None
106
106
  else int(wdk_btc_account_index)
107
107
  )
108
- requested_network = (network or settings.solana_network).strip().lower() or "bitcoin"
109
- effective_network = "bitcoin" if requested_network == "mainnet" else requested_network
108
+ effective_network = normalize_btc_network(network or settings.solana_network)
110
109
  binding: dict[str, Any] | None = None
111
110
  wallet_id = str(wdk_btc_wallet_id or settings.wdk_btc_wallet_id).strip()
112
111
  if not service_url:
@@ -164,15 +163,7 @@ def onboard_openclaw_user_wallet(
164
163
  if wdk_evm_account_index is None
165
164
  else int(wdk_evm_account_index)
166
165
  )
167
- requested_network = (network or settings.solana_network).strip().lower() or "ethereum"
168
- aliases = {
169
- "mainnet": "ethereum",
170
- "eth": "ethereum",
171
- "eth-mainnet": "ethereum",
172
- "base-mainnet": "base",
173
- "base_sepolia": "base-sepolia",
174
- }
175
- effective_network = aliases.get(requested_network, requested_network)
166
+ effective_network = normalize_evm_network(network or settings.solana_network)
176
167
  wallet_id = str(wdk_evm_wallet_id or settings.wdk_evm_wallet_id).strip()
177
168
  if not service_url:
178
169
  raise WalletBackendError("wdk_evm_service_url is required for backend=wdk_evm_local.")
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  from typing import Any
6
6
 
7
- from agent_wallet.config import settings
7
+ from agent_wallet.config import normalize_solana_network, settings
8
8
  from agent_wallet.exceptions import ProviderError
9
9
  from agent_wallet.http_client import get_client
10
10
 
@@ -42,9 +42,7 @@ def _normalized_tx_response(data: Any, *, provider_name: str) -> dict[str, Any]:
42
42
 
43
43
 
44
44
  def _env_name(network: str) -> str:
45
- normalized = str(network).strip().lower()
46
- if normalized == "devnet":
47
- return "devnet"
45
+ normalize_solana_network(network)
48
46
  return "mainnet-beta"
49
47
 
50
48
 
@@ -125,6 +123,25 @@ async def fetch_lend_user_rewards(*, user: str) -> dict[str, Any]:
125
123
  return data
126
124
 
127
125
 
126
+ async def fetch_lend_loan_info(
127
+ *,
128
+ obligation: str,
129
+ network: str,
130
+ ) -> dict[str, Any]:
131
+ """Fetch a detailed Kamino loan report for one obligation."""
132
+ client = get_client()
133
+ response = await client.get(
134
+ f"{_normalized_api_base()}/klend/loans/{obligation}",
135
+ params={"env": _env_name(network)},
136
+ )
137
+ if response.status_code != 200:
138
+ raise ProviderError("kamino", f"HTTP {response.status_code}: {response.text[:300]}")
139
+ data = response.json()
140
+ if not isinstance(data, dict):
141
+ raise ProviderError("kamino", "Unexpected loan info response from Kamino.")
142
+ return data
143
+
144
+
128
145
  async def build_lend_deposit_transaction(
129
146
  *,
130
147
  wallet: str,
@@ -42,11 +42,6 @@ def _parse_gateway_url(rpc_url: str) -> tuple[str, str, str]:
42
42
 
43
43
  def _fallback_for_rpc_url(rpc_url: str) -> str:
44
44
  """Choose a cluster-appropriate official fallback URL."""
45
- lowered = rpc_url.lower()
46
- if "devnet" in lowered:
47
- return "https://api.devnet.solana.com"
48
- if "testnet" in lowered:
49
- return "https://api.testnet.solana.com"
50
45
  return SOLANA_RPC_FALLBACK
51
46
 
52
47
 
@@ -415,24 +410,6 @@ async def simulate_transaction(
415
410
  }
416
411
 
417
412
 
418
- async def request_airdrop(
419
- address: str,
420
- lamports: int,
421
- rpc_url: str,
422
- commitment: str = "confirmed",
423
- ) -> dict[str, Any]:
424
- """Request a devnet or testnet SOL airdrop."""
425
- data = await rpc_call(
426
- "requestAirdrop",
427
- [address, lamports, {"commitment": commitment}],
428
- rpc_url=rpc_url,
429
- )
430
- return {
431
- "signature": data.get("result"),
432
- "source": "solana-rpc",
433
- }
434
-
435
-
436
413
  async def get_signature_status(
437
414
  signature: str,
438
415
  rpc_url: str,
@@ -5,25 +5,24 @@ from __future__ import annotations
5
5
  import base64
6
6
  import hashlib
7
7
  import json
8
+ import logging
8
9
  from typing import Any
9
10
  from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
10
11
 
11
- from agent_wallet.config import resolve_solana_rpc_url
12
+ from agent_wallet.config import normalize_solana_network, resolve_solana_rpc_url
12
13
  from agent_wallet.exceptions import ProviderError
13
14
  from agent_wallet.http_client import get_client
14
15
  from agent_wallet.wallet_layer.base import AgentWalletBackend
15
16
 
16
17
  CDP_BAZAAR_DISCOVERY_BASE_URL = "https://api.cdp.coinbase.com/platform/v2/x402/discovery"
17
18
  AGENTIC_MARKET_API_BASE_URL = "https://api.agentic.market/v1"
19
+ X402_EXECUTE_TIMEOUT_SECONDS = 45.0
18
20
  SOLANA_CAIP_BY_NETWORK = {
19
21
  "mainnet": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
20
- "devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
21
22
  }
22
23
  EVM_CAIP_BY_NETWORK = {
23
24
  "ethereum": "eip155:1",
24
25
  "base": "eip155:8453",
25
- "sepolia": "eip155:11155111",
26
- "base-sepolia": "eip155:84532",
27
26
  }
28
27
  _USDC_IDENTIFIERS = {
29
28
  "usdc",
@@ -32,6 +31,7 @@ _USDC_IDENTIFIERS = {
32
31
  "0x036cbd53842c5426634e7929541ec2318f3dcf7e",
33
32
  "epjfwdd5aufqssqem2qn1xzybapc8g4wegkgkzwytdt1v",
34
33
  }
34
+ log = logging.getLogger("agent_wallet.x402")
35
35
 
36
36
 
37
37
  def _backend_chain(backend: AgentWalletBackend) -> str:
@@ -60,7 +60,7 @@ def _backend_solana_sdk_rpc_url(backend: AgentWalletBackend) -> str | None:
60
60
  if primary.startswith(("http://", "https://")):
61
61
  return primary
62
62
  network = _backend_network(backend)
63
- fallback = resolve_solana_rpc_url(network or "mainnet", "")
63
+ fallback = resolve_solana_rpc_url(normalize_solana_network(network or "mainnet"), "")
64
64
  return _trim(fallback) or None
65
65
 
66
66
 
@@ -137,6 +137,13 @@ def _append_query(url: str, query: dict[str, str]) -> str:
137
137
  )
138
138
 
139
139
 
140
+ def _request_host(url: str) -> str:
141
+ try:
142
+ return _trim(urlsplit(url).netloc).lower()
143
+ except Exception:
144
+ return ""
145
+
146
+
140
147
  def _response_text(response: Any) -> str:
141
148
  try:
142
149
  text = response.text
@@ -351,7 +358,7 @@ def _wallet_caip_networks(backend: AgentWalletBackend) -> list[str]:
351
358
  def _solana_exact_execution_supported(backend: AgentWalletBackend) -> bool:
352
359
  return (
353
360
  _backend_chain(backend) == "solana"
354
- and _backend_network(backend) in {"mainnet", "devnet"}
361
+ and _backend_network(backend) == "mainnet"
355
362
  and getattr(backend, "signer", None) is not None
356
363
  )
357
364
 
@@ -359,7 +366,7 @@ def _solana_exact_execution_supported(backend: AgentWalletBackend) -> bool:
359
366
  def _evm_exact_execution_supported(backend: AgentWalletBackend) -> bool:
360
367
  return (
361
368
  _backend_chain(backend) == "evm"
362
- and _backend_network(backend) in {"base", "base-sepolia"}
369
+ and _backend_network(backend) == "base"
363
370
  and callable(getattr(backend, "sign_x402_evm_exact_typed_data", None))
364
371
  )
365
372
 
@@ -378,9 +385,7 @@ def _wallet_x402_support_summary(backend: AgentWalletBackend) -> dict[str, Any]:
378
385
  supported_networks = _wallet_caip_networks(backend)
379
386
  planned_execution_networks = {
380
387
  "eip155:8453",
381
- "eip155:84532",
382
388
  "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
383
- "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
384
389
  }
385
390
  execution_modes: list[str] = []
386
391
  if _solana_exact_execution_supported(backend):
@@ -513,6 +518,7 @@ def _build_request_metadata(
513
518
  )
514
519
  return {
515
520
  "url": final_url,
521
+ "host": _request_host(final_url),
516
522
  "method": http_method,
517
523
  "headers": normalized_headers,
518
524
  "query": normalized_query,
@@ -529,6 +535,7 @@ async def _send_request(
529
535
  client: Any,
530
536
  request: dict[str, Any],
531
537
  extra_headers: dict[str, str] | None = None,
538
+ timeout: float | None = None,
532
539
  ) -> Any:
533
540
  headers = dict(request["headers"])
534
541
  if extra_headers:
@@ -539,6 +546,7 @@ async def _send_request(
539
546
  headers=headers,
540
547
  json=request["json_body"] if request["json_body"] is not None else None,
541
548
  content=request["text_body"] if request["text_body"] is not None else None,
549
+ timeout=timeout,
542
550
  )
543
551
 
544
552
 
@@ -671,6 +679,96 @@ def _require_executable_payment(
671
679
  return selected
672
680
 
673
681
 
682
+ def _validate_payment_requirement(
683
+ selected: dict[str, Any] | None,
684
+ *,
685
+ backend: AgentWalletBackend,
686
+ request_url: str,
687
+ ) -> dict[str, Any]:
688
+ if not isinstance(selected, dict):
689
+ raise ProviderError(
690
+ "x402-validate",
691
+ "This endpoint returned HTTP 402 but no compatible payment option was found for the active wallet.",
692
+ details={
693
+ "request_url": request_url,
694
+ "wallet_chain": _backend_chain(backend),
695
+ "wallet_network": _backend_network(backend),
696
+ },
697
+ )
698
+
699
+ scheme = _trim(selected.get("scheme")).lower()
700
+ if scheme != "exact":
701
+ raise ProviderError(
702
+ "x402-validate",
703
+ f"Unsupported x402 payment scheme '{scheme or 'unknown'}'. Only 'exact' is supported.",
704
+ details={"request_url": request_url, "selected_payment": selected},
705
+ )
706
+
707
+ if not _trim(selected.get("pay_to")):
708
+ raise ProviderError(
709
+ "x402-validate",
710
+ "Payment destination (payTo) is missing from the x402 requirement.",
711
+ details={"request_url": request_url, "selected_payment": selected},
712
+ )
713
+
714
+ compatibility = _requirement_compatibility(selected, backend)
715
+ if compatibility["currently_executable"]:
716
+ return selected
717
+
718
+ chain = _backend_chain(backend) or "unknown"
719
+ network = _backend_network(backend) or "unknown"
720
+ requirement_network = _trim(selected.get("network")) or "unknown"
721
+ if chain == "solana" and requirement_network not in SOLANA_CAIP_BY_NETWORK.values():
722
+ message = (
723
+ f"This endpoint requires payment on {requirement_network}, but the active wallet is Solana ({network})."
724
+ )
725
+ elif chain == "evm" and requirement_network not in EVM_CAIP_BY_NETWORK.values():
726
+ message = (
727
+ f"This endpoint requires payment on {requirement_network}, but the active wallet is EVM ({network})."
728
+ )
729
+ else:
730
+ message = str(compatibility["reason"])
731
+
732
+ raise ProviderError(
733
+ "x402-validate",
734
+ message,
735
+ details={
736
+ "request_url": request_url,
737
+ "selected_payment": selected,
738
+ "compatibility": compatibility,
739
+ },
740
+ )
741
+
742
+
743
+ def _validate_request_execution_policy(
744
+ *,
745
+ request: dict[str, Any],
746
+ backend: AgentWalletBackend,
747
+ ) -> None:
748
+ host = _trim(request.get("host")).lower()
749
+ if host == "x402.alchemy.com":
750
+ headers = request.get("headers")
751
+ has_auth = isinstance(headers, dict) and any(
752
+ str(key).strip().lower() == "authorization" and str(value).strip()
753
+ for key, value in headers.items()
754
+ )
755
+ if not has_auth:
756
+ raise ProviderError(
757
+ "x402-validate",
758
+ (
759
+ "Alchemy's x402 gateway needs wallet-auth headers in addition to the payment challenge. "
760
+ "The generic x402 tool does not mint Alchemy SIWE/SIWS auth tokens yet, so this endpoint "
761
+ "is not safe to execute through the generic flow."
762
+ ),
763
+ details={
764
+ "request_url": request.get("url"),
765
+ "host": host,
766
+ "wallet_chain": _backend_chain(backend),
767
+ "wallet_network": _backend_network(backend),
768
+ "hint": "Use a dedicated Alchemy agent gateway integration or authenticated CLI flow.",
769
+ },
770
+ )
771
+
674
772
  def _select_sdk_payment_requirement(
675
773
  payment_required: Any,
676
774
  *,
@@ -940,15 +1038,57 @@ async def _create_payment_headers(
940
1038
  )
941
1039
 
942
1040
 
943
- def _extract_settlement_header(response: Any) -> dict[str, Any] | None:
1041
+ def _extract_settlement_header(response: Any) -> dict[str, Any]:
944
1042
  sdk = _load_x402_sdk()
1043
+ settle = sdk["x402HTTPClientBase"]().get_payment_settle_response(
1044
+ lambda name: response.headers.get(name)
1045
+ )
1046
+ return settle.model_dump(by_alias=True, exclude_none=True)
1047
+
1048
+
1049
+ def _extract_settlement_header_safe(response: Any) -> dict[str, Any] | None:
945
1050
  try:
946
- settle = sdk["x402HTTPClientBase"]().get_payment_settle_response(
947
- lambda name: response.headers.get(name)
1051
+ return _extract_settlement_header(response)
1052
+ except Exception as exc:
1053
+ log.warning(
1054
+ "x402 settlement header parse failed",
1055
+ extra={
1056
+ "status_code": getattr(response, "status_code", None),
1057
+ "payment_response": response.headers.get("PAYMENT-RESPONSE")
1058
+ if hasattr(response, "headers")
1059
+ else None,
1060
+ "x_payment_response": response.headers.get("X-PAYMENT-RESPONSE")
1061
+ if hasattr(response, "headers")
1062
+ else None,
1063
+ "error_type": type(exc).__name__,
1064
+ "error": str(exc) or None,
1065
+ },
948
1066
  )
949
- except Exception:
950
1067
  return None
951
- return settle.model_dump(by_alias=True, exclude_none=True)
1068
+
1069
+
1070
+ def _log_x402_execute(
1071
+ *,
1072
+ request: dict[str, Any],
1073
+ selected_payment: dict[str, Any] | None,
1074
+ response: Any,
1075
+ settlement: dict[str, Any] | None,
1076
+ ) -> None:
1077
+ log.info(
1078
+ "x402 execute completed",
1079
+ extra={
1080
+ "url": request.get("url"),
1081
+ "method": request.get("method"),
1082
+ "request_fingerprint": request.get("request_fingerprint"),
1083
+ "x402_network": selected_payment.get("network") if isinstance(selected_payment, dict) else None,
1084
+ "x402_asset": selected_payment.get("asset") if isinstance(selected_payment, dict) else None,
1085
+ "x402_amount": selected_payment.get("amount") if isinstance(selected_payment, dict) else None,
1086
+ "x402_pay_to": selected_payment.get("pay_to") if isinstance(selected_payment, dict) else None,
1087
+ "status_code": getattr(response, "status_code", None),
1088
+ "transaction": settlement.get("transaction") if isinstance(settlement, dict) else None,
1089
+ "confirmed": bool(settlement and settlement.get("success")),
1090
+ },
1091
+ )
952
1092
 
953
1093
 
954
1094
  async def search_services(
@@ -1250,6 +1390,29 @@ async def execute_request(
1250
1390
  query: dict[str, Any] | None = None,
1251
1391
  json_body: Any | None = None,
1252
1392
  text_body: str | None = None,
1393
+ ) -> dict[str, Any]:
1394
+ executed = await pay_and_fetch(
1395
+ backend=backend,
1396
+ url=url,
1397
+ method=method,
1398
+ headers=headers,
1399
+ query=query,
1400
+ json_body=json_body,
1401
+ text_body=text_body,
1402
+ )
1403
+ executed["mode"] = "execute"
1404
+ return executed
1405
+
1406
+
1407
+ async def pay_and_fetch(
1408
+ *,
1409
+ backend: AgentWalletBackend,
1410
+ url: str,
1411
+ method: str = "GET",
1412
+ headers: dict[str, Any] | None = None,
1413
+ query: dict[str, Any] | None = None,
1414
+ json_body: Any | None = None,
1415
+ text_body: str | None = None,
1253
1416
  ) -> dict[str, Any]:
1254
1417
  preview = await preview_request(
1255
1418
  backend=backend,
@@ -1268,7 +1431,13 @@ async def execute_request(
1268
1431
  executed["confirmed"] = False
1269
1432
  return executed
1270
1433
 
1271
- selected_payment = _require_executable_payment(preview=preview, backend=backend)
1434
+ selected_payment = _validate_payment_requirement(
1435
+ preview.get("selected_payment")
1436
+ if isinstance(preview.get("selected_payment"), dict)
1437
+ else None,
1438
+ backend=backend,
1439
+ request_url=str(preview.get("request_url") or url),
1440
+ )
1272
1441
  payment_required_header = (
1273
1442
  dict(preview.get("response_headers") or {}).get("payment-required")
1274
1443
  )
@@ -1283,15 +1452,26 @@ async def execute_request(
1283
1452
  json_body=json_body,
1284
1453
  text_body=text_body,
1285
1454
  )
1455
+ _validate_request_execution_policy(request=request, backend=backend)
1286
1456
  payment_headers = await _create_payment_headers(
1287
1457
  backend=backend,
1288
1458
  payment_required_header=payment_required_header,
1289
1459
  selected_payment=selected_payment,
1290
1460
  )
1291
- payment_headers["Access-Control-Expose-Headers"] = "PAYMENT-RESPONSE,X-PAYMENT-RESPONSE"
1292
1461
  client = get_client()
1293
- response = await _send_request(client=client, request=request, extra_headers=payment_headers)
1294
- settlement = _extract_settlement_header(response)
1462
+ response = await _send_request(
1463
+ client=client,
1464
+ request=request,
1465
+ extra_headers=payment_headers,
1466
+ timeout=X402_EXECUTE_TIMEOUT_SECONDS,
1467
+ )
1468
+ settlement = _extract_settlement_header_safe(response)
1469
+ _log_x402_execute(
1470
+ request=request,
1471
+ selected_payment=selected_payment,
1472
+ response=response,
1473
+ settlement=settlement,
1474
+ )
1295
1475
 
1296
1476
  executed = dict(preview)
1297
1477
  executed.update(
@@ -14,6 +14,7 @@ from agent_wallet.bootstrap import (
14
14
  )
15
15
  from agent_wallet.config import (
16
16
  allow_plaintext_user_wallet_migration,
17
+ normalize_solana_network,
17
18
  resolve_openclaw_home,
18
19
  resolve_runtime_solana_rpc_config,
19
20
  resolve_runtime_solana_swap_config,
@@ -46,13 +47,13 @@ def normalize_user_id(user_id: str) -> str:
46
47
 
47
48
  def resolve_user_wallet_path(user_id: str, network: str | None = None) -> Path:
48
49
  """Resolve the wallet file path for a given OpenClaw user."""
49
- effective_network = (network or settings.solana_network).strip().lower() or "mainnet"
50
+ effective_network = normalize_solana_network(network or settings.solana_network)
50
51
  user_dir = resolve_openclaw_home() / "users" / normalize_user_id(user_id) / "wallets"
51
52
  return user_dir / f"solana-{effective_network}-agent.json"
52
53
 
53
54
 
54
55
  def _user_wallet_metadata(user_id: str, address: str, network: str | None = None) -> dict[str, str]:
55
- effective_network = (network or settings.solana_network).strip().lower() or "mainnet"
56
+ effective_network = normalize_solana_network(network or settings.solana_network)
56
57
  return {
57
58
  "address": address,
58
59
  "user_id": user_id,
@@ -61,7 +62,7 @@ def _user_wallet_metadata(user_id: str, address: str, network: str | None = None
61
62
 
62
63
 
63
64
  def _resolve_effective_network(network: str | None = None) -> str:
64
- return (network or settings.solana_network).strip().lower() or "mainnet"
65
+ return normalize_solana_network(network or settings.solana_network)
65
66
 
66
67
 
67
68
  def _resolve_user_wallet_master_key(
@@ -520,6 +520,9 @@ class AgentWalletBackend(ABC):
520
520
  async def get_kamino_lend_user_rewards(self, user: str | None = None) -> dict[str, Any]:
521
521
  raise WalletBackendError(f"{self.name} does not support Kamino rewards lookup.")
522
522
 
523
+ async def get_kamino_open_positions(self, user: str | None = None) -> dict[str, Any]:
524
+ raise WalletBackendError(f"{self.name} does not support Kamino open position lookup.")
525
+
523
526
  async def preview_kamino_lend_deposit(
524
527
  self,
525
528
  market: str,
@@ -994,9 +997,6 @@ class AgentWalletBackend(ABC):
994
997
  ),
995
998
  )
996
999
 
997
- async def request_testnet_airdrop(self, amount_native: float) -> dict[str, Any]:
998
- raise WalletBackendError(f"{self.name} does not support testnet airdrops.")
999
-
1000
1000
  async def preview_native_stake(
1001
1001
  self,
1002
1002
  vote_account: str,
@@ -7,6 +7,8 @@ from pathlib import Path
7
7
  from agent_wallet.bootstrap import ensure_solana_wallet_ready, ensure_wallet_pin
8
8
  from agent_wallet.encrypted_storage import load_wallet_secret_material
9
9
  from agent_wallet.config import (
10
+ normalize_btc_network,
11
+ normalize_solana_network,
10
12
  resolve_runtime_solana_rpc_config,
11
13
  resolve_runtime_solana_swap_config,
12
14
  resolve_solana_private_key,
@@ -44,6 +46,7 @@ def create_wallet_backend() -> AgentWalletBackend | None:
44
46
  return None
45
47
 
46
48
  if backend in {"solana", "solana_local", "solana-local"}:
49
+ solana_network = normalize_solana_network(settings.solana_network)
47
50
  secret_material = _load_keypair_material()
48
51
  signer = (
49
52
  SolanaLocalKeypairSigner.from_secret_material(secret_material)
@@ -55,19 +58,19 @@ def create_wallet_backend() -> AgentWalletBackend | None:
55
58
  ensure_wallet_pin(
56
59
  Path(keypair_path).expanduser(),
57
60
  address=signer.address,
58
- network=settings.solana_network,
61
+ network=solana_network,
59
62
  )
60
63
  configured_address = settings.solana_agent_public_key.strip() or None
61
64
  rpc_config = resolve_runtime_solana_rpc_config(
62
- settings.solana_network,
65
+ solana_network,
63
66
  settings.solana_rpc_url,
64
67
  settings.solana_rpc_urls,
65
68
  )
66
- swap_config = resolve_runtime_solana_swap_config(settings.solana_network)
69
+ swap_config = resolve_runtime_solana_swap_config(solana_network)
67
70
  return SolanaWalletBackend(
68
71
  rpc_url=rpc_config["rpc_urls"],
69
72
  commitment=settings.solana_commitment,
70
- network=settings.solana_network,
73
+ network=solana_network,
71
74
  signer=signer,
72
75
  address=configured_address,
73
76
  sign_only=settings.agent_wallet_sign_only,
@@ -82,7 +85,7 @@ def create_wallet_backend() -> AgentWalletBackend | None:
82
85
  return WdkBtcLocalWalletBackend(
83
86
  service_url=settings.wdk_btc_service_url,
84
87
  wallet_id=settings.wdk_btc_wallet_id,
85
- network=settings.solana_network,
88
+ network=normalize_btc_network(settings.solana_network),
86
89
  account_index=settings.wdk_btc_account_index,
87
90
  sign_only=settings.agent_wallet_sign_only,
88
91
  )