@agentlayer.tech/wallet 0.1.28 → 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.
Files changed (71) hide show
  1. package/.openclaw/extensions/agent-wallet/README.md +5 -7
  2. package/.openclaw/extensions/agent-wallet/dist/index.js +35 -360
  3. package/.openclaw/extensions/agent-wallet/index.ts +35 -360
  4. package/.openclaw/extensions/agent-wallet/openclaw.plugin.json +2 -45
  5. package/.openclaw/extensions/agent-wallet/package.json +1 -1
  6. package/.openclaw/extensions/agent-wallet/skills/wallet-operator/SKILL.md +1 -3
  7. package/CHANGELOG.md +73 -0
  8. package/README.md +4 -0
  9. package/agent-wallet/.env.example +0 -12
  10. package/agent-wallet/README.md +18 -57
  11. package/agent-wallet/agent_wallet/bootstrap.py +28 -12
  12. package/agent-wallet/agent_wallet/btc_user_wallets.py +33 -7
  13. package/agent-wallet/agent_wallet/config.py +110 -29
  14. package/agent-wallet/agent_wallet/evm_user_wallets.py +4 -14
  15. package/agent-wallet/agent_wallet/openclaw_adapter.py +29 -687
  16. package/agent-wallet/agent_wallet/openclaw_cli.py +0 -7
  17. package/agent-wallet/agent_wallet/openclaw_runtime.py +3 -12
  18. package/agent-wallet/agent_wallet/providers/evm_portfolio.py +18 -42
  19. package/agent-wallet/agent_wallet/providers/jupiter.py +1 -307
  20. package/agent-wallet/agent_wallet/providers/kamino.py +21 -4
  21. package/agent-wallet/agent_wallet/providers/solana_rpc.py +0 -23
  22. package/agent-wallet/agent_wallet/providers/wdk_btc_local.py +31 -3
  23. package/agent-wallet/agent_wallet/providers/wdk_evm_local.py +37 -3
  24. package/agent-wallet/agent_wallet/providers/x402.py +4 -9
  25. package/agent-wallet/agent_wallet/transaction_policy.py +0 -262
  26. package/agent-wallet/agent_wallet/user_wallets.py +4 -3
  27. package/agent-wallet/agent_wallet/wallet_layer/base.py +3 -103
  28. package/agent-wallet/agent_wallet/wallet_layer/factory.py +8 -5
  29. package/agent-wallet/agent_wallet/wallet_layer/solana.py +453 -1177
  30. package/agent-wallet/agent_wallet/wallet_layer/wdk_btc.py +2 -8
  31. package/agent-wallet/agent_wallet/wallet_layer/wdk_evm.py +2 -12
  32. package/agent-wallet/examples/openclaw_runtime_onboarding.py +1 -1
  33. package/agent-wallet/examples/openclaw_user_wallet_example.py +1 -1
  34. package/agent-wallet/openclaw.plugin.json +1 -5
  35. package/agent-wallet/pyproject.toml +2 -1
  36. package/agent-wallet/scripts/bootstrap_openclaw_btc.py +3 -5
  37. package/agent-wallet/scripts/bootstrap_openclaw_evm.py +2 -12
  38. package/agent-wallet/scripts/build_release_bundle.py +1 -0
  39. package/agent-wallet/scripts/flash-sdk-bridge/bridge.mjs +1 -4
  40. package/agent-wallet/scripts/install_agent_wallet.py +114 -6
  41. package/agent-wallet/scripts/install_openclaw_local_config.py +10 -10
  42. package/agent-wallet/scripts/manage_openclaw_btc_wallet.py +2 -4
  43. package/agent-wallet/scripts/manage_openclaw_evm_wallet.py +2 -15
  44. package/agent-wallet/scripts/reveal_btc_seed.sh +7 -16
  45. package/agent-wallet/scripts/setup_btc_wallet.sh +7 -16
  46. package/agent-wallet/scripts/setup_evm_wallet.sh +1 -11
  47. package/agent-wallet/scripts/switch_openclaw_wallet_network.py +4 -1
  48. package/agent-wallet/skills/wallet-operator/SKILL.md +1 -6
  49. package/bin/openclaw-agent-wallet.mjs +356 -0
  50. package/claude-code/plugins/agent-wallet/.claude-plugin/plugin.json +20 -0
  51. package/claude-code/plugins/agent-wallet/.mcp.json +14 -0
  52. package/claude-code/plugins/agent-wallet/README.md +65 -0
  53. package/claude-code/plugins/agent-wallet/scripts/run_mcp.sh +39 -0
  54. package/claude-code/plugins/agent-wallet/skills/wallet-operator/SKILL.md +18 -0
  55. package/codex/plugins/agent-wallet/.codex-plugin/plugin.json +38 -0
  56. package/codex/plugins/agent-wallet/.mcp.json +15 -0
  57. package/codex/plugins/agent-wallet/README.md +39 -0
  58. package/codex/plugins/agent-wallet/scripts/run_mcp.sh +21 -0
  59. package/codex/plugins/agent-wallet/server.py +961 -0
  60. package/codex/plugins/agent-wallet/skills/wallet-operator/SKILL.md +18 -0
  61. package/hermes/plugins/agent_wallet/schemas.py +2 -2
  62. package/hermes/plugins/agent_wallet/tools.py +18 -4
  63. package/package.json +6 -1
  64. package/setup.sh +2 -0
  65. package/wdk-btc-wallet/src/local_vault.js +45 -68
  66. package/wdk-btc-wallet/src/server.js +1 -0
  67. package/wdk-evm-wallet/README.md +4 -3
  68. package/wdk-evm-wallet/src/config.js +15 -0
  69. package/wdk-evm-wallet/src/local_vault.js +45 -68
  70. package/wdk-evm-wallet/src/server.js +1 -0
  71. package/agent-wallet/agent_wallet/providers/houdini.py +0 -539
@@ -1,539 +0,0 @@
1
- """Houdini private swap provider helpers."""
2
-
3
- from __future__ import annotations
4
-
5
- import os
6
- import time
7
- from decimal import Decimal, InvalidOperation
8
- from typing import Any
9
-
10
- from agent_wallet.config import settings
11
- from agent_wallet.exceptions import ProviderError
12
- from agent_wallet.http_client import get_client
13
-
14
- _TOKEN_CACHE_TTL_SECONDS = 24 * 60 * 60
15
- _CEX_TOKEN_CACHE: dict[str, tuple[float, list[dict[str, Any]]]] = {}
16
- _SOLANA_NATIVE_ALIASES = {"sol", "native", "solana"}
17
-
18
-
19
- def _gateway_base_url() -> str:
20
- return os.getenv("PROVIDER_GATEWAY_URL", settings.provider_gateway_url).strip().rstrip("/")
21
-
22
-
23
- def _gateway_enabled() -> bool:
24
- return bool(_gateway_base_url())
25
-
26
-
27
- def _gateway_headers() -> dict[str, str]:
28
- headers = {
29
- "Accept": "application/json",
30
- "x-user-agent": settings.houdini_user_agent.strip() or "AgentLayer/0.1.12",
31
- "x-user-timezone": settings.houdini_user_timezone.strip() or "UTC",
32
- }
33
- bearer = os.getenv(
34
- "PROVIDER_GATEWAY_BEARER_TOKEN",
35
- settings.provider_gateway_bearer_token,
36
- ).strip()
37
- if bearer:
38
- headers["Authorization"] = f"Bearer {bearer}"
39
- return headers
40
-
41
-
42
- def _base_url() -> str:
43
- return settings.houdini_api_base_url.rstrip("/")
44
-
45
-
46
- def _direct_houdini_enabled() -> bool:
47
- return bool(settings.houdini_api_key.strip() and settings.houdini_api_secret.strip())
48
-
49
-
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
- def _normalize_error(payload: Any) -> str:
61
- if isinstance(payload, dict):
62
- message = payload.get("message") or payload.get("error") or payload.get("detail")
63
- if message:
64
- return str(message)
65
- return "Houdini request failed."
66
-
67
-
68
- def _response_text(response: Any) -> str:
69
- try:
70
- text = response.text
71
- except Exception:
72
- return ""
73
- return str(text or "")
74
-
75
-
76
- def _parse_json_response(response: Any, *, provider: str, operation: str) -> Any:
77
- body = _response_text(response)
78
- if not body.strip():
79
- raise ProviderError(provider, f"{operation} returned an empty response body.")
80
- try:
81
- return response.json()
82
- except ValueError as exc:
83
- snippet = body.strip().replace("\n", " ")[:200]
84
- detail = f": {snippet}" if snippet else ""
85
- raise ProviderError(
86
- provider,
87
- f"{operation} returned invalid JSON{detail}",
88
- ) from exc
89
-
90
-
91
- def _require_compliance_headers() -> dict[str, str]:
92
- api_key = settings.houdini_api_key.strip()
93
- api_secret = settings.houdini_api_secret.strip()
94
- if not api_key or not api_secret:
95
- raise ProviderError(
96
- "houdini",
97
- "Houdini API credentials are required. Set HOUDINI_API_KEY and HOUDINI_API_SECRET.",
98
- )
99
-
100
- user_ip = settings.houdini_user_ip.strip()
101
- if not user_ip:
102
- raise ProviderError(
103
- "houdini",
104
- "Houdini requires x-user-ip for compliance. Set HOUDINI_USER_IP first.",
105
- )
106
-
107
- user_agent = settings.houdini_user_agent.strip() or "AgentLayer/0.1.12"
108
- user_timezone = settings.houdini_user_timezone.strip() or "UTC"
109
- return {
110
- "Accept": "application/json",
111
- "Authorization": f"{api_key}:{api_secret}",
112
- "x-user-ip": user_ip,
113
- "x-user-agent": user_agent,
114
- "x-user-timezone": user_timezone,
115
- }
116
-
117
-
118
- def _normalize_decimal_text(amount: Decimal) -> str:
119
- text = format(amount, "f")
120
- if "." in text:
121
- text = text.rstrip("0").rstrip(".")
122
- return text or "0"
123
-
124
-
125
- def _to_decimal(value: Any, *, field_name: str) -> Decimal:
126
- try:
127
- amount = Decimal(str(value))
128
- except (InvalidOperation, TypeError, ValueError) as exc:
129
- raise ProviderError("houdini", f"{field_name} must be a valid decimal amount.") from exc
130
- if amount <= 0:
131
- raise ProviderError("houdini", f"{field_name} must be greater than zero.")
132
- return amount
133
-
134
-
135
- def _normalize_token_record(token: dict[str, Any]) -> dict[str, Any]:
136
- min_max = token.get("minMax") if isinstance(token.get("minMax"), dict) else {}
137
- private_limits = min_max.get("private") if isinstance(min_max.get("private"), dict) else {}
138
- return {
139
- "id": str(token.get("id") or "").strip(),
140
- "symbol": str(token.get("symbol") or "").strip(),
141
- "name": str(token.get("name") or "").strip(),
142
- "address": str(token.get("address") or "").strip(),
143
- "chain": str(token.get("chain") or "").strip().lower(),
144
- "decimals": token.get("decimals"),
145
- "enabled": bool(token.get("enabled", True)),
146
- "has_cex": bool(token.get("hasCex", False)),
147
- "price": token.get("price"),
148
- "min_max_private": private_limits,
149
- "raw": token,
150
- }
151
-
152
-
153
- def _unwrap_gateway_payload(
154
- status_code: int,
155
- payload: Any,
156
- *,
157
- operation: str,
158
- ) -> Any:
159
- if isinstance(payload, dict) and payload.get("ok") is False:
160
- message = str(payload.get("error") or f"{operation} failed.")
161
- raise ProviderError(
162
- "houdini-gateway",
163
- f"{operation} failed via provider gateway: {message}",
164
- details=payload,
165
- )
166
-
167
- if status_code != 200:
168
- message = payload
169
- if isinstance(payload, dict):
170
- message = payload.get("error") or payload
171
- raise ProviderError(
172
- "houdini-gateway",
173
- f"{operation} failed via provider gateway: {message}",
174
- details=payload if isinstance(payload, dict) else None,
175
- )
176
-
177
- return payload
178
-
179
-
180
- async def _gateway_get(
181
- path: str,
182
- *,
183
- params: dict[str, Any] | None,
184
- operation: str,
185
- ) -> Any:
186
- client = get_client()
187
- response = await client.get(
188
- f"{_gateway_base_url()}{path}",
189
- params=params,
190
- headers=_gateway_headers(),
191
- )
192
- payload = _parse_json_response(response, provider="houdini-gateway", operation=operation)
193
- if _gateway_route_missing(response.status_code, payload) and _direct_houdini_enabled():
194
- return None
195
- return _unwrap_gateway_payload(
196
- response.status_code,
197
- payload,
198
- operation=operation,
199
- )
200
-
201
-
202
- async def _gateway_post(
203
- path: str,
204
- *,
205
- body: dict[str, Any],
206
- operation: str,
207
- ) -> Any:
208
- client = get_client()
209
- response = await client.post(
210
- f"{_gateway_base_url()}{path}",
211
- json=body,
212
- headers={**_gateway_headers(), "Content-Type": "application/json"},
213
- )
214
- payload = _parse_json_response(response, provider="houdini-gateway", operation=operation)
215
- if _gateway_route_missing(response.status_code, payload) and _direct_houdini_enabled():
216
- return None
217
- return _unwrap_gateway_payload(
218
- response.status_code,
219
- payload,
220
- operation=operation,
221
- )
222
-
223
-
224
- async def fetch_cex_tokens(*, chain: str) -> list[dict[str, Any]]:
225
- normalized_chain = str(chain or "").strip().lower()
226
- if not normalized_chain:
227
- raise ProviderError("houdini", "chain is required when fetching Houdini tokens.")
228
-
229
- cached = _CEX_TOKEN_CACHE.get(normalized_chain)
230
- now = time.time()
231
- if cached and now - cached[0] < _TOKEN_CACHE_TTL_SECONDS:
232
- return cached[1]
233
-
234
- tokens = await _fetch_cex_tokens_uncached(normalized_chain)
235
-
236
- _CEX_TOKEN_CACHE[normalized_chain] = (now, tokens)
237
- return tokens
238
-
239
-
240
- async def _fetch_cex_tokens_uncached(chain: str) -> list[dict[str, Any]]:
241
- page = 1
242
- tokens: list[dict[str, Any]] = []
243
- client = get_client()
244
- while True:
245
- gateway_payload = None
246
- if _gateway_enabled():
247
- gateway_payload = await _gateway_get(
248
- "/v1/houdini/tokens",
249
- params={
250
- "hasCex": "true",
251
- "chain": chain,
252
- "pageSize": 100,
253
- "page": page,
254
- },
255
- operation="Houdini tokens",
256
- )
257
- if gateway_payload is not None:
258
- payload = gateway_payload
259
- else:
260
- response = await client.get(
261
- f"{_base_url()}/tokens",
262
- params={
263
- "hasCex": "true",
264
- "chain": chain,
265
- "pageSize": 100,
266
- "page": page,
267
- },
268
- headers=_require_compliance_headers(),
269
- )
270
- payload = _parse_json_response(response, provider="houdini", operation="Houdini tokens")
271
- if response.status_code != 200:
272
- raise ProviderError("houdini", f"HTTP {response.status_code}: {_normalize_error(payload)}")
273
- page_tokens = payload.get("tokens")
274
- if not isinstance(page_tokens, list):
275
- raise ProviderError("houdini", "Unexpected tokens response from Houdini.")
276
- tokens.extend(
277
- _normalize_token_record(item)
278
- for item in page_tokens
279
- if isinstance(item, dict) and item.get("enabled", True)
280
- )
281
- total_pages = int(payload.get("totalPages") or 1)
282
- if page >= total_pages:
283
- break
284
- page += 1
285
- return tokens
286
-
287
-
288
- def _token_match_rank(term: str, token: dict[str, Any]) -> tuple[int, int, str]:
289
- lowered = term.strip().lower()
290
- symbol = str(token.get("symbol") or "").strip().lower()
291
- name = str(token.get("name") or "").strip().lower()
292
- address = str(token.get("address") or "").strip().lower()
293
- token_id = str(token.get("id") or "").strip().lower()
294
-
295
- if lowered == token_id:
296
- return (0, 0, symbol)
297
- if lowered == address:
298
- return (1, 0, symbol)
299
- if lowered == symbol:
300
- return (2, 0, symbol)
301
- if lowered == name:
302
- return (3, 0, symbol)
303
- if lowered in name or lowered in symbol or lowered in address:
304
- return (4, len(name), symbol)
305
- return (9, len(name), symbol)
306
-
307
-
308
- async def resolve_cex_token(*, term: str, chain: str) -> dict[str, Any]:
309
- normalized_term = str(term or "").strip()
310
- if not normalized_term:
311
- raise ProviderError("houdini", "token term is required.")
312
-
313
- tokens = await fetch_cex_tokens(chain=chain)
314
- candidates = [
315
- token
316
- for token in tokens
317
- if token.get("enabled", True) and token.get("has_cex", True)
318
- ]
319
- ranked = sorted(
320
- enumerate(candidates),
321
- key=lambda pair: (_token_match_rank(normalized_term, pair[1]), pair[0]),
322
- )
323
- if not ranked or ranked[0][1] is None:
324
- raise ProviderError(
325
- "houdini",
326
- f"Houdini does not expose a CEX token match for '{normalized_term}' on {chain}.",
327
- )
328
- best_rank = _token_match_rank(normalized_term, ranked[0][1])
329
- if best_rank[0] >= 9:
330
- raise ProviderError(
331
- "houdini",
332
- f"Houdini does not expose a CEX token match for '{normalized_term}' on {chain}.",
333
- )
334
- resolved = ranked[0][1]
335
- if resolved.get("chain") != str(chain).strip().lower():
336
- raise ProviderError(
337
- "houdini",
338
- f"Houdini token '{normalized_term}' resolved to the wrong chain ({resolved.get('chain')}).",
339
- )
340
- return resolved
341
-
342
-
343
- async def fetch_private_quotes(
344
- *,
345
- from_token_id: str,
346
- to_token_id: str,
347
- amount_ui: Decimal,
348
- ) -> list[dict[str, Any]]:
349
- payload = None
350
- if _gateway_enabled():
351
- payload = await _gateway_get(
352
- "/v1/houdini/quotes/private",
353
- params={
354
- "from": from_token_id,
355
- "to": to_token_id,
356
- "amount": _normalize_decimal_text(amount_ui),
357
- },
358
- operation="Houdini private quotes",
359
- )
360
- if payload is None:
361
- client = get_client()
362
- response = await client.get(
363
- f"{_base_url()}/quotes",
364
- params={
365
- "from": from_token_id,
366
- "to": to_token_id,
367
- "amount": _normalize_decimal_text(amount_ui),
368
- "types": "private",
369
- },
370
- headers=_require_compliance_headers(),
371
- )
372
- payload = _parse_json_response(response, provider="houdini", operation="Houdini private quotes")
373
- if response.status_code != 200:
374
- raise ProviderError("houdini", f"HTTP {response.status_code}: {_normalize_error(payload)}")
375
- quotes = payload.get("quotes")
376
- if not isinstance(quotes, list):
377
- raise ProviderError("houdini", "Unexpected quotes response from Houdini.")
378
- private_quotes = [
379
- item
380
- for item in quotes
381
- if isinstance(item, dict) and str(item.get("type") or "").strip().lower() == "private"
382
- ]
383
- if not private_quotes:
384
- raise ProviderError("houdini", "No private Houdini quote is available for this route.")
385
- return private_quotes
386
-
387
-
388
- def select_best_private_quote(quotes: list[dict[str, Any]]) -> dict[str, Any]:
389
- if not quotes:
390
- raise ProviderError("houdini", "No private Houdini quote is available for this route.")
391
-
392
- def _sort_key(item: dict[str, Any]) -> tuple[Decimal, Decimal]:
393
- amount_out = _to_decimal(item.get("amountOut"), field_name="amountOut")
394
- try:
395
- duration = Decimal(str(item.get("duration") or 0))
396
- except (InvalidOperation, TypeError, ValueError):
397
- duration = Decimal("0")
398
- return (amount_out, Decimal("-1") * duration)
399
-
400
- return max(quotes, key=_sort_key)
401
-
402
-
403
- async def create_multi_swap(
404
- *,
405
- orders: list[dict[str, Any]],
406
- ) -> dict[str, Any]:
407
- if not orders:
408
- raise ProviderError("houdini", "orders are required.")
409
- payload = None
410
- if _gateway_enabled():
411
- payload = await _gateway_post(
412
- "/v1/houdini/exchanges/multi",
413
- body={"orders": orders},
414
- operation="Houdini multi create",
415
- )
416
- if payload is None:
417
- client = get_client()
418
- response = await client.post(
419
- f"{_base_url()}/exchanges/multi",
420
- json={"orders": orders},
421
- headers={**_require_compliance_headers(), "Content-Type": "application/json"},
422
- )
423
- payload = _parse_json_response(response, provider="houdini", operation="Houdini multi create")
424
- if response.status_code != 200:
425
- raise ProviderError("houdini", f"HTTP {response.status_code}: {_normalize_error(payload)}")
426
- if not isinstance(payload, dict) or not isinstance(payload.get("orders"), list):
427
- raise ProviderError("houdini", "Unexpected multi-swap response from Houdini.")
428
- return payload
429
-
430
-
431
- async def create_exchange(
432
- *,
433
- quote_id: str,
434
- destination_address: str,
435
- ) -> dict[str, Any]:
436
- normalized_quote_id = str(quote_id).strip()
437
- normalized_destination = str(destination_address).strip()
438
- if not normalized_quote_id:
439
- raise ProviderError("houdini", "quote_id is required.")
440
- if not normalized_destination:
441
- raise ProviderError("houdini", "destination_address is required.")
442
-
443
- body = {
444
- "quoteId": normalized_quote_id,
445
- "addressTo": normalized_destination,
446
- }
447
- payload = None
448
- if _gateway_enabled():
449
- payload = await _gateway_post(
450
- "/v1/houdini/exchanges",
451
- body=body,
452
- operation="Houdini exchange create",
453
- )
454
- if payload is None:
455
- client = get_client()
456
- response = await client.post(
457
- f"{_base_url()}/exchanges",
458
- json=body,
459
- headers={**_require_compliance_headers(), "Content-Type": "application/json"},
460
- )
461
- payload = _parse_json_response(response, provider="houdini", operation="Houdini exchange create")
462
- if response.status_code != 200:
463
- raise ProviderError("houdini", f"HTTP {response.status_code}: {_normalize_error(payload)}")
464
- if not isinstance(payload, dict) or not payload:
465
- raise ProviderError("houdini", "Unexpected exchange response from Houdini.")
466
- return payload
467
-
468
-
469
- async def fetch_multi_status(*, multi_id: str) -> dict[str, Any]:
470
- payload = None
471
- normalized_multi_id = str(multi_id).strip()
472
- if _gateway_enabled():
473
- payload = await _gateway_get(
474
- f"/v1/houdini/exchanges/multi/{normalized_multi_id}",
475
- params=None,
476
- operation="Houdini multi status",
477
- )
478
- if payload is None:
479
- client = get_client()
480
- response = await client.get(
481
- f"{_base_url()}/exchanges/multi/{normalized_multi_id}",
482
- headers=_require_compliance_headers(),
483
- )
484
- payload = _parse_json_response(response, provider="houdini", operation="Houdini multi status")
485
- if response.status_code != 200:
486
- raise ProviderError("houdini", f"HTTP {response.status_code}: {_normalize_error(payload)}")
487
- if not isinstance(payload, dict) or not isinstance(payload.get("orders"), list):
488
- raise ProviderError("houdini", "Unexpected multi-status response from Houdini.")
489
- return payload
490
-
491
-
492
- async def fetch_order_status(*, houdini_id: str) -> dict[str, Any]:
493
- payload = None
494
- normalized_houdini_id = str(houdini_id).strip()
495
- if not normalized_houdini_id:
496
- raise ProviderError("houdini", "houdini_id is required.")
497
- if _gateway_enabled():
498
- payload = await _gateway_get(
499
- f"/v1/houdini/orders/{normalized_houdini_id}",
500
- params=None,
501
- operation="Houdini order status",
502
- )
503
- if payload is None:
504
- client = get_client()
505
- response = await client.get(
506
- f"{_base_url()}/orders/{normalized_houdini_id}",
507
- headers=_require_compliance_headers(),
508
- )
509
- payload = _parse_json_response(response, provider="houdini", operation="Houdini order status")
510
- if response.status_code != 200:
511
- raise ProviderError("houdini", f"HTTP {response.status_code}: {_normalize_error(payload)}")
512
- if not isinstance(payload, dict) or not payload:
513
- raise ProviderError("houdini", "Unexpected order-status response from Houdini.")
514
- return payload
515
-
516
-
517
- async def fetch_multi_solana_transactions(*, multi_id: str, sender: str) -> dict[str, Any]:
518
- payload = None
519
- normalized_multi_id = str(multi_id).strip()
520
- normalized_sender = str(sender).strip()
521
- if _gateway_enabled():
522
- payload = await _gateway_get(
523
- f"/v1/houdini/exchanges/multi/{normalized_multi_id}/tx",
524
- params={"sender": normalized_sender},
525
- operation="Houdini multi tx",
526
- )
527
- if payload is None:
528
- client = get_client()
529
- response = await client.get(
530
- f"{_base_url()}/exchanges/multi/{normalized_multi_id}/tx",
531
- params={"sender": normalized_sender},
532
- headers=_require_compliance_headers(),
533
- )
534
- payload = _parse_json_response(response, provider="houdini", operation="Houdini multi tx")
535
- if response.status_code != 200:
536
- raise ProviderError("houdini", f"HTTP {response.status_code}: {_normalize_error(payload)}")
537
- if not isinstance(payload, dict) or not isinstance(payload.get("transactions"), list):
538
- raise ProviderError("houdini", "Unexpected Solana batch transaction response from Houdini.")
539
- return payload