wayfinder-paths 0.1.28__py3-none-any.whl → 0.1.29__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 (28) hide show
  1. wayfinder_paths/adapters/boros_adapter/adapter.py +142 -12
  2. wayfinder_paths/adapters/boros_adapter/client.py +7 -5
  3. wayfinder_paths/adapters/boros_adapter/test_adapter.py +147 -18
  4. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +2 -12
  5. wayfinder_paths/adapters/multicall_adapter/adapter.py +2 -4
  6. wayfinder_paths/core/clients/TokenClient.py +1 -1
  7. wayfinder_paths/core/constants/__init__.py +23 -1
  8. wayfinder_paths/core/constants/contracts.py +22 -0
  9. wayfinder_paths/core/constants/hyperliquid.py +20 -3
  10. wayfinder_paths/core/engine/manifest.py +1 -1
  11. wayfinder_paths/mcp/scripting.py +2 -2
  12. wayfinder_paths/mcp/tools/discovery.py +3 -72
  13. wayfinder_paths/mcp/tools/execute.py +8 -4
  14. wayfinder_paths/mcp/tools/hyperliquid.py +1 -1
  15. wayfinder_paths/mcp/tools/quotes.py +7 -8
  16. wayfinder_paths/mcp/tools/wallets.py +4 -7
  17. wayfinder_paths/mcp/utils.py +0 -22
  18. wayfinder_paths/policies/lifi.py +5 -2
  19. wayfinder_paths/policies/moonwell.py +3 -1
  20. wayfinder_paths/policies/util.py +4 -2
  21. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +1 -2
  22. wayfinder_paths/strategies/boros_hype_strategy/constants.py +23 -16
  23. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +24 -63
  24. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +2 -1
  25. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.29.dist-info}/METADATA +1 -1
  26. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.29.dist-info}/RECORD +28 -28
  27. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.29.dist-info}/LICENSE +0 -0
  28. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.29.dist-info}/WHEEL +0 -0
@@ -6,12 +6,12 @@ from wayfinder_paths.mcp.utils import err, ok, read_text_excerpt, read_yaml, rep
6
6
 
7
7
 
8
8
  async def discover(
9
- kind: Literal["adapters", "strategies"],
9
+ kind: Literal["adapter", "strategy"],
10
10
  detail: Literal["summary", "full"] = "summary",
11
11
  ) -> dict[str, Any]:
12
12
  root = repo_root()
13
13
  base = (
14
- root / "wayfinder_paths" / ("adapters" if kind == "adapters" else "strategies")
14
+ root / "wayfinder_paths" / ("adapters" if kind == "adapter" else "strategies")
15
15
  )
16
16
  if not base.exists():
17
17
  return err("not_found", f"Directory not found: {base}")
@@ -26,7 +26,7 @@ async def discover(
26
26
  manifest = read_yaml(manifest_path)
27
27
  rel_manifest = str(manifest_path.relative_to(root))
28
28
 
29
- if kind == "adapters":
29
+ if kind == "adapter":
30
30
  item = {
31
31
  "name": child.name,
32
32
  "entrypoint": manifest.get("entrypoint"),
@@ -58,75 +58,11 @@ async def discover(
58
58
  return ok({"kind": kind, "items": items})
59
59
 
60
60
 
61
- def _default_claude_notes_for_adapter(name: str) -> dict[str, Any] | None:
62
- # Keep this deliberately small; the real docs live in .claude/skills.
63
- if name == "brap_adapter":
64
- return {
65
- "reads": [
66
- {
67
- "name": "quote_swap (via mcp__wayfinder__quote_swap)",
68
- "summary": "Get best BRAP route + fees; no on-chain effects.",
69
- }
70
- ],
71
- "writes": [
72
- {
73
- "name": "swap (via mcp__wayfinder__execute kind=swap)",
74
- "risk": "funds",
75
- "summary": "May submit ERC20 approvals and a swap/bridge tx.",
76
- }
77
- ],
78
- "gotchas": [
79
- "USDT-style tokens may require setting allowance to 0 before approve.",
80
- "Cross-chain swaps still broadcast a tx on the source chain.",
81
- ],
82
- }
83
- if name == "balance_adapter":
84
- return {
85
- "reads": [
86
- {
87
- "name": "balances (via mcp__wayfinder__balances)",
88
- "summary": "Read token/pool balances via Wayfinder API.",
89
- }
90
- ],
91
- "writes": [
92
- {
93
- "name": "send (via mcp__wayfinder__execute kind=send)",
94
- "risk": "funds",
95
- "summary": "Native/ERC20 sends from a local wallet.",
96
- }
97
- ],
98
- "gotchas": ["Prefer DRY_RUN=1 until ready to broadcast."],
99
- }
100
- if name == "hyperliquid_adapter":
101
- return {
102
- "reads": [
103
- {
104
- "name": "hyperliquid (via mcp__wayfinder__hyperliquid)",
105
- "summary": "User state + market reads (no signing).",
106
- }
107
- ],
108
- "writes": [
109
- {
110
- "name": "hyperliquid_execute (via mcp__wayfinder__hyperliquid_execute)",
111
- "risk": "funds",
112
- "summary": "Perp orders/leverage/withdraw with local signing (gated by review prompt).",
113
- }
114
- ],
115
- "gotchas": [
116
- "Perp coins are mapped to asset_id (<10000).",
117
- "Builder fees are attributed to 0xaA1D89f333857eD78F8434CC4f896A9293EFE65c and use tenths-of-bp units.",
118
- "Prefer DRY_RUN=1 until ready to execute.",
119
- ],
120
- }
121
- return None
122
-
123
-
124
61
  async def describe(
125
62
  kind: Literal["adapter", "strategy"],
126
63
  name: str,
127
64
  include_manifest: bool = True,
128
65
  include_readme_excerpt: bool = True,
129
- include_claude_notes: bool = True,
130
66
  ) -> dict[str, Any]:
131
67
  root = repo_root()
132
68
  base = (
@@ -150,9 +86,4 @@ async def describe(
150
86
  if readme:
151
87
  out["readme_excerpt"] = readme
152
88
 
153
- if include_claude_notes and kind == "adapter":
154
- notes = _default_claude_notes_for_adapter(name)
155
- if notes:
156
- out["claude_notes"] = notes
157
-
158
89
  return ok(out)
@@ -38,7 +38,7 @@ from wayfinder_paths.mcp.utils import (
38
38
 
39
39
  class ExecutionRequest(BaseModel):
40
40
  kind: Literal["swap", "send", "hyperliquid_deposit"]
41
- wallet_label: str = Field(..., description="wallets.json label (e.g. main)")
41
+ wallet_label: str = Field(..., description="config.json wallet label (e.g. main)")
42
42
 
43
43
  # Shared
44
44
  amount: str = Field(..., description="Human units as a string (e.g. '1000')")
@@ -257,8 +257,10 @@ def _compact_quote(
257
257
  """Create a compact summary of quote data, stripping verbose nested structures."""
258
258
  result: dict[str, Any] = {}
259
259
 
260
- # Extract provider list from all_quotes
261
- all_quotes = quote_data.get("quotes", {}).get("all_quotes", [])
260
+ # Extract provider list from quotes (API returns quotes as a list at top level)
261
+ all_quotes = quote_data.get("quotes", [])
262
+ if not isinstance(all_quotes, list):
263
+ all_quotes = []
262
264
  if isinstance(all_quotes, list):
263
265
  result["providers"] = list(
264
266
  {
@@ -439,7 +441,7 @@ async def execute(
439
441
  if not sender or not pk:
440
442
  response = err(
441
443
  "invalid_wallet",
442
- "Wallet must include address and private_key_hex in wallets.json (local dev only)",
444
+ "Wallet must include address and private_key_hex in config.json (local dev only)",
443
445
  {"wallet_label": req.wallet_label},
444
446
  )
445
447
  store.put(key, tool_input, response)
@@ -530,6 +532,8 @@ async def execute(
530
532
  swap_tx = dict(calldata)
531
533
  swap_tx["chainId"] = int(from_chain_id)
532
534
  swap_tx["from"] = to_checksum_address(sender)
535
+ if "value" in swap_tx:
536
+ swap_tx["value"] = int(swap_tx["value"])
533
537
 
534
538
  token_addr = from_token_addr
535
539
  spender = swap_tx.get("to")
@@ -382,7 +382,7 @@ async def hyperliquid_execute(
382
382
  if not sender or not pk:
383
383
  response = err(
384
384
  "invalid_wallet",
385
- "Wallet must include address and private_key_hex in wallets.json (local dev only)",
385
+ "Wallet must include address and private_key_hex in config.json (local dev only)",
386
386
  {"wallet_label": want},
387
387
  )
388
388
  store.put(key, tool_input, response)
@@ -205,14 +205,13 @@ async def quote_swap(
205
205
  except Exception as exc: # noqa: BLE001
206
206
  return err("quote_error", str(exc))
207
207
 
208
- quotes_data = data.get("quotes", {}) if isinstance(data, dict) else {}
209
- if not isinstance(quotes_data, dict):
210
- quotes_data = {}
211
- all_quotes = quotes_data.get("all_quotes", [])
212
- if not isinstance(all_quotes, list):
213
- all_quotes = []
214
- best_quote = quotes_data.get("best_quote")
215
- quote_count = quotes_data.get("quote_count", len(all_quotes))
208
+ # API returns {"quotes": [...], "best_quote": {...}} at top level
209
+ raw_quotes = data.get("quotes", []) if isinstance(data, dict) else []
210
+ if not isinstance(raw_quotes, list):
211
+ raw_quotes = []
212
+ all_quotes = raw_quotes
213
+ best_quote = data.get("best_quote") if isinstance(data, dict) else None
214
+ quote_count = len(all_quotes)
216
215
 
217
216
  providers: list[str] = []
218
217
  seen: set[str] = set()
@@ -14,7 +14,6 @@ from wayfinder_paths.mcp.utils import (
14
14
  normalize_address,
15
15
  ok,
16
16
  repo_root,
17
- wallets_path,
18
17
  )
19
18
 
20
19
  PROTOCOL_ADAPTERS: dict[str, dict[str, Any]] = {
@@ -146,9 +145,7 @@ async def wallets(
146
145
  parallel: bool = False,
147
146
  include_zero_positions: bool = False,
148
147
  ) -> dict[str, Any]:
149
- p = wallets_path()
150
148
  root = repo_root()
151
- rel = str(p.relative_to(root)) if p.is_absolute() and root in p.parents else str(p)
152
149
  store = WalletProfileStore.default()
153
150
 
154
151
  if action == "list":
@@ -164,7 +161,7 @@ async def wallets(
164
161
  else:
165
162
  view["protocols"] = []
166
163
  wallet_list.append(view)
167
- return ok({"wallets_path": rel, "wallets": wallet_list})
164
+ return ok({"config_path": "config.json", "wallets": wallet_list})
168
165
 
169
166
  if action == "create":
170
167
  existing = load_wallets()
@@ -178,7 +175,7 @@ async def wallets(
178
175
  if str(w.get("label", "")).strip() == want:
179
176
  return ok(
180
177
  {
181
- "wallets_path": rel,
178
+ "config_path": "config.json",
182
179
  "wallets": [_public_wallet_view(x) for x in existing],
183
180
  "created": _public_wallet_view(w),
184
181
  "note": "Wallet label already existed; returning existing wallet.",
@@ -187,12 +184,12 @@ async def wallets(
187
184
 
188
185
  w = make_random_wallet()
189
186
  w["label"] = want
190
- write_wallet_to_json(w, out_dir=p.parent, filename=p.name)
187
+ write_wallet_to_json(w, out_dir=root, filename="config.json")
191
188
 
192
189
  refreshed = load_wallets()
193
190
  return ok(
194
191
  {
195
- "wallets_path": rel,
192
+ "config_path": "config.json",
196
193
  "wallets": [_public_wallet_view(x) for x in refreshed],
197
194
  "created": _public_wallet_view(w),
198
195
  }
@@ -64,32 +64,10 @@ def load_config_json() -> dict[str, Any]:
64
64
  return parsed if isinstance(parsed, dict) else {}
65
65
 
66
66
 
67
- def wallets_path() -> Path:
68
- cfg = load_config_json()
69
- system = cfg.get("system") if isinstance(cfg.get("system"), dict) else {}
70
- candidate = (
71
- (system or {}).get("wallets_path")
72
- or os.getenv("WALLETS_PATH")
73
- or "wallets.json"
74
- )
75
- p = Path(candidate)
76
- if not p.is_absolute():
77
- p = repo_root() / p
78
- return p
79
-
80
-
81
67
  def load_wallets() -> list[dict[str, Any]]:
82
68
  cfg = load_config_json()
83
69
  if isinstance(cfg.get("wallets"), list):
84
70
  return [w for w in cfg["wallets"] if isinstance(w, dict)]
85
-
86
- p = wallets_path()
87
- if p.exists():
88
- parsed = load_json_file(p)
89
- if isinstance(parsed, list):
90
- return [w for w in parsed if isinstance(w, dict)]
91
- if isinstance(parsed, dict) and isinstance(parsed.get("wallets"), list):
92
- return [w for w in parsed["wallets"] if isinstance(w, dict)]
93
71
  return []
94
72
 
95
73
 
@@ -1,7 +1,10 @@
1
+ from wayfinder_paths.core.constants.contracts import (
2
+ LIFI_GENERIC,
3
+ LIFI_ROUTER_HYPEREVM,
4
+ )
1
5
  from wayfinder_paths.policies.util import allow_functions
2
6
 
3
- LIFI_ROUTERS: dict[int:str] = {999: "0x0a0758d937d1059c356D4714e57F5df0239bce1A"}
4
- LIFI_GENERIC = "0x31a9b1835864706Af10103b31Ea2b79bdb995F5F"
7
+ LIFI_ROUTERS: dict[int:str] = {999: LIFI_ROUTER_HYPEREVM}
5
8
 
6
9
 
7
10
  async def lifi_swap(chain_id):
@@ -5,6 +5,7 @@ from wayfinder_paths.core.constants.contracts import (
5
5
  MOONWELL_M_WETH,
6
6
  MOONWELL_M_WSTETH,
7
7
  )
8
+ from wayfinder_paths.core.constants.moonwell_abi import COMPTROLLER_ABI
8
9
  from wayfinder_paths.policies.util import allow_functions
9
10
 
10
11
  WETH = BASE_WETH
@@ -52,8 +53,9 @@ async def mwsteth_approve_or_mint_or_redeem():
52
53
 
53
54
  async def moonwell_comptroller_enter_markets_or_claim_rewards():
54
55
  return await allow_functions(
55
- policy_name="Allow Moonwell Comptroller Enter Markets or Claim Rewards",
56
+ policy_name="Allow lend and claimReward",
56
57
  abi_chain_id=8453,
57
58
  address=COMPTROLLER,
58
59
  function_names=["enterMarkets", "claimReward"],
60
+ manual_abi=COMPTROLLER_ABI,
59
61
  )
@@ -6,7 +6,8 @@ async def allow_functions(
6
6
  abi_chain_id: int,
7
7
  address: str,
8
8
  function_names: list[str],
9
- abi_address_override=None,
9
+ abi_address_override: str = None,
10
+ manual_abi: dict = None,
10
11
  ):
11
12
  # Note: ChainID is just for fetching ABI, doesn't appear in the final policy. Doesn't bind a strict chain.
12
13
  return {
@@ -23,7 +24,8 @@ async def allow_functions(
23
24
  {
24
25
  "field_source": "ethereum_calldata",
25
26
  "field": "function_name",
26
- "abi": await get_abi_filtered(
27
+ "abi": manual_abi
28
+ or await get_abi_filtered(
27
29
  abi_chain_id, abi_address_override or address, function_names
28
30
  ),
29
31
  "operator": "in",
@@ -48,7 +48,7 @@ from wayfinder_paths.core.analytics import (
48
48
  from wayfinder_paths.core.analytics import (
49
49
  z_from_conf as analytics_z_from_conf,
50
50
  )
51
- from wayfinder_paths.core.constants.contracts import HYPERLIQUID_BRIDGE
51
+ from wayfinder_paths.core.constants.contracts import HYPE_FEE_WALLET, HYPERLIQUID_BRIDGE
52
52
  from wayfinder_paths.core.strategies.descriptors import (
53
53
  Complexity,
54
54
  Directionality,
@@ -111,7 +111,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
111
111
  # Rotation cooldown
112
112
  ROTATION_MIN_INTERVAL_DAYS = 14
113
113
 
114
- HYPE_FEE_WALLET: str = "0xaA1D89f333857eD78F8434CC4f896A9293EFE65c"
115
114
  HYPE_PRO_FEE: int = 30
116
115
  DEFAULT_BUILDER_FEE: dict[str, Any] = {"b": HYPE_FEE_WALLET, "f": HYPE_PRO_FEE}
117
116
 
@@ -1,3 +1,24 @@
1
+ from wayfinder_paths.core.constants.contracts import (
2
+ HYPE_OFT_ADDRESS,
3
+ KHYPE_ADDRESS,
4
+ KHYPE_STAKING_ACCOUNTANT,
5
+ LHYPE_ACCOUNTANT,
6
+ LOOPED_HYPE_ADDRESS,
7
+ )
8
+ from wayfinder_paths.core.constants.contracts import (
9
+ HYPEREVM_WHYPE as WHYPE_ADDRESS,
10
+ )
11
+
12
+ # Re-export addresses for use by strategy modules
13
+ __all__ = [
14
+ "HYPE_OFT_ADDRESS",
15
+ "WHYPE_ADDRESS",
16
+ "KHYPE_ADDRESS",
17
+ "KHYPE_STAKING_ACCOUNTANT",
18
+ "LHYPE_ACCOUNTANT",
19
+ "LOOPED_HYPE_ADDRESS",
20
+ ]
21
+
1
22
  # ─────────────────────────────────────────────────────────────────────────────
2
23
  # TOKEN IDS (wayfinder token identifiers)
3
24
  # ─────────────────────────────────────────────────────────────────────────────
@@ -14,20 +35,6 @@ KHYPE_LST = "kinetic-staked-hype-hyperevm"
14
35
  LOOPED_HYPE = "looped-hype-hyperevm"
15
36
  USDC_HYPE = "usd-coin-hyperevm"
16
37
 
17
-
18
- # ─────────────────────────────────────────────────────────────────────────────
19
- # CONTRACT ADDRESSES
20
- # ─────────────────────────────────────────────────────────────────────────────
21
-
22
- # HyperEVM token addresses
23
- KHYPE_ADDRESS = "0xfD739d4e423301CE9385c1fb8850539D657C296D"
24
- LOOPED_HYPE_ADDRESS = "0x5748ae796AE46A4F1348a1693de4b50560485562"
25
- WHYPE_ADDRESS = "0x5555555555555555555555555555555555555555"
26
-
27
- # Accountant contracts for exchange rate calculations
28
- KHYPE_STAKING_ACCOUNTANT = "0x9209648Ec9D448EF57116B73A2f081835643dc7A"
29
- LHYPE_ACCOUNTANT = "0xcE621a3CA6F72706678cFF0572ae8d15e5F001c3"
30
-
31
38
  # Chain IDs
32
39
  HYPEREVM_CHAIN_ID = 999
33
40
  ARBITRUM_CHAIN_ID = 42161
@@ -100,8 +107,8 @@ BOROS_MIN_DEPOSIT_HYPE = 0.4
100
107
  BOROS_MIN_TENOR_DAYS = 3 # Roll to new market if < 3 days to expiry
101
108
  BOROS_ENABLE_MIN_TOTAL_USD = 80.0 # Skip Boros if capital below this
102
109
 
103
- # LayerZero OFT bridge (HyperEVM native HYPE Arbitrum OFT HYPE)
104
- HYPE_OFT_ADDRESS = "0x007C26Ed5C33Fe6fEF62223d4c363A01F1b1dDc1"
110
+ # LayerZero OFT bridge (HyperEVM native HYPE -> Arbitrum OFT HYPE)
111
+ # HYPE_OFT_ADDRESS imported from contracts.py
105
112
  LZ_EID_ARBITRUM = 30110
106
113
 
107
114
  # Minimal IOFT ABI for quoting + sending.
@@ -23,7 +23,6 @@ from loguru import logger
23
23
 
24
24
  from wayfinder_paths.adapters.balance_adapter.adapter import BalanceAdapter
25
25
  from wayfinder_paths.adapters.boros_adapter import BorosAdapter, BorosMarketQuote
26
- from wayfinder_paths.adapters.boros_adapter.client import BorosClient
27
26
  from wayfinder_paths.adapters.brap_adapter.adapter import BRAPAdapter
28
27
  from wayfinder_paths.adapters.hyperliquid_adapter.adapter import (
29
28
  HyperliquidAdapter,
@@ -273,75 +272,37 @@ class BorosHypeStrategy(
273
272
  user_address=user_address,
274
273
  )
275
274
 
276
- # Verify Boros connection
277
- try:
278
- connected = await self.boros_adapter.connect()
279
- except Exception as e:
280
- connected = False
281
- logger.warning(f"Boros adapter connection failed: {e}")
282
- if not connected:
283
- logger.warning(
284
- "Boros adapter connection failed - some features may not work"
285
- )
286
-
287
- # Initialize Hyperliquid adapter for market data and perp trading
288
- # The adapter will create Exchange internally with local signer from config
289
- # if no sign_callback is provided (and not in simulation mode).
290
- try:
291
- self.hyperliquid_adapter = HyperliquidAdapter(
292
- config=self._config,
293
- simulation=self.simulation,
294
- sign_callback=self.strategy_sign_typed_data,
295
- )
296
- try:
297
- hl_connected = await self.hyperliquid_adapter.connect()
298
- except Exception as e:
299
- hl_connected = False
300
- logger.warning(f"Hyperliquid adapter connection failed: {e}")
301
- if not hl_connected:
302
- logger.warning("Hyperliquid adapter connection failed")
303
- except Exception as e:
304
- logger.warning(f"Hyperliquid adapter not available: {e}")
305
- self.hyperliquid_adapter = None
306
-
307
- # Initialize BalanceAdapter for on-chain balance reads
308
- try:
309
- self.balance_adapter = BalanceAdapter(
310
- config=self._config,
311
- main_wallet_signing_callback=main_sign_callback,
312
- strategy_wallet_signing_callback=self._sign_callback,
313
- )
314
- logger.debug("BalanceAdapter initialized for on-chain balance reads")
315
- except Exception as e:
316
- logger.warning(f"BalanceAdapter initialization failed: {e}")
317
- self.balance_adapter = None
275
+ self.hyperliquid_adapter = HyperliquidAdapter(
276
+ config=self._config,
277
+ simulation=self.simulation,
278
+ sign_callback=self.strategy_sign_typed_data,
279
+ )
318
280
 
319
- # Initialize BRAP adapter for swaps and bridging
320
- try:
321
- self.brap_adapter = BRAPAdapter(
322
- config=self._config,
323
- strategy_wallet_signing_callback=self._sign_callback,
324
- )
325
- logger.debug("BRAPAdapter initialized for swaps")
326
- except Exception as e:
327
- logger.warning(f"BRAPAdapter initialization failed: {e}")
328
- self.brap_adapter = None
281
+ self.balance_adapter = BalanceAdapter(
282
+ config=self._config,
283
+ main_wallet_signing_callback=main_sign_callback,
284
+ strategy_wallet_signing_callback=self._sign_callback,
285
+ )
286
+ self.brap_adapter = BRAPAdapter(
287
+ config=self._config,
288
+ strategy_wallet_signing_callback=self._sign_callback,
289
+ )
329
290
 
330
- # Initialize ledger adapter for transaction recording
331
- try:
332
- self.ledger_adapter = LedgerAdapter()
333
- logger.debug("LedgerAdapter initialized")
334
- except Exception as e:
335
- logger.warning(f"LedgerAdapter initialization failed: {e}")
336
- self.ledger_adapter = None
291
+ self.ledger_adapter = LedgerAdapter()
337
292
 
338
293
  logger.info(f"BorosHypeStrategy setup complete (simulation={self.simulation})")
339
294
 
340
295
  async def analyze(self, deposit_usdc: float = 1000.0) -> dict[str, Any]:
341
296
  # Read-only market analysis returning Boros fixed-rate markets for HYPE
342
- client = (
343
- self.boros_adapter.boros_client if self.boros_adapter else BorosClient()
344
- )
297
+ # Client ownership: BorosAdapter owns the client; we require adapter to be set up
298
+ if not self.boros_adapter:
299
+ return {
300
+ "success": False,
301
+ "error": "BorosAdapter not initialized - call setup() first",
302
+ "deposit_usdc": float(deposit_usdc),
303
+ "hype_markets": [],
304
+ }
305
+ client = self.boros_adapter.boros_client
345
306
  try:
346
307
  markets = await client.list_markets(is_whitelisted=True, skip=0, limit=250)
347
308
  except Exception as exc: # noqa: BLE001
@@ -12,6 +12,7 @@ from wayfinder_paths.adapters.brap_adapter.adapter import BRAPAdapter
12
12
  from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
13
13
  from wayfinder_paths.adapters.pool_adapter.adapter import PoolAdapter
14
14
  from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
15
+ from wayfinder_paths.core.constants.contracts import ENSO_ROUTER
15
16
  from wayfinder_paths.core.strategies.descriptors import (
16
17
  Complexity,
17
18
  Directionality,
@@ -1591,7 +1592,7 @@ class StablecoinYieldStrategy(Strategy):
1591
1592
 
1592
1593
  @staticmethod
1593
1594
  def policies() -> list[str]:
1594
- enso_router = "0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf".lower()
1595
+ enso_router = ENSO_ROUTER.lower()
1595
1596
  approve_enso = (
1596
1597
  "eth.tx.data[0..10] == '0x095ea7b3' && "
1597
1598
  f"eth.tx.data[34..74] == '{enso_router[2:]}'"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: wayfinder-paths
3
- Version: 0.1.28
3
+ Version: 0.1.29
4
4
  Summary: Wayfinder Path: strategies and adapters
5
5
  Author: Wayfinder
6
6
  Author-email: dev@wayfinder.ai