@agentlayer.tech/wallet 0.1.0

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 (96) hide show
  1. package/.openclaw/AGENTS.md +98 -0
  2. package/.openclaw/extensions/agent-wallet/README.md +127 -0
  3. package/.openclaw/extensions/agent-wallet/index.ts +1520 -0
  4. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +184 -0
  5. package/.openclaw/extensions/agent-wallet/package.json +11 -0
  6. package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +20 -0
  7. package/CHANGELOG.md +42 -0
  8. package/LICENSE +104 -0
  9. package/README.md +332 -0
  10. package/RELEASING.md +204 -0
  11. package/agent-wallet/.env.example +62 -0
  12. package/agent-wallet/AGENTS.md +129 -0
  13. package/agent-wallet/README.md +527 -0
  14. package/agent-wallet/agent_wallet/__init__.py +11 -0
  15. package/agent-wallet/agent_wallet/approval.py +161 -0
  16. package/agent-wallet/agent_wallet/bootstrap.py +178 -0
  17. package/agent-wallet/agent_wallet/btc_user_wallets.py +217 -0
  18. package/agent-wallet/agent_wallet/config.py +382 -0
  19. package/agent-wallet/agent_wallet/encrypted_storage.py +161 -0
  20. package/agent-wallet/agent_wallet/evm_user_wallets.py +370 -0
  21. package/agent-wallet/agent_wallet/exceptions.py +9 -0
  22. package/agent-wallet/agent_wallet/file_ops.py +34 -0
  23. package/agent-wallet/agent_wallet/http_client.py +25 -0
  24. package/agent-wallet/agent_wallet/models.py +66 -0
  25. package/agent-wallet/agent_wallet/nonce_registry.py +59 -0
  26. package/agent-wallet/agent_wallet/openclaw_adapter.py +5128 -0
  27. package/agent-wallet/agent_wallet/openclaw_cli.py +626 -0
  28. package/agent-wallet/agent_wallet/openclaw_runtime.py +272 -0
  29. package/agent-wallet/agent_wallet/plugin_bundle.py +42 -0
  30. package/agent-wallet/agent_wallet/providers/__init__.py +1 -0
  31. package/agent-wallet/agent_wallet/providers/bags.py +259 -0
  32. package/agent-wallet/agent_wallet/providers/evm_portfolio.py +470 -0
  33. package/agent-wallet/agent_wallet/providers/jupiter.py +567 -0
  34. package/agent-wallet/agent_wallet/providers/kamino.py +215 -0
  35. package/agent-wallet/agent_wallet/providers/lifi.py +277 -0
  36. package/agent-wallet/agent_wallet/providers/solana_rpc.py +470 -0
  37. package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +114 -0
  38. package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +205 -0
  39. package/agent-wallet/agent_wallet/sealed_keys.py +61 -0
  40. package/agent-wallet/agent_wallet/solana_stake.py +103 -0
  41. package/agent-wallet/agent_wallet/solana_tx.py +93 -0
  42. package/agent-wallet/agent_wallet/spending_limits.py +101 -0
  43. package/agent-wallet/agent_wallet/transaction_policy.py +518 -0
  44. package/agent-wallet/agent_wallet/user_wallets.py +355 -0
  45. package/agent-wallet/agent_wallet/validation.py +31 -0
  46. package/agent-wallet/agent_wallet/wallet_layer/__init__.py +1 -0
  47. package/agent-wallet/agent_wallet/wallet_layer/base.py +808 -0
  48. package/agent-wallet/agent_wallet/wallet_layer/base58.py +44 -0
  49. package/agent-wallet/agent_wallet/wallet_layer/factory.py +102 -0
  50. package/agent-wallet/agent_wallet/wallet_layer/solana.py +4252 -0
  51. package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +272 -0
  52. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +1628 -0
  53. package/agent-wallet/examples/bootstrap_wallet.py +21 -0
  54. package/agent-wallet/examples/openclaw_runtime_onboarding.py +28 -0
  55. package/agent-wallet/examples/openclaw_user_wallet_example.py +31 -0
  56. package/agent-wallet/examples/openclaw_wallet_adapter_example.py +33 -0
  57. package/agent-wallet/openclaw.plugin.json +138 -0
  58. package/agent-wallet/pyproject.toml +31 -0
  59. package/agent-wallet/scripts/bootstrap_openclaw_btc.py +278 -0
  60. package/agent-wallet/scripts/build_release_bundle.py +188 -0
  61. package/agent-wallet/scripts/finalize_openclaw_local_wallet_config.py +121 -0
  62. package/agent-wallet/scripts/install_agent_wallet.py +505 -0
  63. package/agent-wallet/scripts/install_openclaw_local_config.py +226 -0
  64. package/agent-wallet/scripts/install_openclaw_sealed_keys.py +105 -0
  65. package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +244 -0
  66. package/agent-wallet/scripts/reveal_btc_seed.sh +130 -0
  67. package/agent-wallet/scripts/security_utils.py +37 -0
  68. package/agent-wallet/scripts/setup_btc_wallet.sh +146 -0
  69. package/agent-wallet/scripts/switch_openclaw_wallet_network.py +106 -0
  70. package/agent-wallet/skills/wallet-operator/SKILL.md +128 -0
  71. package/bin/openclaw-agent-wallet.mjs +487 -0
  72. package/install-from-github.sh +134 -0
  73. package/package.json +61 -0
  74. package/setup.sh +40 -0
  75. package/wdk-btc-wallet/README.md +325 -0
  76. package/wdk-btc-wallet/bootstrap.sh +22 -0
  77. package/wdk-btc-wallet/package-lock.json +1839 -0
  78. package/wdk-btc-wallet/package.json +18 -0
  79. package/wdk-btc-wallet/run-local.sh +21 -0
  80. package/wdk-btc-wallet/src/config.js +160 -0
  81. package/wdk-btc-wallet/src/json.js +35 -0
  82. package/wdk-btc-wallet/src/local_vault.js +432 -0
  83. package/wdk-btc-wallet/src/network_state.js +84 -0
  84. package/wdk-btc-wallet/src/server.js +257 -0
  85. package/wdk-btc-wallet/src/wdk_btc_wallet.js +332 -0
  86. package/wdk-evm-wallet/README.md +183 -0
  87. package/wdk-evm-wallet/bootstrap.sh +8 -0
  88. package/wdk-evm-wallet/package-lock.json +2340 -0
  89. package/wdk-evm-wallet/package.json +23 -0
  90. package/wdk-evm-wallet/run-local.sh +12 -0
  91. package/wdk-evm-wallet/src/config.js +274 -0
  92. package/wdk-evm-wallet/src/json.js +35 -0
  93. package/wdk-evm-wallet/src/local_vault.js +430 -0
  94. package/wdk-evm-wallet/src/network_state.js +92 -0
  95. package/wdk-evm-wallet/src/server.js +575 -0
  96. package/wdk-evm-wallet/src/wdk_evm_wallet.js +4981 -0
@@ -0,0 +1,567 @@
1
+ """Jupiter providers for swap routing, prices, portfolio, and lend/earn flows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ from typing import Any
7
+
8
+ from agent_wallet.config import settings
9
+ from agent_wallet.exceptions import ProviderError
10
+ from agent_wallet.http_client import get_client
11
+
12
+
13
+ def _headers() -> dict[str, str]:
14
+ headers = {"Accept": "application/json"}
15
+ if settings.jupiter_api_key.strip():
16
+ headers["x-api-key"] = settings.jupiter_api_key.strip()
17
+ return headers
18
+
19
+
20
+ def _require_api_key(provider_name: str) -> None:
21
+ if not settings.jupiter_api_key.strip():
22
+ raise ProviderError(
23
+ provider_name,
24
+ "Jupiter API key is required for this endpoint. Set JUPITER_API_KEY first.",
25
+ )
26
+
27
+
28
+ def _gateway_base_url() -> str:
29
+ return os.getenv("PROVIDER_GATEWAY_URL", settings.provider_gateway_url).strip().rstrip("/")
30
+
31
+
32
+ def _gateway_headers() -> dict[str, str]:
33
+ headers = {"Accept": "application/json"}
34
+ bearer = os.getenv(
35
+ "PROVIDER_GATEWAY_BEARER_TOKEN",
36
+ settings.provider_gateway_bearer_token,
37
+ ).strip()
38
+ if bearer:
39
+ headers["Authorization"] = f"Bearer {bearer}"
40
+ return headers
41
+
42
+
43
+ def _gateway_enabled() -> bool:
44
+ return bool(_gateway_base_url())
45
+
46
+
47
+ def _gateway_route_missing(status_code: int, payload: Any) -> bool:
48
+ if status_code == 404:
49
+ return True
50
+ if isinstance(payload, dict):
51
+ message = str(payload.get("error") or "").lower()
52
+ if "not found" in message:
53
+ return True
54
+ return False
55
+
56
+
57
+ def _direct_jupiter_enabled() -> bool:
58
+ return bool(settings.jupiter_api_key.strip())
59
+
60
+
61
+ def _unwrap_gateway_payload(
62
+ status_code: int,
63
+ payload: Any,
64
+ *,
65
+ operation: str,
66
+ ) -> Any:
67
+ if isinstance(payload, dict) and payload.get("ok") is False:
68
+ message = str(payload.get("error") or f"{operation} failed.")
69
+ raise ProviderError("jupiter-lend", f"{operation} failed via provider gateway: {message}")
70
+
71
+ if status_code != 200:
72
+ message = payload
73
+ if isinstance(payload, dict):
74
+ message = payload.get("error") or payload
75
+ raise ProviderError("jupiter-lend", f"{operation} failed via provider gateway: {message}")
76
+
77
+ return payload
78
+
79
+
80
+ async def _gateway_get_json(
81
+ path: str,
82
+ *,
83
+ params: dict[str, Any] | None,
84
+ operation: str,
85
+ ) -> Any:
86
+ client = get_client()
87
+ response = await client.get(
88
+ f"{_gateway_base_url()}{path}",
89
+ params=params,
90
+ headers=_gateway_headers(),
91
+ )
92
+ payload = response.json() if response.content else {}
93
+ return _unwrap_gateway_payload(
94
+ response.status_code,
95
+ payload,
96
+ operation=operation,
97
+ )
98
+
99
+
100
+ async def _gateway_post_json(
101
+ path: str,
102
+ *,
103
+ body: dict[str, Any],
104
+ operation: str,
105
+ ) -> Any:
106
+ client = get_client()
107
+ response = await client.post(
108
+ f"{_gateway_base_url()}{path}",
109
+ json=body,
110
+ headers={**_gateway_headers(), "Content-Type": "application/json"},
111
+ )
112
+ payload = response.json() if response.content else {}
113
+ return _unwrap_gateway_payload(
114
+ response.status_code,
115
+ payload,
116
+ operation=operation,
117
+ )
118
+
119
+
120
+ async def _earn_get_with_gateway_fallback(
121
+ *,
122
+ path: str,
123
+ params: dict[str, Any] | None,
124
+ operation: str,
125
+ ) -> Any:
126
+ if _gateway_enabled():
127
+ client = get_client()
128
+ response = await client.get(
129
+ f"{_gateway_base_url()}{path}",
130
+ params=params,
131
+ headers=_gateway_headers(),
132
+ )
133
+ payload = response.json() if response.content else {}
134
+ if _gateway_route_missing(response.status_code, payload) and _direct_jupiter_enabled():
135
+ return None
136
+ return _unwrap_gateway_payload(response.status_code, payload, operation=operation)
137
+ return None
138
+
139
+
140
+ async def _earn_post_with_gateway_fallback(
141
+ *,
142
+ path: str,
143
+ body: dict[str, Any],
144
+ operation: str,
145
+ ) -> Any:
146
+ if _gateway_enabled():
147
+ client = get_client()
148
+ response = await client.post(
149
+ f"{_gateway_base_url()}{path}",
150
+ json=body,
151
+ headers={**_gateway_headers(), "Content-Type": "application/json"},
152
+ )
153
+ payload = response.json() if response.content else {}
154
+ if _gateway_route_missing(response.status_code, payload) and _direct_jupiter_enabled():
155
+ return None
156
+ return _unwrap_gateway_payload(response.status_code, payload, operation=operation)
157
+ return None
158
+
159
+
160
+ def _normalize_named_list_response(
161
+ data: Any,
162
+ *,
163
+ key: str,
164
+ provider_name: str,
165
+ ) -> dict[str, Any]:
166
+ if isinstance(data, list):
167
+ return {key: data}
168
+ if isinstance(data, dict):
169
+ items = data.get(key)
170
+ if isinstance(items, list):
171
+ return data
172
+ fallback = data.get("data")
173
+ if isinstance(fallback, list):
174
+ normalized = dict(data)
175
+ normalized[key] = fallback
176
+ return normalized
177
+ raise ProviderError(provider_name, f"Unexpected {key} response from Jupiter.")
178
+
179
+
180
+ async def fetch_quote(
181
+ *,
182
+ input_mint: str,
183
+ output_mint: str,
184
+ amount_raw: int,
185
+ slippage_bps: int = 50,
186
+ restrict_intermediate_tokens: bool = True,
187
+ only_direct_routes: bool = False,
188
+ swap_mode: str = "ExactIn",
189
+ ) -> dict[str, Any]:
190
+ """Fetch a Jupiter quote for an exact-in swap."""
191
+ client = get_client()
192
+ params = {
193
+ "inputMint": input_mint,
194
+ "outputMint": output_mint,
195
+ "amount": str(amount_raw),
196
+ "slippageBps": str(slippage_bps),
197
+ "swapMode": swap_mode,
198
+ "restrictIntermediateTokens": str(restrict_intermediate_tokens).lower(),
199
+ "onlyDirectRoutes": str(only_direct_routes).lower(),
200
+ }
201
+ response = await client.get(
202
+ f"{settings.jupiter_api_base_url.rstrip('/')}/quote",
203
+ params=params,
204
+ headers=_headers(),
205
+ )
206
+ if response.status_code != 200:
207
+ raise ProviderError("jupiter", f"HTTP {response.status_code}: {response.text[:300]}")
208
+ data = response.json()
209
+ if not isinstance(data, dict) or "outAmount" not in data:
210
+ raise ProviderError("jupiter", "Unexpected quote response from Jupiter.")
211
+ return data
212
+
213
+
214
+ async def fetch_ultra_order(
215
+ *,
216
+ input_mint: str,
217
+ output_mint: str,
218
+ amount_raw: int,
219
+ taker: str | None = None,
220
+ slippage_bps: int = 50,
221
+ swap_mode: str = "ExactIn",
222
+ ) -> dict[str, Any]:
223
+ """Fetch a Jupiter Ultra order for an exact-in swap."""
224
+ client = get_client()
225
+ params = {
226
+ "inputMint": input_mint,
227
+ "outputMint": output_mint,
228
+ "amount": str(amount_raw),
229
+ "swapMode": swap_mode,
230
+ "slippageBps": str(slippage_bps),
231
+ }
232
+ if taker:
233
+ params["taker"] = taker
234
+ response = await client.get(
235
+ f"{settings.jupiter_ultra_api_base_url.rstrip('/')}/order",
236
+ params=params,
237
+ headers=_headers(),
238
+ )
239
+ if response.status_code != 200:
240
+ raise ProviderError("jupiter-ultra", f"HTTP {response.status_code}: {response.text[:300]}")
241
+ data = response.json()
242
+ if not isinstance(data, dict):
243
+ raise ProviderError("jupiter-ultra", "Unexpected order response from Jupiter Ultra.")
244
+ if data.get("error") or data.get("errorCode"):
245
+ raise ProviderError(
246
+ "jupiter-ultra",
247
+ str(data.get("error") or data.get("errorCode") or "Unknown Ultra error."),
248
+ )
249
+ if "outAmount" not in data:
250
+ raise ProviderError("jupiter-ultra", "Unexpected order response from Jupiter Ultra.")
251
+ return data
252
+
253
+
254
+ async def build_swap_transaction(
255
+ *,
256
+ user_public_key: str,
257
+ quote_response: dict[str, Any],
258
+ wrap_and_unwrap_sol: bool = True,
259
+ ) -> dict[str, Any]:
260
+ """Build a serialized swap transaction from a Jupiter quote."""
261
+ client = get_client()
262
+ body = {
263
+ "userPublicKey": user_public_key,
264
+ "quoteResponse": quote_response,
265
+ "wrapAndUnwrapSol": wrap_and_unwrap_sol,
266
+ "dynamicComputeUnitLimit": True,
267
+ "prioritizationFeeLamports": "auto",
268
+ }
269
+ response = await client.post(
270
+ f"{settings.jupiter_api_base_url.rstrip('/')}/swap",
271
+ json=body,
272
+ headers=_headers(),
273
+ )
274
+ if response.status_code != 200:
275
+ raise ProviderError("jupiter", f"HTTP {response.status_code}: {response.text[:300]}")
276
+ data = response.json()
277
+ if not isinstance(data, dict) or "swapTransaction" not in data:
278
+ raise ProviderError("jupiter", "Unexpected swap response from Jupiter.")
279
+ return data
280
+
281
+
282
+ async def execute_ultra_order(
283
+ *,
284
+ signed_transaction_base64: str,
285
+ request_id: str,
286
+ ) -> dict[str, Any]:
287
+ """Execute a signed Jupiter Ultra order."""
288
+ client = get_client()
289
+ body = {
290
+ "signedTransaction": signed_transaction_base64,
291
+ "requestId": request_id,
292
+ }
293
+ response = await client.post(
294
+ f"{settings.jupiter_ultra_api_base_url.rstrip('/')}/execute",
295
+ json=body,
296
+ headers=_headers(),
297
+ )
298
+ if response.status_code != 200:
299
+ raise ProviderError("jupiter-ultra", f"HTTP {response.status_code}: {response.text[:300]}")
300
+ data = response.json()
301
+ if not isinstance(data, dict):
302
+ raise ProviderError("jupiter-ultra", "Unexpected execute response from Jupiter Ultra.")
303
+ if data.get("error") or data.get("errorCode"):
304
+ raise ProviderError(
305
+ "jupiter-ultra",
306
+ str(data.get("error") or data.get("errorCode") or "Unknown Ultra execute error."),
307
+ )
308
+ return data
309
+
310
+
311
+ async def fetch_prices(
312
+ *,
313
+ mints: list[str],
314
+ show_extra_info: bool = False,
315
+ ) -> dict[str, Any]:
316
+ """Fetch token prices from Jupiter Price API V3."""
317
+ if not mints:
318
+ raise ProviderError("jupiter", "At least one mint is required for price lookup.")
319
+ client = get_client()
320
+ params = {
321
+ "ids": ",".join(mints),
322
+ }
323
+ if show_extra_info:
324
+ params["showExtraInfo"] = "true"
325
+ response = await client.get(
326
+ settings.jupiter_price_api_base_url,
327
+ params=params,
328
+ headers=_headers(),
329
+ )
330
+ if response.status_code != 200:
331
+ raise ProviderError("jupiter", f"HTTP {response.status_code}: {response.text[:300]}")
332
+ data = response.json()
333
+ if not isinstance(data, dict):
334
+ raise ProviderError("jupiter", "Unexpected price response from Jupiter.")
335
+ return data
336
+
337
+
338
+ async def fetch_portfolio_platforms() -> dict[str, Any]:
339
+ """Fetch the list of supported Jupiter Portfolio platforms."""
340
+ client = get_client()
341
+ response = await client.get(
342
+ f"{settings.jupiter_portfolio_api_base_url.rstrip('/')}/platforms",
343
+ headers=_headers(),
344
+ )
345
+ if response.status_code != 200:
346
+ raise ProviderError(
347
+ "jupiter-portfolio",
348
+ f"HTTP {response.status_code}: {response.text[:300]}",
349
+ )
350
+ data = response.json()
351
+ if not isinstance(data, dict):
352
+ raise ProviderError("jupiter-portfolio", "Unexpected portfolio platforms response.")
353
+ return data
354
+
355
+
356
+ async def fetch_portfolio_positions(
357
+ *,
358
+ address: str,
359
+ platforms: list[str] | None = None,
360
+ ) -> dict[str, Any]:
361
+ """Fetch Jupiter Portfolio positions for a wallet address."""
362
+ client = get_client()
363
+ params: dict[str, str] = {"address": address}
364
+ if platforms:
365
+ params["platforms"] = ",".join(platforms)
366
+ response = await client.get(
367
+ f"{settings.jupiter_portfolio_api_base_url.rstrip('/')}/positions",
368
+ params=params,
369
+ headers=_headers(),
370
+ )
371
+ if response.status_code != 200:
372
+ raise ProviderError(
373
+ "jupiter-portfolio",
374
+ f"HTTP {response.status_code}: {response.text[:300]}",
375
+ )
376
+ data = response.json()
377
+ if not isinstance(data, dict):
378
+ raise ProviderError("jupiter-portfolio", "Unexpected portfolio positions response.")
379
+ return data
380
+
381
+
382
+ async def fetch_staked_jup(*, address: str) -> dict[str, Any]:
383
+ """Fetch staked JUP information for a wallet address."""
384
+ client = get_client()
385
+ response = await client.get(
386
+ f"{settings.jupiter_portfolio_api_base_url.rstrip('/')}/staked-jup",
387
+ params={"address": address},
388
+ headers=_headers(),
389
+ )
390
+ if response.status_code != 200:
391
+ raise ProviderError(
392
+ "jupiter-portfolio",
393
+ f"HTTP {response.status_code}: {response.text[:300]}",
394
+ )
395
+ data = response.json()
396
+ if not isinstance(data, dict):
397
+ raise ProviderError("jupiter-portfolio", "Unexpected staked JUP response.")
398
+ return data
399
+
400
+
401
+ async def fetch_earn_tokens() -> dict[str, Any]:
402
+ """Fetch supported Jupiter Earn vault tokens."""
403
+ gateway_response = await _earn_get_with_gateway_fallback(
404
+ path="/v1/jupiter/earn/tokens",
405
+ params=None,
406
+ operation="Jupiter Earn tokens",
407
+ )
408
+ if gateway_response is not None:
409
+ return _normalize_named_list_response(
410
+ gateway_response,
411
+ key="tokens",
412
+ provider_name="jupiter-lend",
413
+ )
414
+
415
+ _require_api_key("jupiter-lend")
416
+ client = get_client()
417
+ response = await client.get(
418
+ f"{settings.jupiter_lend_api_base_url.rstrip('/')}/earn/tokens",
419
+ headers=_headers(),
420
+ )
421
+ if response.status_code != 200:
422
+ raise ProviderError("jupiter-lend", f"HTTP {response.status_code}: {response.text[:300]}")
423
+ return _normalize_named_list_response(
424
+ response.json(),
425
+ key="tokens",
426
+ provider_name="jupiter-lend",
427
+ )
428
+
429
+
430
+ async def fetch_earn_positions(*, users: list[str]) -> dict[str, Any]:
431
+ """Fetch Jupiter Earn positions for one or more users."""
432
+ gateway_response = await _earn_get_with_gateway_fallback(
433
+ path="/v1/jupiter/earn/positions",
434
+ params={"users": ",".join(users)},
435
+ operation="Jupiter Earn positions",
436
+ )
437
+ if gateway_response is not None:
438
+ return _normalize_named_list_response(
439
+ gateway_response,
440
+ key="positions",
441
+ provider_name="jupiter-lend",
442
+ )
443
+
444
+ _require_api_key("jupiter-lend")
445
+ client = get_client()
446
+ response = await client.get(
447
+ f"{settings.jupiter_lend_api_base_url.rstrip('/')}/earn/positions",
448
+ params={"users": ",".join(users)},
449
+ headers=_headers(),
450
+ )
451
+ if response.status_code != 200:
452
+ raise ProviderError("jupiter-lend", f"HTTP {response.status_code}: {response.text[:300]}")
453
+ return _normalize_named_list_response(
454
+ response.json(),
455
+ key="positions",
456
+ provider_name="jupiter-lend",
457
+ )
458
+
459
+
460
+ async def fetch_earn_earnings(*, user: str, positions: list[str]) -> dict[str, Any]:
461
+ """Fetch Jupiter Earn earnings for a user and position list."""
462
+ gateway_response = await _earn_get_with_gateway_fallback(
463
+ path="/v1/jupiter/earn/earnings",
464
+ params={"user": user, "positions": ",".join(positions)},
465
+ operation="Jupiter Earn earnings",
466
+ )
467
+ if gateway_response is not None:
468
+ return _normalize_named_list_response(
469
+ gateway_response,
470
+ key="earnings",
471
+ provider_name="jupiter-lend",
472
+ )
473
+
474
+ _require_api_key("jupiter-lend")
475
+ client = get_client()
476
+ response = await client.get(
477
+ f"{settings.jupiter_lend_api_base_url.rstrip('/')}/earn/earnings",
478
+ params={"user": user, "positions": ",".join(positions)},
479
+ headers=_headers(),
480
+ )
481
+ if response.status_code != 200:
482
+ raise ProviderError("jupiter-lend", f"HTTP {response.status_code}: {response.text[:300]}")
483
+ return _normalize_named_list_response(
484
+ response.json(),
485
+ key="earnings",
486
+ provider_name="jupiter-lend",
487
+ )
488
+
489
+
490
+ async def build_earn_deposit_transaction(
491
+ *,
492
+ asset: str,
493
+ user_address: str,
494
+ amount_raw: str,
495
+ ) -> dict[str, Any]:
496
+ """Build an unsigned Jupiter Earn deposit transaction."""
497
+ gateway_response = await _earn_post_with_gateway_fallback(
498
+ path="/v1/jupiter/earn/deposit",
499
+ body={
500
+ "asset": asset,
501
+ "signer": user_address,
502
+ "amount": amount_raw,
503
+ },
504
+ operation="Jupiter Earn deposit",
505
+ )
506
+ if gateway_response is not None:
507
+ if not isinstance(gateway_response, dict) or "transaction" not in gateway_response:
508
+ raise ProviderError("jupiter-lend", "Unexpected Earn deposit response.")
509
+ return gateway_response
510
+
511
+ _require_api_key("jupiter-lend")
512
+ client = get_client()
513
+ response = await client.post(
514
+ f"{settings.jupiter_lend_api_base_url.rstrip('/')}/earn/deposit",
515
+ json={
516
+ "asset": asset,
517
+ "signer": user_address,
518
+ "amount": amount_raw,
519
+ },
520
+ headers=_headers(),
521
+ )
522
+ if response.status_code != 200:
523
+ raise ProviderError("jupiter-lend", f"HTTP {response.status_code}: {response.text[:300]}")
524
+ data = response.json()
525
+ if not isinstance(data, dict) or "transaction" not in data:
526
+ raise ProviderError("jupiter-lend", "Unexpected Earn deposit response.")
527
+ return data
528
+
529
+
530
+ async def build_earn_withdraw_transaction(
531
+ *,
532
+ asset: str,
533
+ user_address: str,
534
+ amount_raw: str,
535
+ ) -> dict[str, Any]:
536
+ """Build an unsigned Jupiter Earn withdraw transaction."""
537
+ gateway_response = await _earn_post_with_gateway_fallback(
538
+ path="/v1/jupiter/earn/withdraw",
539
+ body={
540
+ "asset": asset,
541
+ "signer": user_address,
542
+ "amount": amount_raw,
543
+ },
544
+ operation="Jupiter Earn withdraw",
545
+ )
546
+ if gateway_response is not None:
547
+ if not isinstance(gateway_response, dict) or "transaction" not in gateway_response:
548
+ raise ProviderError("jupiter-lend", "Unexpected Earn withdraw response.")
549
+ return gateway_response
550
+
551
+ _require_api_key("jupiter-lend")
552
+ client = get_client()
553
+ response = await client.post(
554
+ f"{settings.jupiter_lend_api_base_url.rstrip('/')}/earn/withdraw",
555
+ json={
556
+ "asset": asset,
557
+ "signer": user_address,
558
+ "amount": amount_raw,
559
+ },
560
+ headers=_headers(),
561
+ )
562
+ if response.status_code != 200:
563
+ raise ProviderError("jupiter-lend", f"HTTP {response.status_code}: {response.text[:300]}")
564
+ data = response.json()
565
+ if not isinstance(data, dict) or "transaction" not in data:
566
+ raise ProviderError("jupiter-lend", "Unexpected Earn withdraw response.")
567
+ return data