wayfinder-paths 0.1.24__py3-none-any.whl → 0.1.27__py3-none-any.whl

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.

Potentially problematic release.


This version of wayfinder-paths might be problematic. Click here for more details.

Files changed (44) hide show
  1. wayfinder_paths/__init__.py +2 -0
  2. wayfinder_paths/adapters/brap_adapter/adapter.py +7 -47
  3. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +10 -31
  4. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +128 -60
  5. wayfinder_paths/adapters/hyperliquid_adapter/exchange.py +399 -0
  6. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +74 -0
  7. wayfinder_paths/adapters/hyperliquid_adapter/local_signer.py +82 -0
  8. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +1 -1
  9. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
  10. wayfinder_paths/adapters/hyperliquid_adapter/util.py +237 -0
  11. wayfinder_paths/adapters/pendle_adapter/adapter.py +19 -55
  12. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +14 -46
  13. wayfinder_paths/core/__init__.py +2 -0
  14. wayfinder_paths/core/clients/BalanceClient.py +72 -0
  15. wayfinder_paths/core/clients/TokenClient.py +1 -1
  16. wayfinder_paths/core/clients/__init__.py +2 -0
  17. wayfinder_paths/core/strategies/Strategy.py +3 -3
  18. wayfinder_paths/core/types.py +19 -0
  19. wayfinder_paths/core/utils/tokens.py +19 -1
  20. wayfinder_paths/core/utils/transaction.py +9 -7
  21. wayfinder_paths/mcp/tools/balances.py +122 -214
  22. wayfinder_paths/mcp/tools/execute.py +63 -41
  23. wayfinder_paths/mcp/tools/quotes.py +16 -5
  24. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +6 -22
  25. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +227 -33
  26. wayfinder_paths/strategies/boros_hype_strategy/constants.py +17 -1
  27. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +44 -1
  28. wayfinder_paths/strategies/boros_hype_strategy/planner.py +87 -32
  29. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +50 -28
  30. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +71 -50
  31. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +3 -1
  32. wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +0 -2
  33. wayfinder_paths/strategies/boros_hype_strategy/types.py +4 -1
  34. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +0 -2
  35. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -2
  36. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +0 -2
  37. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +0 -2
  38. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -2
  39. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -2
  40. wayfinder_paths/tests/test_mcp_quote_swap.py +3 -3
  41. {wayfinder_paths-0.1.24.dist-info → wayfinder_paths-0.1.27.dist-info}/METADATA +2 -3
  42. {wayfinder_paths-0.1.24.dist-info → wayfinder_paths-0.1.27.dist-info}/RECORD +44 -39
  43. {wayfinder_paths-0.1.24.dist-info → wayfinder_paths-0.1.27.dist-info}/WHEEL +1 -1
  44. {wayfinder_paths-0.1.24.dist-info → wayfinder_paths-0.1.27.dist-info}/LICENSE +0 -0
@@ -3,93 +3,10 @@ from __future__ import annotations
3
3
  import asyncio
4
4
  from typing import Any, Literal
5
5
 
6
- import httpx
7
-
8
- from wayfinder_paths.core.config import get_api_base_url, get_api_key
9
- from wayfinder_paths.core.constants.base import DEFAULT_HTTP_TIMEOUT
6
+ from wayfinder_paths.core.clients import BalanceClient
10
7
  from wayfinder_paths.mcp.utils import err, find_wallet_by_label, normalize_address, ok
11
8
 
12
9
 
13
- class _BalanceClient:
14
- def __init__(self):
15
- self.api_base_url = get_api_base_url()
16
- timeout = httpx.Timeout(DEFAULT_HTTP_TIMEOUT)
17
- self.client = httpx.AsyncClient(timeout=timeout)
18
- self._headers = {"Content-Type": "application/json"}
19
- api_key = get_api_key()
20
- if api_key:
21
- self._headers["X-API-KEY"] = api_key
22
-
23
- async def close(self):
24
- await self.client.aclose()
25
-
26
- async def get_enriched_wallet_balances(
27
- self,
28
- *,
29
- wallet_address: str,
30
- exclude_spam_tokens: bool = True,
31
- ) -> dict:
32
- url = f"{self.api_base_url}/blockchain/balances/enriched/"
33
- params = {
34
- "address": wallet_address,
35
- "exclude_spam_tokens": str(exclude_spam_tokens).lower(),
36
- }
37
- response = await self.client.get(url, params=params, headers=self._headers)
38
- response.raise_for_status()
39
- return response.json()
40
-
41
- async def get_wallet_activity(
42
- self,
43
- *,
44
- wallet_address: str,
45
- limit: int = 20,
46
- offset: str | None = None,
47
- ) -> dict:
48
- url = f"{self.api_base_url}/blockchain/balances/activity/"
49
- params: dict[str, str | int] = {"address": wallet_address, "limit": limit}
50
- if offset:
51
- params["offset"] = offset
52
- response = await self.client.get(url, params=params, headers=self._headers)
53
- response.raise_for_status()
54
- return response.json()
55
-
56
- async def get_token_balance(
57
- self,
58
- *,
59
- wallet_address: str,
60
- token_id: str,
61
- human_readable: bool = True,
62
- ) -> dict:
63
- url = f"{self.api_base_url}/public/balances/token/"
64
- params = {
65
- "wallet_address": wallet_address,
66
- "token_id": token_id,
67
- "human_readable": str(human_readable).lower(),
68
- }
69
- response = await self.client.get(url, params=params, headers=self._headers)
70
- response.raise_for_status()
71
- return response.json()
72
-
73
- async def get_pool_balance(
74
- self,
75
- *,
76
- pool_address: str,
77
- chain_id: int,
78
- user_address: str,
79
- human_readable: bool = True,
80
- ) -> dict:
81
- url = f"{self.api_base_url}/public/balances/pool/"
82
- params = {
83
- "pool_address": pool_address,
84
- "chain_id": chain_id,
85
- "user_address": user_address,
86
- "human_readable": str(human_readable).lower(),
87
- }
88
- response = await self.client.get(url, params=params, headers=self._headers)
89
- response.raise_for_status()
90
- return response.json()
91
-
92
-
93
10
  def _dedupe_ordered(items: list[str]) -> list[str]:
94
11
  seen: set[str] = set()
95
12
  out: list[str] = []
@@ -145,146 +62,137 @@ async def balances(
145
62
  "invalid_request", "wallet_label or wallet_address is required for balances"
146
63
  )
147
64
 
148
- client = _BalanceClient()
65
+ client = BalanceClient()
149
66
 
150
- try:
151
- if action == "enriched":
152
- try:
153
- data = await client.get_enriched_wallet_balances(
154
- wallet_address=waddr,
155
- exclude_spam_tokens=bool(exclude_spam_tokens),
156
- )
157
- if (
158
- not include_solana
159
- and _is_evm_address(waddr)
160
- and isinstance(data, dict)
161
- and isinstance(data.get("balances"), list)
162
- ):
163
- balances_list = [b for b in data["balances"] if isinstance(b, dict)]
164
- filtered = [
165
- b
166
- for b in balances_list
167
- if str(b.get("network", "")).lower() != "solana"
168
- ]
169
- if len(filtered) != len(balances_list):
170
- out = dict(data)
171
- out["balances"] = filtered
172
- out["total_balance_usd"] = sum(
173
- _balance_usd(b) for b in filtered
174
- )
175
- breakdown: dict[str, float] = {}
176
- for b in filtered:
177
- net = str(b.get("network") or "").strip()
178
- if not net:
179
- continue
180
- breakdown[net] = breakdown.get(net, 0.0) + _balance_usd(b)
181
- out["chain_breakdown"] = breakdown
182
- out["filtered"] = {
183
- "excluded_networks": ["solana"],
184
- "original_count": len(balances_list),
185
- "filtered_count": len(filtered),
186
- }
187
- data = out
67
+ if action == "enriched":
68
+ try:
69
+ data = await client.get_enriched_wallet_balances(
70
+ wallet_address=waddr,
71
+ exclude_spam_tokens=bool(exclude_spam_tokens),
72
+ )
73
+ if (
74
+ not include_solana
75
+ and _is_evm_address(waddr)
76
+ and isinstance(data, dict)
77
+ and isinstance(data.get("balances"), list)
78
+ ):
79
+ balances_list = [b for b in data["balances"] if isinstance(b, dict)]
80
+ filtered = [
81
+ b
82
+ for b in balances_list
83
+ if str(b.get("network", "")).lower() != "solana"
84
+ ]
85
+ if len(filtered) != len(balances_list):
86
+ out = dict(data)
87
+ out["balances"] = filtered
88
+ out["total_balance_usd"] = sum(_balance_usd(b) for b in filtered)
89
+ breakdown: dict[str, float] = {}
90
+ for b in filtered:
91
+ net = str(b.get("network") or "").strip()
92
+ if not net:
93
+ continue
94
+ breakdown[net] = breakdown.get(net, 0.0) + _balance_usd(b)
95
+ out["chain_breakdown"] = breakdown
96
+ out["filtered"] = {
97
+ "excluded_networks": ["solana"],
98
+ "original_count": len(balances_list),
99
+ "filtered_count": len(filtered),
100
+ }
101
+ data = out
188
102
 
189
- return ok(data)
190
- except Exception as exc: # noqa: BLE001
191
- return err("balance_error", str(exc))
103
+ return ok(data)
104
+ except Exception as exc: # noqa: BLE001
105
+ return err("balance_error", str(exc))
106
+
107
+ if action == "activity":
108
+ try:
109
+ data = await client.get_wallet_activity(
110
+ wallet_address=waddr,
111
+ limit=int(limit),
112
+ offset=offset,
113
+ )
114
+ return ok(
115
+ {
116
+ "wallet_address": waddr,
117
+ "activity": data.get("activity", []),
118
+ "next_offset": data.get("next_offset"),
119
+ }
120
+ )
121
+ except Exception as exc: # noqa: BLE001
122
+ return err("activity_error", str(exc))
123
+
124
+ if action == "token":
125
+ raw_ids: list[str] = []
126
+ if token_id:
127
+ raw_ids.append(str(token_id).strip())
128
+ if token_ids:
129
+ raw_ids.extend(str(x).strip() for x in token_ids)
130
+
131
+ ids = _dedupe_ordered([x for x in raw_ids if x])
132
+ if not ids:
133
+ return err(
134
+ "invalid_request",
135
+ "token_id or token_ids is required for balances(action=token)",
136
+ )
192
137
 
193
- if action == "activity":
138
+ async def _one(tid: str) -> dict[str, Any]:
194
139
  try:
195
- data = await client.get_wallet_activity(
140
+ data = await client.get_token_balance(
141
+ token_id=tid,
196
142
  wallet_address=waddr,
197
- limit=int(limit),
198
- offset=offset,
199
- )
200
- return ok(
201
- {
202
- "wallet_address": waddr,
203
- "activity": data.get("activity", []),
204
- "next_offset": data.get("next_offset"),
205
- }
143
+ human_readable=bool(human_readable),
206
144
  )
145
+ return {"ok": True, "token_id": tid, "data": data}
207
146
  except Exception as exc: # noqa: BLE001
208
- return err("activity_error", str(exc))
209
-
210
- if action == "token":
211
- raw_ids: list[str] = []
212
- if token_id:
213
- raw_ids.append(str(token_id).strip())
214
- if token_ids:
215
- raw_ids.extend(str(x).strip() for x in token_ids)
216
-
217
- ids = _dedupe_ordered([x for x in raw_ids if x])
218
- if not ids:
219
- return err(
220
- "invalid_request",
221
- "token_id or token_ids is required for balances(action=token)",
222
- )
147
+ return {"ok": False, "token_id": tid, "error": str(exc)}
223
148
 
224
- async def _one(tid: str) -> dict[str, Any]:
225
- try:
226
- data = await client.get_token_balance(
227
- token_id=tid,
228
- wallet_address=waddr,
229
- human_readable=bool(human_readable),
230
- )
231
- return {"ok": True, "token_id": tid, "data": data}
232
- except Exception as exc: # noqa: BLE001
233
- return {"ok": False, "token_id": tid, "error": str(exc)}
149
+ results = await asyncio.gather(*[_one(tid) for tid in ids])
150
+ return ok({"wallet_address": waddr, "balances": results})
234
151
 
235
- results = await asyncio.gather(*[_one(tid) for tid in ids])
236
- return ok({"wallet_address": waddr, "balances": results})
237
-
238
- if action == "pool":
239
- if chain_id is None:
240
- return err(
241
- "invalid_request", "chain_id is required for balances(action=pool)"
242
- )
243
-
244
- raw_pools: list[str] = []
245
- if pool_address:
246
- raw_pools.append(str(pool_address).strip())
247
- if pool_addresses:
248
- raw_pools.extend(str(x).strip() for x in pool_addresses)
249
-
250
- pools = _dedupe_ordered([x for x in raw_pools if x])
251
- if not pools:
252
- return err(
253
- "invalid_request",
254
- "pool_address or pool_addresses is required for balances(action=pool)",
255
- )
256
-
257
- async def _one_pool(paddr: str) -> dict[str, Any]:
258
- try:
259
- data = await client.get_pool_balance(
260
- pool_address=paddr,
261
- chain_id=int(chain_id), # type: ignore[arg-type]
262
- user_address=waddr,
263
- human_readable=bool(human_readable),
264
- )
265
- return {
266
- "ok": True,
267
- "pool_address": paddr,
268
- "chain_id": int(chain_id), # type: ignore[arg-type]
269
- "data": data,
270
- }
271
- except Exception as exc: # noqa: BLE001
272
- return {
273
- "ok": False,
274
- "pool_address": paddr,
275
- "chain_id": int(chain_id), # type: ignore[arg-type]
276
- "error": str(exc),
277
- }
152
+ if action == "pool":
153
+ if chain_id is None:
154
+ return err(
155
+ "invalid_request", "chain_id is required for balances(action=pool)"
156
+ )
278
157
 
279
- results = await asyncio.gather(*[_one_pool(p) for p in pools])
280
- return ok(
281
- {"wallet_address": waddr, "chain_id": int(chain_id), "pools": results}
158
+ raw_pools: list[str] = []
159
+ if pool_address:
160
+ raw_pools.append(str(pool_address).strip())
161
+ if pool_addresses:
162
+ raw_pools.extend(str(x).strip() for x in pool_addresses)
163
+
164
+ pools = _dedupe_ordered([x for x in raw_pools if x])
165
+ if not pools:
166
+ return err(
167
+ "invalid_request",
168
+ "pool_address or pool_addresses is required for balances(action=pool)",
282
169
  )
283
170
 
284
- return err("invalid_request", f"Unknown balances action: {action}")
171
+ async def _one_pool(paddr: str) -> dict[str, Any]:
172
+ try:
173
+ data = await client.get_pool_balance(
174
+ pool_address=paddr,
175
+ chain_id=int(chain_id), # type: ignore[arg-type]
176
+ user_address=waddr,
177
+ human_readable=bool(human_readable),
178
+ )
179
+ return {
180
+ "ok": True,
181
+ "pool_address": paddr,
182
+ "chain_id": int(chain_id), # type: ignore[arg-type]
183
+ "data": data,
184
+ }
185
+ except Exception as exc: # noqa: BLE001
186
+ return {
187
+ "ok": False,
188
+ "pool_address": paddr,
189
+ "chain_id": int(chain_id), # type: ignore[arg-type]
190
+ "error": str(exc),
191
+ }
192
+
193
+ results = await asyncio.gather(*[_one_pool(p) for p in pools])
194
+ return ok(
195
+ {"wallet_address": waddr, "chain_id": int(chain_id), "pools": results}
196
+ )
285
197
 
286
- finally:
287
- try:
288
- await client.close()
289
- except Exception: # noqa: BLE001
290
- pass
198
+ return err("invalid_request", f"Unknown balances action: {action}")
@@ -13,7 +13,6 @@ from wayfinder_paths.core.clients.BRAPClient import BRAPClient
13
13
  from wayfinder_paths.core.clients.TokenClient import TokenClient
14
14
  from wayfinder_paths.core.constants import ZERO_ADDRESS
15
15
  from wayfinder_paths.core.constants.chains import CHAIN_CODE_TO_ID
16
- from wayfinder_paths.core.constants.contracts import TOKENS_REQUIRING_APPROVAL_RESET
17
16
  from wayfinder_paths.core.constants.erc20_abi import ERC20_ABI
18
17
  from wayfinder_paths.core.constants.hyperliquid import (
19
18
  ARBITRUM_USDC_ADDRESS,
@@ -21,8 +20,7 @@ from wayfinder_paths.core.constants.hyperliquid import (
21
20
  HYPERLIQUID_BRIDGE_ADDRESS,
22
21
  )
23
22
  from wayfinder_paths.core.utils.tokens import (
24
- build_approve_transaction,
25
- get_token_allowance,
23
+ ensure_allowance,
26
24
  )
27
25
  from wayfinder_paths.core.utils.transaction import send_transaction
28
26
  from wayfinder_paths.mcp.preview import build_execution_preview
@@ -108,10 +106,6 @@ class ExecutionRequest(BaseModel):
108
106
  return self
109
107
 
110
108
 
111
- def _slippage_float(bps: int) -> float:
112
- return max(0.0, float(int(bps)) / 10_000.0)
113
-
114
-
115
109
  def _addr_lower(addr: str | None) -> str | None:
116
110
  if not addr:
117
111
  return None
@@ -257,6 +251,59 @@ def _sanitize_for_json(obj: Any) -> Any:
257
251
  return obj
258
252
 
259
253
 
254
+ def _compact_quote(
255
+ quote_data: dict[str, Any], best_quote: dict[str, Any] | None
256
+ ) -> dict[str, Any]:
257
+ """Create a compact summary of quote data, stripping verbose nested structures."""
258
+ result: dict[str, Any] = {}
259
+
260
+ # Extract provider list from all_quotes
261
+ all_quotes = quote_data.get("quotes", {}).get("all_quotes", [])
262
+ if isinstance(all_quotes, list):
263
+ result["providers"] = list(
264
+ {
265
+ q.get("provider")
266
+ for q in all_quotes
267
+ if isinstance(q, dict) and q.get("provider")
268
+ }
269
+ )
270
+ result["quote_count"] = len(all_quotes)
271
+
272
+ # Compact best_quote - only essential fields
273
+ if isinstance(best_quote, dict):
274
+ result["best"] = {
275
+ "provider": best_quote.get("provider"),
276
+ "input_amount": best_quote.get("input_amount"),
277
+ "output_amount": best_quote.get("output_amount"),
278
+ "input_usd": best_quote.get("input_amount_usd"),
279
+ "output_usd": best_quote.get("output_amount_usd"),
280
+ }
281
+ # Compact fee info
282
+ fee = best_quote.get("fee_estimate")
283
+ if isinstance(fee, dict):
284
+ result["best"]["fee_usd"] = fee.get("fee_total_usd")
285
+ # Extract route summary (just protocol names)
286
+ quote_inner = best_quote.get("quote", {})
287
+ if isinstance(quote_inner, dict):
288
+ route = quote_inner.get("route", [])
289
+ if isinstance(route, list):
290
+ result["best"]["route"] = [
291
+ r.get("protocol")
292
+ for r in route
293
+ if isinstance(r, dict) and r.get("protocol")
294
+ ]
295
+ # Or from includedSteps
296
+ steps = quote_inner.get("includedSteps", [])
297
+ if isinstance(steps, list) and not result["best"].get("route"):
298
+ result["best"]["route"] = [
299
+ s.get("tool")
300
+ for s in steps
301
+ if isinstance(s, dict) and s.get("tool")
302
+ ]
303
+
304
+ return result
305
+
306
+
260
307
  def _is_dry_run() -> bool:
261
308
  return os.getenv("DRY_RUN", "").lower() in ("1", "true")
262
309
 
@@ -298,41 +345,16 @@ async def _ensure_allowance(
298
345
  # May emit 0-allowance clear tx for tokens requiring approval reset (e.g. USDT)
299
346
  effects: list[dict[str, Any]] = []
300
347
 
301
- allowance = await get_token_allowance(
348
+ sent_ok, sent = await ensure_allowance(
302
349
  token_address=token_address,
350
+ owner=owner,
351
+ spender=spender,
352
+ amount=amount,
303
353
  chain_id=chain_id,
304
- owner_address=owner,
305
- spender_address=spender,
354
+ signing_callback=sign_callback,
306
355
  )
307
-
308
- token_checksum = to_checksum_address(token_address)
309
- if (chain_id, token_checksum) in TOKENS_REQUIRING_APPROVAL_RESET and allowance > 0:
310
- clear_tx = await build_approve_transaction(
311
- from_address=owner,
312
- chain_id=chain_id,
313
- token_address=token_checksum,
314
- spender_address=spender,
315
- amount=0,
316
- )
317
- sent_ok, sent = await _broadcast(sign_callback, clear_tx, chain_id=chain_id)
318
- effects.append({"type": "tx", "label": "approve_clear", **sent})
319
- if not sent_ok:
320
- return False, effects
321
- # After clear, we will still submit a fresh approve below.
322
-
323
- if allowance >= int(amount):
324
- return True, effects
325
-
326
- approve_tx = await build_approve_transaction(
327
- from_address=owner,
328
- chain_id=chain_id,
329
- token_address=token_checksum,
330
- spender_address=spender,
331
- amount=int(amount),
332
- )
333
-
334
- sent_ok, sent = await _broadcast(sign_callback, approve_tx, chain_id=chain_id)
335
- effects.append({"type": "tx", "label": "approve", **sent})
356
+ if isinstance(sent, dict) and sent:
357
+ effects.append({"type": "tx", "label": "approve", **sent})
336
358
  return sent_ok, effects
337
359
 
338
360
 
@@ -550,7 +572,7 @@ async def execute(
550
572
  "recipient": rcpt,
551
573
  "preview": preview_text,
552
574
  "effects": effects,
553
- "raw": {"quote": quote_data},
575
+ "raw": _compact_quote(quote_data, None),
554
576
  }
555
577
  )
556
578
  store.put(key, tool_input, response)
@@ -576,7 +598,7 @@ async def execute(
576
598
  "recipient": rcpt,
577
599
  "preview": preview_text,
578
600
  "effects": effects,
579
- "raw": {"quote": quote_data, "best_quote": best_quote},
601
+ "raw": _compact_quote(quote_data, best_quote),
580
602
  }
581
603
  )
582
604
  store.put(key, tool_input, response)
@@ -196,10 +196,10 @@ async def quote_swap(
196
196
  data = await brap_client.get_quote(
197
197
  from_token=from_token_addr,
198
198
  to_token=to_token_addr,
199
- from_chain_id=from_chain_id,
200
- to_chain_id=to_chain_id,
199
+ from_chain=from_chain_id,
200
+ to_chain=to_chain_id,
201
201
  from_wallet=sender,
202
- amount1=str(amount_raw),
202
+ from_amount=str(amount_raw),
203
203
  slippage=slip,
204
204
  )
205
205
  except Exception as exc: # noqa: BLE001
@@ -244,9 +244,20 @@ async def quote_swap(
244
244
  "native_input": best_quote.get("native_input"),
245
245
  "native_output": best_quote.get("native_output"),
246
246
  "error": best_quote.get("error"),
247
- "wrap_transaction": best_quote.get("wrap_transaction"),
248
- "unwrap_transaction": best_quote.get("unwrap_transaction"),
249
247
  }
248
+
249
+ # Strip data fields from wrap/unwrap transactions to reduce response size
250
+ wrap_tx = best_quote.get("wrap_transaction")
251
+ if isinstance(wrap_tx, dict):
252
+ best_out["wrap_transaction"] = {
253
+ k: v for k, v in wrap_tx.items() if k != "data"
254
+ }
255
+ unwrap_tx = best_quote.get("unwrap_transaction")
256
+ if isinstance(unwrap_tx, dict):
257
+ best_out["unwrap_transaction"] = {
258
+ k: v for k, v in unwrap_tx.items() if k != "data"
259
+ }
260
+
250
261
  if include_calldata:
251
262
  best_out["calldata"] = calldata
252
263
  else:
@@ -14,9 +14,6 @@ from typing import Any
14
14
 
15
15
  from wayfinder_paths.adapters.balance_adapter.adapter import BalanceAdapter
16
16
  from wayfinder_paths.adapters.hyperliquid_adapter.adapter import HyperliquidAdapter
17
- from wayfinder_paths.adapters.hyperliquid_adapter.executor import (
18
- LocalHyperliquidExecutor,
19
- )
20
17
  from wayfinder_paths.adapters.hyperliquid_adapter.paired_filler import (
21
18
  MIN_NOTIONAL_USD,
22
19
  FillConfig,
@@ -51,9 +48,6 @@ from wayfinder_paths.core.analytics import (
51
48
  from wayfinder_paths.core.analytics import (
52
49
  z_from_conf as analytics_z_from_conf,
53
50
  )
54
- from wayfinder_paths.core.clients.protocols import (
55
- HyperliquidExecutorProtocol as HyperliquidExecutor,
56
- )
57
51
  from wayfinder_paths.core.constants.contracts import HYPERLIQUID_BRIDGE
58
52
  from wayfinder_paths.core.strategies.descriptors import (
59
53
  Complexity,
@@ -64,6 +58,7 @@ from wayfinder_paths.core.strategies.descriptors import (
64
58
  Volatility,
65
59
  )
66
60
  from wayfinder_paths.core.strategies.Strategy import StatusDict, StatusTuple, Strategy
61
+ from wayfinder_paths.core.types import HyperliquidSignCallback
67
62
  from wayfinder_paths.policies.erc20 import any_erc20_function
68
63
  from wayfinder_paths.policies.hyperliquid import (
69
64
  any_hyperliquid_l1_payload,
@@ -188,16 +183,15 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
188
183
  *,
189
184
  main_wallet: dict[str, Any] | None = None,
190
185
  strategy_wallet: dict[str, Any] | None = None,
191
- hyperliquid_executor: HyperliquidExecutor | None = None,
192
- api_key: str | None = None,
186
+ strategy_sign_typed_data: HyperliquidSignCallback | None = None,
193
187
  main_wallet_signing_callback: Callable[[dict], Awaitable[str]] | None = None,
194
188
  strategy_wallet_signing_callback: Callable[[dict], Awaitable[str]]
195
189
  | None = None,
196
190
  ) -> None:
197
191
  super().__init__(
198
- api_key=api_key,
199
192
  main_wallet_signing_callback=main_wallet_signing_callback,
200
193
  strategy_wallet_signing_callback=strategy_wallet_signing_callback,
194
+ strategy_sign_typed_data=strategy_sign_typed_data,
201
195
  )
202
196
 
203
197
  merged_config = dict(config or {})
@@ -228,24 +222,14 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
228
222
  "strategy": self.config,
229
223
  }
230
224
 
231
- # This is only required for placing/canceling orders (not market reads).
232
- hl_executor = hyperliquid_executor
233
- if hl_executor is None:
234
- try:
235
- hl_executor = LocalHyperliquidExecutor(config=adapter_config)
236
- self.logger.info("Created LocalHyperliquidExecutor for real execution")
237
- except Exception as e:
238
- self.logger.warning(
239
- f"Could not create LocalHyperliquidExecutor: {e}. "
240
- "Real Hyperliquid execution will not be available."
241
- )
242
-
243
225
  # Hyperliquid market data adapter should be usable even when wallet/web3
244
226
  # configuration is missing (e.g. local --action analyze).
227
+ # The adapter will create Exchange internally with local signer from config
228
+ # if no sign_callback is provided.
245
229
  try:
246
230
  self.hyperliquid_adapter = HyperliquidAdapter(
247
231
  config=adapter_config,
248
- executor=hl_executor,
232
+ sign_callback=strategy_sign_typed_data,
249
233
  )
250
234
  except Exception as e:
251
235
  self.logger.warning(f"Could not initialize HyperliquidAdapter: {e}")