@agentlayer.tech/wallet 0.1.28 → 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 +29 -20
- package/.openclaw/extensions/agent-wallet/index.ts +29 -20
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +2 -2
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/CHANGELOG.md +38 -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 +28 -32
- 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 +4 -9
- 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 +2 -12
- 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
|
@@ -82,6 +82,79 @@ class Settings(BaseSettings):
|
|
|
82
82
|
settings = Settings()
|
|
83
83
|
|
|
84
84
|
|
|
85
|
+
def normalize_solana_network(network: str | None) -> str:
|
|
86
|
+
"""Canonicalize supported Solana network names and reject test clusters."""
|
|
87
|
+
normalized = str(network or "").strip().lower() or "mainnet"
|
|
88
|
+
aliases = {
|
|
89
|
+
"mainnet-beta": "mainnet",
|
|
90
|
+
}
|
|
91
|
+
normalized = aliases.get(normalized, normalized)
|
|
92
|
+
if normalized in {"devnet", "testnet"}:
|
|
93
|
+
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
94
|
+
|
|
95
|
+
raise WalletBackendError(
|
|
96
|
+
"Solana devnet/testnet are no longer supported by agent-wallet. "
|
|
97
|
+
"Use mainnet or remove the Solana network override."
|
|
98
|
+
)
|
|
99
|
+
if normalized != "mainnet":
|
|
100
|
+
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
101
|
+
|
|
102
|
+
raise WalletBackendError(
|
|
103
|
+
f"Unsupported Solana network: {normalized}. Only mainnet is supported."
|
|
104
|
+
)
|
|
105
|
+
return "mainnet"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def normalize_evm_network(network: str | None) -> str:
|
|
109
|
+
"""Canonicalize supported EVM network names and reject testnets."""
|
|
110
|
+
normalized = str(network or "").strip().lower() or "ethereum"
|
|
111
|
+
aliases = {
|
|
112
|
+
"mainnet": "ethereum",
|
|
113
|
+
"eth": "ethereum",
|
|
114
|
+
"eth-mainnet": "ethereum",
|
|
115
|
+
"base-mainnet": "base",
|
|
116
|
+
}
|
|
117
|
+
normalized = aliases.get(normalized, normalized)
|
|
118
|
+
if normalized in {"sepolia", "base-sepolia", "base_sepolia"}:
|
|
119
|
+
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
120
|
+
|
|
121
|
+
raise WalletBackendError(
|
|
122
|
+
"EVM testnets are no longer supported by agent-wallet. Use ethereum or base."
|
|
123
|
+
)
|
|
124
|
+
if normalized not in {"ethereum", "base"}:
|
|
125
|
+
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
126
|
+
|
|
127
|
+
raise WalletBackendError(
|
|
128
|
+
f"Unsupported EVM network: {normalized}. Use ethereum or base."
|
|
129
|
+
)
|
|
130
|
+
return normalized
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def normalize_btc_network(network: str | None) -> str:
|
|
134
|
+
"""Canonicalize supported BTC network names and reject non-mainnet chains."""
|
|
135
|
+
normalized = str(network or "").strip().lower() or "bitcoin"
|
|
136
|
+
aliases = {
|
|
137
|
+
"mainnet": "bitcoin",
|
|
138
|
+
"btc": "bitcoin",
|
|
139
|
+
"bitcoin-mainnet": "bitcoin",
|
|
140
|
+
"bitcoin_mainnet": "bitcoin",
|
|
141
|
+
}
|
|
142
|
+
normalized = aliases.get(normalized, normalized)
|
|
143
|
+
if normalized in {"testnet", "regtest"}:
|
|
144
|
+
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
145
|
+
|
|
146
|
+
raise WalletBackendError(
|
|
147
|
+
"Bitcoin testnet/regtest are no longer supported by agent-wallet. Use bitcoin."
|
|
148
|
+
)
|
|
149
|
+
if normalized != "bitcoin":
|
|
150
|
+
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
151
|
+
|
|
152
|
+
raise WalletBackendError(
|
|
153
|
+
f"Unsupported Bitcoin network: {normalized}. Only bitcoin is supported."
|
|
154
|
+
)
|
|
155
|
+
return "bitcoin"
|
|
156
|
+
|
|
157
|
+
|
|
85
158
|
def _normalize_provider_mode(value: str | None) -> str:
|
|
86
159
|
mode = (value or "").strip().lower()
|
|
87
160
|
if not mode:
|
|
@@ -129,20 +202,20 @@ def resolve_openclaw_home() -> Path:
|
|
|
129
202
|
|
|
130
203
|
def default_solana_wallet_path(network: str) -> Path:
|
|
131
204
|
"""Return the default keypair path for a Solana wallet."""
|
|
132
|
-
|
|
205
|
+
normalized_network = normalize_solana_network(network)
|
|
206
|
+
return resolve_openclaw_home() / "wallets" / f"solana-{normalized_network}-agent.json"
|
|
133
207
|
|
|
134
208
|
|
|
135
209
|
def resolve_solana_rpc_url(network: str, configured: str) -> str:
|
|
136
210
|
"""Resolve the effective Solana RPC URL from network + optional override."""
|
|
211
|
+
normalized_network = normalize_solana_network(network)
|
|
137
212
|
if configured.strip():
|
|
138
213
|
return configured.strip()
|
|
139
214
|
|
|
140
215
|
mapping = {
|
|
141
216
|
"mainnet": "https://api.mainnet-beta.solana.com",
|
|
142
|
-
"devnet": "https://api.devnet.solana.com",
|
|
143
|
-
"testnet": "https://api.testnet.solana.com",
|
|
144
217
|
}
|
|
145
|
-
return mapping.get(
|
|
218
|
+
return mapping.get(normalized_network, mapping["mainnet"])
|
|
146
219
|
|
|
147
220
|
|
|
148
221
|
def resolve_solana_rpc_urls(
|
|
@@ -151,17 +224,18 @@ def resolve_solana_rpc_urls(
|
|
|
151
224
|
configured_list: str = "",
|
|
152
225
|
) -> list[str]:
|
|
153
226
|
"""Resolve the ordered list of Solana RPC URLs to try."""
|
|
227
|
+
normalized_network = normalize_solana_network(network)
|
|
154
228
|
candidates: list[str] = []
|
|
155
229
|
for raw in (configured_list or "").split(","):
|
|
156
230
|
value = raw.strip()
|
|
157
231
|
if value and value not in candidates:
|
|
158
232
|
candidates.append(value)
|
|
159
233
|
|
|
160
|
-
primary = resolve_solana_rpc_url(
|
|
234
|
+
primary = resolve_solana_rpc_url(normalized_network, configured)
|
|
161
235
|
if primary and primary not in candidates:
|
|
162
236
|
candidates.insert(0, primary)
|
|
163
237
|
|
|
164
|
-
official = resolve_solana_rpc_url(
|
|
238
|
+
official = resolve_solana_rpc_url(normalized_network, "")
|
|
165
239
|
if official and official not in candidates:
|
|
166
240
|
candidates.append(official)
|
|
167
241
|
|
|
@@ -169,7 +243,8 @@ def resolve_solana_rpc_urls(
|
|
|
169
243
|
|
|
170
244
|
|
|
171
245
|
def _build_provider_gateway_rpc_url(base_url: str, provider: str, network: str) -> str:
|
|
172
|
-
|
|
246
|
+
normalized_network = normalize_solana_network(network)
|
|
247
|
+
return f"gateway::{provider}::{normalized_network}::{base_url.rstrip('/')}/v1/rpc"
|
|
173
248
|
|
|
174
249
|
|
|
175
250
|
def resolve_runtime_solana_rpc_config(
|
|
@@ -185,6 +260,7 @@ def resolve_runtime_solana_rpc_config(
|
|
|
185
260
|
3. shared proxy gateway
|
|
186
261
|
4. public official fallback
|
|
187
262
|
"""
|
|
263
|
+
normalized_network = normalize_solana_network(network)
|
|
188
264
|
mode = _normalize_provider_mode(
|
|
189
265
|
os.getenv("SOLANA_RPC_PROVIDER_MODE", settings.solana_rpc_provider_mode)
|
|
190
266
|
)
|
|
@@ -200,10 +276,10 @@ def resolve_runtime_solana_rpc_config(
|
|
|
200
276
|
"mode": "user_direct",
|
|
201
277
|
"provider": "custom",
|
|
202
278
|
"transport": "direct",
|
|
203
|
-
"rpc_urls": resolve_solana_rpc_urls(
|
|
279
|
+
"rpc_urls": resolve_solana_rpc_urls(normalized_network, env_primary, env_list),
|
|
204
280
|
}
|
|
205
281
|
if env_list:
|
|
206
|
-
official = resolve_solana_rpc_url(
|
|
282
|
+
official = resolve_solana_rpc_url(normalized_network, "")
|
|
207
283
|
candidates = [item.strip() for item in env_list.split(",") if item.strip()]
|
|
208
284
|
if official and official not in candidates:
|
|
209
285
|
candidates.append(official)
|
|
@@ -218,16 +294,15 @@ def resolve_runtime_solana_rpc_config(
|
|
|
218
294
|
if alchemy_key:
|
|
219
295
|
alchemy_base_by_network = {
|
|
220
296
|
"mainnet": "https://solana-mainnet.g.alchemy.com/v2",
|
|
221
|
-
"devnet": "https://solana-devnet.g.alchemy.com/v2",
|
|
222
297
|
}
|
|
223
|
-
alchemy_base = alchemy_base_by_network.get(
|
|
298
|
+
alchemy_base = alchemy_base_by_network.get(normalized_network)
|
|
224
299
|
if alchemy_base:
|
|
225
300
|
return {
|
|
226
301
|
"mode": "user_direct",
|
|
227
302
|
"provider": "alchemy",
|
|
228
303
|
"transport": "direct",
|
|
229
304
|
"rpc_urls": resolve_solana_rpc_urls(
|
|
230
|
-
|
|
305
|
+
normalized_network,
|
|
231
306
|
f"{alchemy_base}/{alchemy_key}",
|
|
232
307
|
"",
|
|
233
308
|
),
|
|
@@ -237,35 +312,40 @@ def resolve_runtime_solana_rpc_config(
|
|
|
237
312
|
if helius_key:
|
|
238
313
|
helius_base_by_network = {
|
|
239
314
|
"mainnet": "https://mainnet.helius-rpc.com/",
|
|
240
|
-
"devnet": "https://devnet.helius-rpc.com/",
|
|
241
315
|
}
|
|
242
|
-
helius_base = helius_base_by_network.get(
|
|
316
|
+
helius_base = helius_base_by_network.get(normalized_network)
|
|
243
317
|
if helius_base:
|
|
244
318
|
return {
|
|
245
319
|
"mode": "user_direct",
|
|
246
320
|
"provider": "helius",
|
|
247
321
|
"transport": "direct",
|
|
248
322
|
"rpc_urls": resolve_solana_rpc_urls(
|
|
249
|
-
|
|
323
|
+
normalized_network,
|
|
250
324
|
f"{helius_base}?api-key={helius_key}",
|
|
251
325
|
"",
|
|
252
326
|
),
|
|
253
327
|
}
|
|
254
328
|
|
|
255
|
-
if
|
|
329
|
+
if normalized_network == "mainnet" and (mode == "shared_proxy" or (mode == "auto" and gateway_url)):
|
|
256
330
|
if gateway_url:
|
|
257
331
|
return {
|
|
258
332
|
"mode": "shared_proxy",
|
|
259
333
|
"provider": gateway_provider,
|
|
260
334
|
"transport": "proxy",
|
|
261
|
-
"rpc_urls": [
|
|
335
|
+
"rpc_urls": [
|
|
336
|
+
_build_provider_gateway_rpc_url(
|
|
337
|
+
gateway_url,
|
|
338
|
+
gateway_provider,
|
|
339
|
+
normalized_network,
|
|
340
|
+
)
|
|
341
|
+
],
|
|
262
342
|
}
|
|
263
343
|
|
|
264
344
|
return {
|
|
265
345
|
"mode": "public_fallback",
|
|
266
346
|
"provider": "official",
|
|
267
347
|
"transport": "direct",
|
|
268
|
-
"rpc_urls": resolve_solana_rpc_urls(
|
|
348
|
+
"rpc_urls": resolve_solana_rpc_urls(normalized_network, configured, configured_list),
|
|
269
349
|
}
|
|
270
350
|
|
|
271
351
|
|
|
@@ -289,10 +369,7 @@ def resolve_runtime_solana_swap_config(network: str) -> dict[str, str]:
|
|
|
289
369
|
requested = _normalize_swap_provider(
|
|
290
370
|
os.getenv("SOLANA_SWAP_PROVIDER", settings.solana_swap_provider)
|
|
291
371
|
)
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if normalized_network != "mainnet":
|
|
295
|
-
return {"provider": "jupiter", "transport": "direct"}
|
|
372
|
+
normalize_solana_network(network)
|
|
296
373
|
|
|
297
374
|
if requested == "jupiter":
|
|
298
375
|
return {"provider": "jupiter", "transport": "direct"}
|
|
@@ -14,6 +14,7 @@ from urllib.parse import urlparse
|
|
|
14
14
|
from urllib.request import urlopen
|
|
15
15
|
|
|
16
16
|
from agent_wallet.config import (
|
|
17
|
+
normalize_evm_network,
|
|
17
18
|
resolve_boot_key,
|
|
18
19
|
resolve_evm_wallet_password,
|
|
19
20
|
resolve_openclaw_home,
|
|
@@ -27,18 +28,7 @@ LOCAL_WDK_EVM_HOSTS = {"127.0.0.1", "localhost", "::1"}
|
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
def _normalize_evm_network(value: str | None) -> str:
|
|
30
|
-
|
|
31
|
-
aliases = {
|
|
32
|
-
"mainnet": "ethereum",
|
|
33
|
-
"eth": "ethereum",
|
|
34
|
-
"eth-mainnet": "ethereum",
|
|
35
|
-
"base-mainnet": "base",
|
|
36
|
-
"base_sepolia": "base-sepolia",
|
|
37
|
-
}
|
|
38
|
-
network = aliases.get(network, network)
|
|
39
|
-
if network not in {"ethereum", "sepolia", "base", "base-sepolia"}:
|
|
40
|
-
return "ethereum"
|
|
41
|
-
return network
|
|
31
|
+
return normalize_evm_network(value)
|
|
42
32
|
|
|
43
33
|
|
|
44
34
|
def _resolve_service_url(service_url: str | None = None) -> str:
|
|
@@ -52,8 +42,6 @@ def _paired_network(network: str) -> str | None:
|
|
|
52
42
|
mapping = {
|
|
53
43
|
"ethereum": "base",
|
|
54
44
|
"base": "ethereum",
|
|
55
|
-
"sepolia": "base-sepolia",
|
|
56
|
-
"base-sepolia": "sepolia",
|
|
57
45
|
}
|
|
58
46
|
return mapping.get(_normalize_evm_network(network))
|
|
59
47
|
|
|
@@ -101,9 +101,10 @@ class OpenClawWalletAdapter:
|
|
|
101
101
|
"eth": "ethereum",
|
|
102
102
|
"eth-mainnet": "ethereum",
|
|
103
103
|
"base-mainnet": "base",
|
|
104
|
-
"base_sepolia": "base-sepolia",
|
|
105
104
|
}
|
|
106
105
|
network = aliases.get(network, network)
|
|
106
|
+
if network in {"sepolia", "base-sepolia", "base_sepolia"}:
|
|
107
|
+
raise WalletBackendError("EVM testnets are no longer supported. Use ethereum or base.")
|
|
107
108
|
if network not in {"ethereum", "base"}:
|
|
108
109
|
raise WalletBackendError("EVM network must be 'ethereum' or 'base'.")
|
|
109
110
|
return network
|
|
@@ -2314,6 +2315,25 @@ class OpenClawWalletAdapter:
|
|
|
2314
2315
|
read_only=True,
|
|
2315
2316
|
risk_level="low",
|
|
2316
2317
|
),
|
|
2318
|
+
AgentToolSpec(
|
|
2319
|
+
name="get_kamino_open_positions",
|
|
2320
|
+
description=(
|
|
2321
|
+
"Get all open Kamino lending positions for a Solana wallet on mainnet, "
|
|
2322
|
+
"aggregated across markets with loan details, reserve APYs, and rewards."
|
|
2323
|
+
),
|
|
2324
|
+
input_schema={
|
|
2325
|
+
"type": "object",
|
|
2326
|
+
"properties": {
|
|
2327
|
+
"user": {
|
|
2328
|
+
"type": "string",
|
|
2329
|
+
"description": "Optional Solana wallet address override. If omitted, use the configured wallet.",
|
|
2330
|
+
},
|
|
2331
|
+
},
|
|
2332
|
+
"additionalProperties": False,
|
|
2333
|
+
},
|
|
2334
|
+
read_only=True,
|
|
2335
|
+
risk_level="low",
|
|
2336
|
+
),
|
|
2317
2337
|
]
|
|
2318
2338
|
|
|
2319
2339
|
if capabilities.can_sign_message:
|
|
@@ -3221,30 +3241,6 @@ class OpenClawWalletAdapter:
|
|
|
3221
3241
|
)
|
|
3222
3242
|
)
|
|
3223
3243
|
|
|
3224
|
-
tools.append(
|
|
3225
|
-
AgentToolSpec(
|
|
3226
|
-
name="request_devnet_airdrop",
|
|
3227
|
-
description=(
|
|
3228
|
-
"Request SOL from the Solana faucet on devnet or testnet. "
|
|
3229
|
-
"Only available outside mainnet."
|
|
3230
|
-
),
|
|
3231
|
-
input_schema={
|
|
3232
|
-
"type": "object",
|
|
3233
|
-
"properties": {
|
|
3234
|
-
"amount": {
|
|
3235
|
-
"type": "number",
|
|
3236
|
-
"description": "Amount of SOL to request from faucet.",
|
|
3237
|
-
}
|
|
3238
|
-
},
|
|
3239
|
-
"required": ["amount"],
|
|
3240
|
-
"additionalProperties": False,
|
|
3241
|
-
},
|
|
3242
|
-
read_only=False,
|
|
3243
|
-
requires_explicit_user_intent=True,
|
|
3244
|
-
risk_level="low",
|
|
3245
|
-
)
|
|
3246
|
-
)
|
|
3247
|
-
|
|
3248
3244
|
tools.extend(self._x402_tool_specs())
|
|
3249
3245
|
return [tool for tool in tools if tool.name not in TEMPORARILY_DISABLED_TOOLS]
|
|
3250
3246
|
|
|
@@ -4637,6 +4633,13 @@ class OpenClawWalletAdapter:
|
|
|
4637
4633
|
data = await self.backend.get_kamino_lend_user_rewards(user=user)
|
|
4638
4634
|
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
4639
4635
|
|
|
4636
|
+
if tool_name == "get_kamino_open_positions":
|
|
4637
|
+
user = args.get("user")
|
|
4638
|
+
if user is not None and not isinstance(user, str):
|
|
4639
|
+
raise WalletBackendError("user must be a string when provided.")
|
|
4640
|
+
data = await self.backend.get_kamino_open_positions(user=user)
|
|
4641
|
+
return AgentToolResult(tool=tool_name, ok=True, data=data)
|
|
4642
|
+
|
|
4640
4643
|
if tool_name == "sign_wallet_message":
|
|
4641
4644
|
user_confirmed = args.get("user_confirmed")
|
|
4642
4645
|
if user_confirmed is not True:
|
|
@@ -5199,13 +5202,6 @@ class OpenClawWalletAdapter:
|
|
|
5199
5202
|
),
|
|
5200
5203
|
)
|
|
5201
5204
|
|
|
5202
|
-
if tool_name == "request_devnet_airdrop":
|
|
5203
|
-
amount = args.get("amount")
|
|
5204
|
-
if not isinstance(amount, (int, float)) or amount <= 0:
|
|
5205
|
-
raise WalletBackendError("amount must be a positive number.")
|
|
5206
|
-
result = await self.backend.request_testnet_airdrop(float(amount))
|
|
5207
|
-
return AgentToolResult(tool=tool_name, ok=True, data=result)
|
|
5208
|
-
|
|
5209
5205
|
if tool_name == "transfer_spl_token":
|
|
5210
5206
|
recipient = args.get("recipient")
|
|
5211
5207
|
mint = args.get("mint")
|
|
@@ -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,
|
|
@@ -9,7 +9,7 @@ import logging
|
|
|
9
9
|
from typing import Any
|
|
10
10
|
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
|
11
11
|
|
|
12
|
-
from agent_wallet.config import resolve_solana_rpc_url
|
|
12
|
+
from agent_wallet.config import normalize_solana_network, resolve_solana_rpc_url
|
|
13
13
|
from agent_wallet.exceptions import ProviderError
|
|
14
14
|
from agent_wallet.http_client import get_client
|
|
15
15
|
from agent_wallet.wallet_layer.base import AgentWalletBackend
|
|
@@ -19,13 +19,10 @@ AGENTIC_MARKET_API_BASE_URL = "https://api.agentic.market/v1"
|
|
|
19
19
|
X402_EXECUTE_TIMEOUT_SECONDS = 45.0
|
|
20
20
|
SOLANA_CAIP_BY_NETWORK = {
|
|
21
21
|
"mainnet": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
22
|
-
"devnet": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
23
22
|
}
|
|
24
23
|
EVM_CAIP_BY_NETWORK = {
|
|
25
24
|
"ethereum": "eip155:1",
|
|
26
25
|
"base": "eip155:8453",
|
|
27
|
-
"sepolia": "eip155:11155111",
|
|
28
|
-
"base-sepolia": "eip155:84532",
|
|
29
26
|
}
|
|
30
27
|
_USDC_IDENTIFIERS = {
|
|
31
28
|
"usdc",
|
|
@@ -63,7 +60,7 @@ def _backend_solana_sdk_rpc_url(backend: AgentWalletBackend) -> str | None:
|
|
|
63
60
|
if primary.startswith(("http://", "https://")):
|
|
64
61
|
return primary
|
|
65
62
|
network = _backend_network(backend)
|
|
66
|
-
fallback = resolve_solana_rpc_url(network or "mainnet", "")
|
|
63
|
+
fallback = resolve_solana_rpc_url(normalize_solana_network(network or "mainnet"), "")
|
|
67
64
|
return _trim(fallback) or None
|
|
68
65
|
|
|
69
66
|
|
|
@@ -361,7 +358,7 @@ def _wallet_caip_networks(backend: AgentWalletBackend) -> list[str]:
|
|
|
361
358
|
def _solana_exact_execution_supported(backend: AgentWalletBackend) -> bool:
|
|
362
359
|
return (
|
|
363
360
|
_backend_chain(backend) == "solana"
|
|
364
|
-
and _backend_network(backend)
|
|
361
|
+
and _backend_network(backend) == "mainnet"
|
|
365
362
|
and getattr(backend, "signer", None) is not None
|
|
366
363
|
)
|
|
367
364
|
|
|
@@ -369,7 +366,7 @@ def _solana_exact_execution_supported(backend: AgentWalletBackend) -> bool:
|
|
|
369
366
|
def _evm_exact_execution_supported(backend: AgentWalletBackend) -> bool:
|
|
370
367
|
return (
|
|
371
368
|
_backend_chain(backend) == "evm"
|
|
372
|
-
and _backend_network(backend)
|
|
369
|
+
and _backend_network(backend) == "base"
|
|
373
370
|
and callable(getattr(backend, "sign_x402_evm_exact_typed_data", None))
|
|
374
371
|
)
|
|
375
372
|
|
|
@@ -388,9 +385,7 @@ def _wallet_x402_support_summary(backend: AgentWalletBackend) -> dict[str, Any]:
|
|
|
388
385
|
supported_networks = _wallet_caip_networks(backend)
|
|
389
386
|
planned_execution_networks = {
|
|
390
387
|
"eip155:8453",
|
|
391
|
-
"eip155:84532",
|
|
392
388
|
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
393
|
-
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
|
|
394
389
|
}
|
|
395
390
|
execution_modes: list[str] = []
|
|
396
391
|
if _solana_exact_execution_supported(backend):
|
|
@@ -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
|
)
|