@agentlayer.tech/wallet 0.1.30 → 0.1.32
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 +1 -2
- package/.openclaw/extensions/agent-wallet/dist/index.js +6 -340
- package/.openclaw/extensions/agent-wallet/index.ts +6 -340
- package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +0 -43
- package/.openclaw/extensions/agent-wallet/package.json +1 -1
- package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +1 -3
- package/CHANGELOG.md +35 -0
- package/README.md +0 -5
- package/agent-wallet/.env.example +0 -12
- package/agent-wallet/README.md +0 -35
- package/agent-wallet/agent_wallet/btc_user_wallets.py +32 -1
- package/agent-wallet/agent_wallet/config.py +11 -7
- package/agent-wallet/agent_wallet/evm_user_wallets.py +2 -0
- package/agent-wallet/agent_wallet/openclaw_adapter.py +1 -655
- package/agent-wallet/agent_wallet/openclaw_cli.py +0 -7
- package/agent-wallet/agent_wallet/providers/evm_portfolio.py +18 -42
- package/agent-wallet/agent_wallet/providers/jupiter.py +1 -307
- package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +31 -3
- package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +37 -3
- package/agent-wallet/agent_wallet/transaction_policy.py +0 -262
- package/agent-wallet/agent_wallet/wallet_layer/base.py +0 -100
- package/agent-wallet/agent_wallet/wallet_layer/solana.py +1 -1118
- package/agent-wallet/openclaw.plugin.json +0 -4
- package/agent-wallet/pyproject.toml +1 -1
- package/agent-wallet/scripts/install_agent_wallet.py +113 -6
- package/agent-wallet/scripts/install_openclaw_local_config.py +7 -5
- package/agent-wallet/skills/wallet-operator/SKILL.md +1 -5
- package/bin/openclaw-agent-wallet.mjs +103 -36
- package/claude-code/plugins/agent-wallet/scripts/run_mcp.sh +7 -2
- package/codex/plugins/agent-wallet/server.py +2 -118
- package/hermes/plugins/agent_wallet/tools.py +1 -1
- package/package.json +1 -1
- package/wdk-btc-wallet/src/local_vault.js +45 -68
- package/wdk-btc-wallet/src/server.js +1 -0
- package/wdk-evm-wallet/README.md +4 -3
- package/wdk-evm-wallet/src/config.js +15 -0
- package/wdk-evm-wallet/src/local_vault.js +45 -68
- package/wdk-evm-wallet/src/server.js +1 -0
- package/agent-wallet/agent_wallet/providers/houdini.py +0 -539
|
@@ -101,14 +101,7 @@ def _apply_config_overrides(config: dict[str, Any]) -> None:
|
|
|
101
101
|
config.get("jupiterPortfolioBaseUrl"),
|
|
102
102
|
True,
|
|
103
103
|
),
|
|
104
|
-
"jupiterLendBaseUrl": ("JUPITER_LEND_API_BASE_URL", config.get("jupiterLendBaseUrl"), True),
|
|
105
104
|
"jupiterApiKey": ("JUPITER_API_KEY", config.get("jupiterApiKey"), True),
|
|
106
|
-
"houdiniBaseUrl": ("HOUDINI_API_BASE_URL", config.get("houdiniBaseUrl"), True),
|
|
107
|
-
"houdiniApiKey": ("HOUDINI_API_KEY", config.get("houdiniApiKey"), True),
|
|
108
|
-
"houdiniApiSecret": ("HOUDINI_API_SECRET", config.get("houdiniApiSecret"), True),
|
|
109
|
-
"houdiniUserIp": ("HOUDINI_USER_IP", config.get("houdiniUserIp"), True),
|
|
110
|
-
"houdiniUserAgent": ("HOUDINI_USER_AGENT", config.get("houdiniUserAgent"), True),
|
|
111
|
-
"houdiniUserTimezone": ("HOUDINI_USER_TIMEZONE", config.get("houdiniUserTimezone"), True),
|
|
112
105
|
"kaminoBaseUrl": ("KAMINO_API_BASE_URL", config.get("kaminoBaseUrl"), True),
|
|
113
106
|
"kaminoProgramId": ("KAMINO_PROGRAM_ID", config.get("kaminoProgramId"), True),
|
|
114
107
|
}
|
|
@@ -14,11 +14,6 @@ COINGECKO_API_URL = "https://api.coingecko.com/api/v3"
|
|
|
14
14
|
PORTFOLIO_TOKEN_CACHE_TTL_SECONDS = 30.0
|
|
15
15
|
PORTFOLIO_PRICE_CACHE_TTL_SECONDS = 60.0
|
|
16
16
|
|
|
17
|
-
ALCHEMY_RPC_URLS = {
|
|
18
|
-
"ethereum": "https://eth-mainnet.g.alchemy.com/v2",
|
|
19
|
-
"base": "https://base-mainnet.g.alchemy.com/v2",
|
|
20
|
-
}
|
|
21
|
-
|
|
22
17
|
TOKEN_METADATA: dict[str, dict[str, dict[str, Any]]] = {
|
|
23
18
|
"ethereum": {
|
|
24
19
|
"0xdac17f958d2ee523a2206206994597c13d831ec7": {
|
|
@@ -204,51 +199,32 @@ def _to_decimal(value: Any) -> Decimal | None:
|
|
|
204
199
|
return None
|
|
205
200
|
|
|
206
201
|
|
|
207
|
-
async def
|
|
202
|
+
async def _gateway_rpc_call(network: str, method: str, params: list[Any]) -> dict[str, Any]:
|
|
208
203
|
client = get_client()
|
|
209
204
|
payload = {"jsonrpc": "2.0", "id": 1, "method": method, "params": params}
|
|
210
205
|
gateway_url = str(settings.provider_gateway_url or "").strip()
|
|
211
206
|
bearer = str(settings.provider_gateway_bearer_token or "").strip()
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
f"{gateway_url.rstrip('/')}/v1/evm/rpc/{network}?provider=alchemy",
|
|
218
|
-
json=payload,
|
|
219
|
-
headers={"Authorization": f"Bearer {bearer}"} if bearer else None,
|
|
220
|
-
)
|
|
221
|
-
if response.status_code == 200:
|
|
222
|
-
data = response.json()
|
|
223
|
-
if isinstance(data, dict) and "error" not in data:
|
|
224
|
-
return data
|
|
225
|
-
gateway_error = ProviderError("evm-portfolio", f"Gateway RPC error: {data}")
|
|
226
|
-
else:
|
|
227
|
-
gateway_error = ProviderError(
|
|
228
|
-
"evm-portfolio",
|
|
229
|
-
f"Gateway returned HTTP {response.status_code} for {method}",
|
|
230
|
-
)
|
|
231
|
-
except Exception as exc: # pragma: no cover - network path
|
|
232
|
-
gateway_error = exc
|
|
233
|
-
|
|
234
|
-
alchemy_key = str(settings.alchemy_api_key or "").strip()
|
|
235
|
-
if not alchemy_key:
|
|
236
|
-
if gateway_error is not None:
|
|
237
|
-
raise ProviderError("evm-portfolio", f"No fallback Alchemy key available after gateway failure: {gateway_error}")
|
|
238
|
-
raise ProviderError("evm-portfolio", "No gateway URL or Alchemy API key configured for EVM portfolio lookup.")
|
|
239
|
-
|
|
240
|
-
base_url = ALCHEMY_RPC_URLS.get(network)
|
|
241
|
-
if not base_url:
|
|
242
|
-
raise ProviderError("evm-portfolio", f"No Alchemy RPC URL configured for {network}.")
|
|
207
|
+
if not gateway_url:
|
|
208
|
+
raise ProviderError(
|
|
209
|
+
"evm-portfolio",
|
|
210
|
+
"Provider gateway URL is required for EVM portfolio lookup on ethereum/base.",
|
|
211
|
+
)
|
|
243
212
|
try:
|
|
244
|
-
response = await client.post(
|
|
213
|
+
response = await client.post(
|
|
214
|
+
f"{gateway_url.rstrip('/')}/v1/evm/rpc/{network}?provider=alchemy",
|
|
215
|
+
json=payload,
|
|
216
|
+
headers={"Authorization": f"Bearer {bearer}"} if bearer else None,
|
|
217
|
+
)
|
|
245
218
|
except Exception as exc: # pragma: no cover - network path
|
|
246
|
-
raise ProviderError("evm-portfolio", f"
|
|
219
|
+
raise ProviderError("evm-portfolio", f"Provider gateway request failed: {exc}") from exc
|
|
247
220
|
if response.status_code != 200:
|
|
248
|
-
raise ProviderError(
|
|
221
|
+
raise ProviderError(
|
|
222
|
+
"evm-portfolio",
|
|
223
|
+
f"Provider gateway returned HTTP {response.status_code} for {method}.",
|
|
224
|
+
)
|
|
249
225
|
data = response.json()
|
|
250
226
|
if "error" in data:
|
|
251
|
-
raise ProviderError("evm-portfolio", f"
|
|
227
|
+
raise ProviderError("evm-portfolio", f"Provider gateway RPC error: {data['error']}")
|
|
252
228
|
return data
|
|
253
229
|
|
|
254
230
|
|
|
@@ -259,7 +235,7 @@ async def fetch_token_balances(address: str, network: str) -> list[dict[str, Any
|
|
|
259
235
|
if cached is not None:
|
|
260
236
|
return cached
|
|
261
237
|
|
|
262
|
-
data = await
|
|
238
|
+
data = await _gateway_rpc_call(
|
|
263
239
|
normalized_network,
|
|
264
240
|
"alchemy_getTokenBalances",
|
|
265
241
|
[address, "erc20"],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Jupiter providers for swap routing, prices,
|
|
1
|
+
"""Jupiter providers for swap routing, prices, and portfolio flows."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -20,14 +20,6 @@ def _headers() -> dict[str, str]:
|
|
|
20
20
|
return headers
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def _require_api_key(provider_name: str) -> None:
|
|
24
|
-
if not settings.jupiter_api_key.strip():
|
|
25
|
-
raise ProviderError(
|
|
26
|
-
provider_name,
|
|
27
|
-
"Jupiter API key is required for this endpoint. Set JUPITER_API_KEY first.",
|
|
28
|
-
)
|
|
29
|
-
|
|
30
|
-
|
|
31
23
|
def _gateway_base_url() -> str:
|
|
32
24
|
return os.getenv("PROVIDER_GATEWAY_URL", settings.provider_gateway_url).strip().rstrip("/")
|
|
33
25
|
|
|
@@ -47,16 +39,6 @@ def _gateway_enabled() -> bool:
|
|
|
47
39
|
return bool(_gateway_base_url())
|
|
48
40
|
|
|
49
41
|
|
|
50
|
-
def _gateway_route_missing(status_code: int, payload: Any) -> bool:
|
|
51
|
-
if status_code == 404:
|
|
52
|
-
return True
|
|
53
|
-
if isinstance(payload, dict):
|
|
54
|
-
message = str(payload.get("error") or "").lower()
|
|
55
|
-
if "not found" in message:
|
|
56
|
-
return True
|
|
57
|
-
return False
|
|
58
|
-
|
|
59
|
-
|
|
60
42
|
async def _gateway_get(path_suffix: str, *, params: dict[str, Any] | None = None) -> tuple[int, Any]:
|
|
61
43
|
"""Make a GET request through provider gateway."""
|
|
62
44
|
client = get_client()
|
|
@@ -126,125 +108,6 @@ def _swap_v2_base_url() -> str:
|
|
|
126
108
|
).strip().rstrip("/")
|
|
127
109
|
|
|
128
110
|
|
|
129
|
-
def _unwrap_gateway_payload(
|
|
130
|
-
status_code: int,
|
|
131
|
-
payload: Any,
|
|
132
|
-
*,
|
|
133
|
-
operation: str,
|
|
134
|
-
) -> Any:
|
|
135
|
-
if isinstance(payload, dict) and payload.get("ok") is False:
|
|
136
|
-
message = str(payload.get("error") or f"{operation} failed.")
|
|
137
|
-
raise ProviderError("jupiter-lend", f"{operation} failed via provider gateway: {message}")
|
|
138
|
-
|
|
139
|
-
if status_code != 200:
|
|
140
|
-
message = payload
|
|
141
|
-
if isinstance(payload, dict):
|
|
142
|
-
message = payload.get("error") or payload
|
|
143
|
-
raise ProviderError("jupiter-lend", f"{operation} failed via provider gateway: {message}")
|
|
144
|
-
|
|
145
|
-
return payload
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
async def _gateway_get_json(
|
|
149
|
-
path: str,
|
|
150
|
-
*,
|
|
151
|
-
params: dict[str, Any] | None,
|
|
152
|
-
operation: str,
|
|
153
|
-
) -> Any:
|
|
154
|
-
client = get_client()
|
|
155
|
-
response = await client.get(
|
|
156
|
-
f"{_gateway_base_url()}{path}",
|
|
157
|
-
params=params,
|
|
158
|
-
headers=_gateway_headers(),
|
|
159
|
-
)
|
|
160
|
-
payload = response.json() if response.content else {}
|
|
161
|
-
return _unwrap_gateway_payload(
|
|
162
|
-
response.status_code,
|
|
163
|
-
payload,
|
|
164
|
-
operation=operation,
|
|
165
|
-
)
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
async def _gateway_post_json(
|
|
169
|
-
path: str,
|
|
170
|
-
*,
|
|
171
|
-
body: dict[str, Any],
|
|
172
|
-
operation: str,
|
|
173
|
-
) -> Any:
|
|
174
|
-
client = get_client()
|
|
175
|
-
response = await client.post(
|
|
176
|
-
f"{_gateway_base_url()}{path}",
|
|
177
|
-
json=body,
|
|
178
|
-
headers={**_gateway_headers(), "Content-Type": "application/json"},
|
|
179
|
-
)
|
|
180
|
-
payload = response.json() if response.content else {}
|
|
181
|
-
return _unwrap_gateway_payload(
|
|
182
|
-
response.status_code,
|
|
183
|
-
payload,
|
|
184
|
-
operation=operation,
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
async def _earn_get_with_gateway_fallback(
|
|
189
|
-
*,
|
|
190
|
-
path: str,
|
|
191
|
-
params: dict[str, Any] | None,
|
|
192
|
-
operation: str,
|
|
193
|
-
) -> Any:
|
|
194
|
-
if _gateway_enabled():
|
|
195
|
-
client = get_client()
|
|
196
|
-
response = await client.get(
|
|
197
|
-
f"{_gateway_base_url()}{path}",
|
|
198
|
-
params=params,
|
|
199
|
-
headers=_gateway_headers(),
|
|
200
|
-
)
|
|
201
|
-
payload = response.json() if response.content else {}
|
|
202
|
-
if _gateway_route_missing(response.status_code, payload) and _direct_jupiter_enabled():
|
|
203
|
-
return None
|
|
204
|
-
return _unwrap_gateway_payload(response.status_code, payload, operation=operation)
|
|
205
|
-
return None
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
async def _earn_post_with_gateway_fallback(
|
|
209
|
-
*,
|
|
210
|
-
path: str,
|
|
211
|
-
body: dict[str, Any],
|
|
212
|
-
operation: str,
|
|
213
|
-
) -> Any:
|
|
214
|
-
if _gateway_enabled():
|
|
215
|
-
client = get_client()
|
|
216
|
-
response = await client.post(
|
|
217
|
-
f"{_gateway_base_url()}{path}",
|
|
218
|
-
json=body,
|
|
219
|
-
headers={**_gateway_headers(), "Content-Type": "application/json"},
|
|
220
|
-
)
|
|
221
|
-
payload = response.json() if response.content else {}
|
|
222
|
-
if _gateway_route_missing(response.status_code, payload) and _direct_jupiter_enabled():
|
|
223
|
-
return None
|
|
224
|
-
return _unwrap_gateway_payload(response.status_code, payload, operation=operation)
|
|
225
|
-
return None
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
def _normalize_named_list_response(
|
|
229
|
-
data: Any,
|
|
230
|
-
*,
|
|
231
|
-
key: str,
|
|
232
|
-
provider_name: str,
|
|
233
|
-
) -> dict[str, Any]:
|
|
234
|
-
if isinstance(data, list):
|
|
235
|
-
return {key: data}
|
|
236
|
-
if isinstance(data, dict):
|
|
237
|
-
items = data.get(key)
|
|
238
|
-
if isinstance(items, list):
|
|
239
|
-
return data
|
|
240
|
-
fallback = data.get("data")
|
|
241
|
-
if isinstance(fallback, list):
|
|
242
|
-
normalized = dict(data)
|
|
243
|
-
normalized[key] = fallback
|
|
244
|
-
return normalized
|
|
245
|
-
raise ProviderError(provider_name, f"Unexpected {key} response from Jupiter.")
|
|
246
|
-
|
|
247
|
-
|
|
248
111
|
async def fetch_quote(
|
|
249
112
|
*,
|
|
250
113
|
input_mint: str,
|
|
@@ -678,172 +541,3 @@ async def fetch_staked_jup(*, address: str) -> dict[str, Any]:
|
|
|
678
541
|
if not isinstance(data, dict):
|
|
679
542
|
raise ProviderError("jupiter-portfolio", "Unexpected staked JUP response.")
|
|
680
543
|
return data
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
async def fetch_earn_tokens() -> dict[str, Any]:
|
|
684
|
-
"""Fetch supported Jupiter Earn vault tokens."""
|
|
685
|
-
gateway_response = await _earn_get_with_gateway_fallback(
|
|
686
|
-
path="/v1/jupiter/earn/tokens",
|
|
687
|
-
params=None,
|
|
688
|
-
operation="Jupiter Earn tokens",
|
|
689
|
-
)
|
|
690
|
-
if gateway_response is not None:
|
|
691
|
-
return _normalize_named_list_response(
|
|
692
|
-
gateway_response,
|
|
693
|
-
key="tokens",
|
|
694
|
-
provider_name="jupiter-lend",
|
|
695
|
-
)
|
|
696
|
-
|
|
697
|
-
_require_api_key("jupiter-lend")
|
|
698
|
-
client = get_client()
|
|
699
|
-
response = await client.get(
|
|
700
|
-
f"{settings.jupiter_lend_api_base_url.rstrip('/')}/earn/tokens",
|
|
701
|
-
headers=_headers(),
|
|
702
|
-
)
|
|
703
|
-
if response.status_code != 200:
|
|
704
|
-
raise ProviderError("jupiter-lend", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
705
|
-
return _normalize_named_list_response(
|
|
706
|
-
response.json(),
|
|
707
|
-
key="tokens",
|
|
708
|
-
provider_name="jupiter-lend",
|
|
709
|
-
)
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
async def fetch_earn_positions(*, users: list[str]) -> dict[str, Any]:
|
|
713
|
-
"""Fetch Jupiter Earn positions for one or more users."""
|
|
714
|
-
gateway_response = await _earn_get_with_gateway_fallback(
|
|
715
|
-
path="/v1/jupiter/earn/positions",
|
|
716
|
-
params={"users": ",".join(users)},
|
|
717
|
-
operation="Jupiter Earn positions",
|
|
718
|
-
)
|
|
719
|
-
if gateway_response is not None:
|
|
720
|
-
return _normalize_named_list_response(
|
|
721
|
-
gateway_response,
|
|
722
|
-
key="positions",
|
|
723
|
-
provider_name="jupiter-lend",
|
|
724
|
-
)
|
|
725
|
-
|
|
726
|
-
_require_api_key("jupiter-lend")
|
|
727
|
-
client = get_client()
|
|
728
|
-
response = await client.get(
|
|
729
|
-
f"{settings.jupiter_lend_api_base_url.rstrip('/')}/earn/positions",
|
|
730
|
-
params={"users": ",".join(users)},
|
|
731
|
-
headers=_headers(),
|
|
732
|
-
)
|
|
733
|
-
if response.status_code != 200:
|
|
734
|
-
raise ProviderError("jupiter-lend", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
735
|
-
return _normalize_named_list_response(
|
|
736
|
-
response.json(),
|
|
737
|
-
key="positions",
|
|
738
|
-
provider_name="jupiter-lend",
|
|
739
|
-
)
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
async def fetch_earn_earnings(*, user: str, positions: list[str]) -> dict[str, Any]:
|
|
743
|
-
"""Fetch Jupiter Earn earnings for a user and position list."""
|
|
744
|
-
gateway_response = await _earn_get_with_gateway_fallback(
|
|
745
|
-
path="/v1/jupiter/earn/earnings",
|
|
746
|
-
params={"user": user, "positions": ",".join(positions)},
|
|
747
|
-
operation="Jupiter Earn earnings",
|
|
748
|
-
)
|
|
749
|
-
if gateway_response is not None:
|
|
750
|
-
return _normalize_named_list_response(
|
|
751
|
-
gateway_response,
|
|
752
|
-
key="earnings",
|
|
753
|
-
provider_name="jupiter-lend",
|
|
754
|
-
)
|
|
755
|
-
|
|
756
|
-
_require_api_key("jupiter-lend")
|
|
757
|
-
client = get_client()
|
|
758
|
-
response = await client.get(
|
|
759
|
-
f"{settings.jupiter_lend_api_base_url.rstrip('/')}/earn/earnings",
|
|
760
|
-
params={"user": user, "positions": ",".join(positions)},
|
|
761
|
-
headers=_headers(),
|
|
762
|
-
)
|
|
763
|
-
if response.status_code != 200:
|
|
764
|
-
raise ProviderError("jupiter-lend", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
765
|
-
return _normalize_named_list_response(
|
|
766
|
-
response.json(),
|
|
767
|
-
key="earnings",
|
|
768
|
-
provider_name="jupiter-lend",
|
|
769
|
-
)
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
async def build_earn_deposit_transaction(
|
|
773
|
-
*,
|
|
774
|
-
asset: str,
|
|
775
|
-
user_address: str,
|
|
776
|
-
amount_raw: str,
|
|
777
|
-
) -> dict[str, Any]:
|
|
778
|
-
"""Build an unsigned Jupiter Earn deposit transaction."""
|
|
779
|
-
gateway_response = await _earn_post_with_gateway_fallback(
|
|
780
|
-
path="/v1/jupiter/earn/deposit",
|
|
781
|
-
body={
|
|
782
|
-
"asset": asset,
|
|
783
|
-
"signer": user_address,
|
|
784
|
-
"amount": amount_raw,
|
|
785
|
-
},
|
|
786
|
-
operation="Jupiter Earn deposit",
|
|
787
|
-
)
|
|
788
|
-
if gateway_response is not None:
|
|
789
|
-
if not isinstance(gateway_response, dict) or "transaction" not in gateway_response:
|
|
790
|
-
raise ProviderError("jupiter-lend", "Unexpected Earn deposit response.")
|
|
791
|
-
return gateway_response
|
|
792
|
-
|
|
793
|
-
_require_api_key("jupiter-lend")
|
|
794
|
-
client = get_client()
|
|
795
|
-
response = await client.post(
|
|
796
|
-
f"{settings.jupiter_lend_api_base_url.rstrip('/')}/earn/deposit",
|
|
797
|
-
json={
|
|
798
|
-
"asset": asset,
|
|
799
|
-
"signer": user_address,
|
|
800
|
-
"amount": amount_raw,
|
|
801
|
-
},
|
|
802
|
-
headers=_headers(),
|
|
803
|
-
)
|
|
804
|
-
if response.status_code != 200:
|
|
805
|
-
raise ProviderError("jupiter-lend", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
806
|
-
data = response.json()
|
|
807
|
-
if not isinstance(data, dict) or "transaction" not in data:
|
|
808
|
-
raise ProviderError("jupiter-lend", "Unexpected Earn deposit response.")
|
|
809
|
-
return data
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
async def build_earn_withdraw_transaction(
|
|
813
|
-
*,
|
|
814
|
-
asset: str,
|
|
815
|
-
user_address: str,
|
|
816
|
-
amount_raw: str,
|
|
817
|
-
) -> dict[str, Any]:
|
|
818
|
-
"""Build an unsigned Jupiter Earn withdraw transaction."""
|
|
819
|
-
gateway_response = await _earn_post_with_gateway_fallback(
|
|
820
|
-
path="/v1/jupiter/earn/withdraw",
|
|
821
|
-
body={
|
|
822
|
-
"asset": asset,
|
|
823
|
-
"signer": user_address,
|
|
824
|
-
"amount": amount_raw,
|
|
825
|
-
},
|
|
826
|
-
operation="Jupiter Earn withdraw",
|
|
827
|
-
)
|
|
828
|
-
if gateway_response is not None:
|
|
829
|
-
if not isinstance(gateway_response, dict) or "transaction" not in gateway_response:
|
|
830
|
-
raise ProviderError("jupiter-lend", "Unexpected Earn withdraw response.")
|
|
831
|
-
return gateway_response
|
|
832
|
-
|
|
833
|
-
_require_api_key("jupiter-lend")
|
|
834
|
-
client = get_client()
|
|
835
|
-
response = await client.post(
|
|
836
|
-
f"{settings.jupiter_lend_api_base_url.rstrip('/')}/earn/withdraw",
|
|
837
|
-
json={
|
|
838
|
-
"asset": asset,
|
|
839
|
-
"signer": user_address,
|
|
840
|
-
"amount": amount_raw,
|
|
841
|
-
},
|
|
842
|
-
headers=_headers(),
|
|
843
|
-
)
|
|
844
|
-
if response.status_code != 200:
|
|
845
|
-
raise ProviderError("jupiter-lend", f"HTTP {response.status_code}: {response.text[:300]}")
|
|
846
|
-
data = response.json()
|
|
847
|
-
if not isinstance(data, dict) or "transaction" not in data:
|
|
848
|
-
raise ProviderError("jupiter-lend", "Unexpected Earn withdraw response.")
|
|
849
|
-
return data
|
|
@@ -9,7 +9,7 @@ from urllib.parse import urlparse
|
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
11
|
|
|
12
|
-
from agent_wallet.config import resolve_openclaw_home
|
|
12
|
+
from agent_wallet.config import resolve_btc_wallet_password, resolve_openclaw_home
|
|
13
13
|
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
14
14
|
|
|
15
15
|
LOCAL_WDK_BTC_HOSTS = {"127.0.0.1", "localhost", "::1"}
|
|
@@ -72,6 +72,30 @@ class WdkBtcLocalClient:
|
|
|
72
72
|
"Accept": "application/json",
|
|
73
73
|
"Authorization": f"Bearer {_load_local_token()}",
|
|
74
74
|
}
|
|
75
|
+
self._wallet_password: str | None = None
|
|
76
|
+
|
|
77
|
+
def _resolve_wallet_password(self) -> str:
|
|
78
|
+
"""Resolve the sealed local BTC vault password once per client instance.
|
|
79
|
+
|
|
80
|
+
The decrypt-on-demand vault needs the password on every signing request;
|
|
81
|
+
resolving it once per client keeps the Argon2id unseal off the per-request
|
|
82
|
+
path. The agent already holds the boot key, so caching adds no extra exposure.
|
|
83
|
+
"""
|
|
84
|
+
if self._wallet_password is None:
|
|
85
|
+
try:
|
|
86
|
+
self._wallet_password = resolve_btc_wallet_password() or ""
|
|
87
|
+
except Exception:
|
|
88
|
+
self._wallet_password = ""
|
|
89
|
+
return self._wallet_password
|
|
90
|
+
|
|
91
|
+
def _with_credentials(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
92
|
+
"""Attach the sealed vault password unless the caller supplied one."""
|
|
93
|
+
if not isinstance(payload, dict) or payload.get("password"):
|
|
94
|
+
return payload
|
|
95
|
+
password = self._resolve_wallet_password()
|
|
96
|
+
if not password:
|
|
97
|
+
return payload
|
|
98
|
+
return {**payload, "password": password}
|
|
75
99
|
|
|
76
100
|
async def post(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
77
101
|
async with httpx.AsyncClient(
|
|
@@ -80,7 +104,9 @@ class WdkBtcLocalClient:
|
|
|
80
104
|
follow_redirects=False,
|
|
81
105
|
trust_env=False,
|
|
82
106
|
) as client:
|
|
83
|
-
response = await client.post(
|
|
107
|
+
response = await client.post(
|
|
108
|
+
f"{self.base_url}{path}", json=self._with_credentials(payload)
|
|
109
|
+
)
|
|
84
110
|
return _unwrap_payload(response)
|
|
85
111
|
|
|
86
112
|
async def get(self, path: str) -> dict[str, Any]:
|
|
@@ -100,7 +126,9 @@ class WdkBtcLocalClient:
|
|
|
100
126
|
follow_redirects=False,
|
|
101
127
|
trust_env=False,
|
|
102
128
|
) as client:
|
|
103
|
-
response = client.post(
|
|
129
|
+
response = client.post(
|
|
130
|
+
f"{self.base_url}{path}", json=self._with_credentials(payload)
|
|
131
|
+
)
|
|
104
132
|
return _unwrap_payload(response)
|
|
105
133
|
|
|
106
134
|
def get_sync(self, path: str) -> dict[str, Any]:
|
|
@@ -9,7 +9,7 @@ from urllib.parse import urlparse
|
|
|
9
9
|
|
|
10
10
|
import httpx
|
|
11
11
|
|
|
12
|
-
from agent_wallet.config import resolve_openclaw_home, settings
|
|
12
|
+
from agent_wallet.config import resolve_evm_wallet_password, resolve_openclaw_home, settings
|
|
13
13
|
from agent_wallet.wallet_layer.base import WalletBackendError
|
|
14
14
|
|
|
15
15
|
LOCAL_WDK_EVM_HOSTS = {"127.0.0.1", "localhost", "::1"}
|
|
@@ -140,6 +140,36 @@ class WdkEvmLocalClient:
|
|
|
140
140
|
"Accept": "application/json",
|
|
141
141
|
"Authorization": f"Bearer {_load_local_token()}",
|
|
142
142
|
}
|
|
143
|
+
self._wallet_password: str | None = None
|
|
144
|
+
|
|
145
|
+
def _resolve_wallet_password(self) -> str:
|
|
146
|
+
"""Resolve the sealed local EVM vault password once per client instance.
|
|
147
|
+
|
|
148
|
+
The decrypt-on-demand vault needs the password on every signing request.
|
|
149
|
+
Resolving (and unsealing) it once per client keeps the Argon2id unseal off
|
|
150
|
+
the per-request path. The agent already holds the boot key, so caching the
|
|
151
|
+
derived password here adds no exposure beyond what the boot key grants.
|
|
152
|
+
"""
|
|
153
|
+
if self._wallet_password is None:
|
|
154
|
+
try:
|
|
155
|
+
self._wallet_password = resolve_evm_wallet_password() or ""
|
|
156
|
+
except Exception:
|
|
157
|
+
self._wallet_password = ""
|
|
158
|
+
return self._wallet_password
|
|
159
|
+
|
|
160
|
+
def _with_credentials(self, payload: dict[str, Any]) -> dict[str, Any]:
|
|
161
|
+
"""Attach the sealed vault password unless the caller supplied one.
|
|
162
|
+
|
|
163
|
+
Read endpoints that resolve via a stored address ignore the field; only
|
|
164
|
+
seed-requiring (signing) endpoints consume it. Injecting it uniformly means
|
|
165
|
+
no signing path can accidentally be missed.
|
|
166
|
+
"""
|
|
167
|
+
if not isinstance(payload, dict) or payload.get("password"):
|
|
168
|
+
return payload
|
|
169
|
+
password = self._resolve_wallet_password()
|
|
170
|
+
if not password:
|
|
171
|
+
return payload
|
|
172
|
+
return {**payload, "password": password}
|
|
143
173
|
|
|
144
174
|
async def post(self, path: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
145
175
|
try:
|
|
@@ -149,7 +179,9 @@ class WdkEvmLocalClient:
|
|
|
149
179
|
follow_redirects=False,
|
|
150
180
|
trust_env=False,
|
|
151
181
|
) as client:
|
|
152
|
-
response = await client.post(
|
|
182
|
+
response = await client.post(
|
|
183
|
+
f"{self.base_url}{path}", json=self._with_credentials(payload)
|
|
184
|
+
)
|
|
153
185
|
except httpx.TimeoutException as exc:
|
|
154
186
|
raise WalletBackendError(
|
|
155
187
|
"wdk-evm-wallet request timed out.",
|
|
@@ -195,7 +227,9 @@ class WdkEvmLocalClient:
|
|
|
195
227
|
follow_redirects=False,
|
|
196
228
|
trust_env=False,
|
|
197
229
|
) as client:
|
|
198
|
-
response = client.post(
|
|
230
|
+
response = client.post(
|
|
231
|
+
f"{self.base_url}{path}", json=self._with_credentials(payload)
|
|
232
|
+
)
|
|
199
233
|
except httpx.TimeoutException as exc:
|
|
200
234
|
raise WalletBackendError(
|
|
201
235
|
"wdk-evm-wallet request timed out.",
|