@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.
- package/.openclaw/extensions/agent-wallet/README.md +4 -5
- package/.openclaw/extensions/agent-wallet/dist/index.js +31 -31
- package/.openclaw/extensions/agent-wallet/index.ts +31 -31
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +2 -2
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +52 -0
- package/README.md +9 -0
- package/agent-wallet/README.md +18 -22
- package/agent-wallet/agent_wallet/bootstrap.py +28 -12
- package/agent-wallet/agent_wallet/btc_user_wallets.py +2 -7
- package/agent-wallet/agent_wallet/config.py +99 -22
- package/agent-wallet/agent_wallet/evm_user_wallets.py +2 -14
- package/agent-wallet/agent_wallet/openclaw_adapter.py +72 -108
- package/agent-wallet/agent_wallet/openclaw_runtime.py +3 -12
- package/agent-wallet/agent_wallet/providers/kamino.py +21 -4
- package/agent-wallet/agent_wallet/providers/solana_rpc.py +0 -23
- package/agent-wallet/agent_wallet/providers/x402.py +198 -18
- package/agent-wallet/agent_wallet/user_wallets.py +4 -3
- package/agent-wallet/agent_wallet/wallet_layer/base.py +3 -3
- package/agent-wallet/agent_wallet/wallet_layer/factory.py +8 -5
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +437 -44
- package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +2 -8
- package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +13 -13
- package/agent-wallet/examples/openclaw_runtime_onboarding.py +1 -1
- package/agent-wallet/examples/openclaw_user_wallet_example.py +1 -1
- package/agent-wallet/openclaw.plugin.json +1 -1
- package/agent-wallet/pyproject.toml +2 -1
- package/agent-wallet/scripts/bootstrap_openclaw_btc.py +3 -5
- package/agent-wallet/scripts/bootstrap_openclaw_evm.py +2 -12
- package/agent-wallet/scripts/build_release_bundle.py +1 -0
- package/agent-wallet/scripts/flash-sdk-bridge/bridge.mjs +1 -4
- package/agent-wallet/scripts/install_agent_wallet.py +1 -0
- package/agent-wallet/scripts/install_openclaw_local_config.py +4 -6
- package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +2 -4
- package/agent-wallet/scripts/manage_openclaw_evm_wallet.py +2 -15
- package/agent-wallet/scripts/reveal_btc_seed.sh +7 -16
- package/agent-wallet/scripts/setup_btc_wallet.sh +7 -16
- package/agent-wallet/scripts/setup_evm_wallet.sh +1 -11
- package/agent-wallet/scripts/switch_openclaw_wallet_network.py +4 -1
- package/agent-wallet/skills/wallet-operator/SKILL.md +0 -1
- package/bin/openclaw-agent-wallet.mjs +289 -0
- package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +20 -0
- package/claude-code/plugins/agent-wallet/.mcp.json +14 -0
- package/claude-code/plugins/agent-wallet/README.md +65 -0
- package/claude-code/plugins/agent-wallet/scripts/run_mcp.sh +34 -0
- package/claude-code/plugins/agent-wallet/skills/wallet-operator/SKILL.md +18 -0
- package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +38 -0
- package/codex/plugins/agent-wallet/.mcp.json +15 -0
- package/codex/plugins/agent-wallet/README.md +39 -0
- package/codex/plugins/agent-wallet/scripts/run_mcp.sh +21 -0
- package/codex/plugins/agent-wallet/server.py +1077 -0
- package/codex/plugins/agent-wallet/skills/wallet-operator/SKILL.md +18 -0
- package/hermes/plugins/agent_wallet/schemas.py +2 -2
- package/hermes/plugins/agent_wallet/tools.py +17 -3
- package/package.json +6 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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)
|
|
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]
|
|
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
|
-
|
|
947
|
-
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
1294
|
-
|
|
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)
|
|
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)
|
|
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)
|
|
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=
|
|
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
|
-
|
|
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(
|
|
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=
|
|
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
|
)
|