wayfinder-paths 0.1.23__py3-none-any.whl → 0.1.24__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 (122) hide show
  1. wayfinder_paths/adapters/balance_adapter/adapter.py +250 -0
  2. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -11
  4. wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
  5. wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
  6. wayfinder_paths/adapters/boros_adapter/client.py +476 -0
  7. wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
  8. wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
  9. wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
  10. wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
  11. wayfinder_paths/adapters/boros_adapter/types.py +70 -0
  12. wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
  13. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -1
  14. wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
  15. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +161 -26
  16. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
  17. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +77 -13
  18. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
  19. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +585 -61
  20. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
  21. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
  22. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
  23. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
  24. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
  27. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
  28. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
  29. wayfinder_paths/adapters/moonwell_adapter/adapter.py +592 -400
  30. wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
  31. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +126 -219
  32. wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
  33. wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
  34. wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
  35. wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
  36. wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
  37. wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
  38. wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
  39. wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
  40. wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
  41. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
  42. wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
  43. wayfinder_paths/adapters/token_adapter/examples.json +0 -4
  44. wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
  45. wayfinder_paths/conftest.py +24 -17
  46. wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
  47. wayfinder_paths/core/adapters/models.py +17 -7
  48. wayfinder_paths/core/clients/BRAPClient.py +1 -1
  49. wayfinder_paths/core/clients/TokenClient.py +47 -1
  50. wayfinder_paths/core/clients/WayfinderClient.py +1 -2
  51. wayfinder_paths/core/clients/protocols.py +21 -22
  52. wayfinder_paths/core/clients/test_ledger_client.py +448 -0
  53. wayfinder_paths/core/config.py +12 -0
  54. wayfinder_paths/core/constants/__init__.py +15 -0
  55. wayfinder_paths/core/constants/base.py +6 -1
  56. wayfinder_paths/core/constants/contracts.py +39 -26
  57. wayfinder_paths/core/constants/erc20_abi.py +0 -1
  58. wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
  59. wayfinder_paths/core/constants/hyperliquid.py +16 -0
  60. wayfinder_paths/core/constants/moonwell_abi.py +0 -15
  61. wayfinder_paths/core/engine/manifest.py +66 -0
  62. wayfinder_paths/core/strategies/Strategy.py +0 -61
  63. wayfinder_paths/core/strategies/__init__.py +10 -1
  64. wayfinder_paths/core/strategies/opa_loop.py +167 -0
  65. wayfinder_paths/core/utils/test_transaction.py +289 -0
  66. wayfinder_paths/core/utils/transaction.py +44 -1
  67. wayfinder_paths/core/utils/web3.py +3 -0
  68. wayfinder_paths/mcp/__init__.py +5 -0
  69. wayfinder_paths/mcp/preview.py +185 -0
  70. wayfinder_paths/mcp/scripting.py +84 -0
  71. wayfinder_paths/mcp/server.py +52 -0
  72. wayfinder_paths/mcp/state/profile_store.py +195 -0
  73. wayfinder_paths/mcp/state/store.py +89 -0
  74. wayfinder_paths/mcp/test_scripting.py +267 -0
  75. wayfinder_paths/mcp/tools/__init__.py +0 -0
  76. wayfinder_paths/mcp/tools/balances.py +290 -0
  77. wayfinder_paths/mcp/tools/discovery.py +158 -0
  78. wayfinder_paths/mcp/tools/execute.py +770 -0
  79. wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
  80. wayfinder_paths/mcp/tools/quotes.py +288 -0
  81. wayfinder_paths/mcp/tools/run_script.py +286 -0
  82. wayfinder_paths/mcp/tools/strategies.py +188 -0
  83. wayfinder_paths/mcp/tools/tokens.py +46 -0
  84. wayfinder_paths/mcp/tools/wallets.py +354 -0
  85. wayfinder_paths/mcp/utils.py +129 -0
  86. wayfinder_paths/policies/hyperliquid.py +1 -1
  87. wayfinder_paths/policies/lifi.py +18 -0
  88. wayfinder_paths/policies/util.py +8 -2
  89. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +28 -119
  90. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
  91. wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
  92. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
  93. wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
  94. wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
  95. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
  96. wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
  97. wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
  98. wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
  99. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
  100. wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
  101. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
  102. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
  103. wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +202 -0
  104. wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
  105. wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
  106. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +3 -12
  107. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +7 -29
  108. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +63 -40
  109. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
  110. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -34
  111. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +11 -34
  112. wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
  113. wayfinder_paths/tests/test_test_coverage.py +1 -4
  114. wayfinder_paths-0.1.24.dist-info/METADATA +378 -0
  115. wayfinder_paths-0.1.24.dist-info/RECORD +185 -0
  116. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.24.dist-info}/WHEEL +1 -1
  117. wayfinder_paths/scripts/create_strategy.py +0 -139
  118. wayfinder_paths/scripts/make_wallets.py +0 -142
  119. wayfinder_paths-0.1.23.dist-info/METADATA +0 -354
  120. wayfinder_paths-0.1.23.dist-info/RECORD +0 -120
  121. /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
  122. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.24.dist-info}/LICENSE +0 -0
@@ -1,12 +1,17 @@
1
+ import asyncio
1
2
  from typing import Any
2
3
 
3
4
  from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
5
+ from wayfinder_paths.adapters.multicall_adapter.adapter import MulticallAdapter
4
6
  from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
5
7
  from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
6
8
  from wayfinder_paths.core.clients.TokenClient import TokenClient
9
+ from wayfinder_paths.core.constants import ZERO_ADDRESS
10
+ from wayfinder_paths.core.constants.erc20_abi import ERC20_ABI
7
11
  from wayfinder_paths.core.utils.evm_helpers import resolve_chain_id
8
12
  from wayfinder_paths.core.utils.tokens import build_send_transaction, get_token_balance
9
13
  from wayfinder_paths.core.utils.transaction import send_transaction
14
+ from wayfinder_paths.core.utils.web3 import web3_from_chain_id
10
15
 
11
16
 
12
17
  class BalanceAdapter(BaseAdapter):
@@ -43,6 +48,12 @@ class BalanceAdapter(BaseAdapter):
43
48
  except Exception as e:
44
49
  return False, str(e)
45
50
 
51
+ async def get_vault_wallet_balance(self, token_id: str) -> tuple[bool, int | str]:
52
+ addr = self._wallet_address(self.config.get("strategy_wallet", {}))
53
+ if not addr:
54
+ return False, "No strategy_wallet configured"
55
+ return await self.get_balance(wallet_address=addr, token_id=token_id)
56
+
46
57
  async def move_from_main_wallet_to_strategy_wallet(
47
58
  self,
48
59
  token_id: str,
@@ -177,3 +188,242 @@ class BalanceAdapter(BaseAdapter):
177
188
  )
178
189
  except Exception as exc:
179
190
  self.logger.warning(f"Ledger entry failed: {exc}", wallet=wallet_address)
191
+
192
+ @staticmethod
193
+ def _wallet_address(wallet: dict[str, Any] | None) -> str | None:
194
+ if wallet and isinstance(wallet, dict):
195
+ return wallet.get("address")
196
+ return None
197
+
198
+ async def get_wallet_balances_multicall(
199
+ self,
200
+ *,
201
+ assets: list[dict[str, Any]],
202
+ wallet_address: str | None = None,
203
+ default_native_decimals: int = 18,
204
+ ) -> tuple[bool, list[dict[str, Any]] | str]:
205
+ """
206
+ Fetch many balances via Multicall3, grouped by chain.
207
+
208
+ Each asset entry supports either:
209
+ - {"token_address": "0x...", "chain_id": 42161}
210
+ - {"token_id": "usd-coin-arbitrum"} (resolved via TokenClient)
211
+ Native token entries can use token_address=None/"native"/ZERO_ADDRESS.
212
+
213
+ Returns a list aligned with the input `assets`, each containing:
214
+ - success: bool
215
+ - chain_id: int | None
216
+ - token_address: str | None
217
+ - token_id: str | None
218
+ - balance_raw: int | None
219
+ - decimals: int | None
220
+ - balance_decimal: float | None
221
+ - error: str | None
222
+ """
223
+ if not assets:
224
+ return True, []
225
+
226
+ base_wallet = wallet_address
227
+ if base_wallet is None:
228
+ strategy_wallet = self.config.get("strategy_wallet", {})
229
+ base_wallet = self._wallet_address(strategy_wallet)
230
+
231
+ results: list[dict[str, Any]] = [{"success": False} for _ in assets]
232
+ all_success = True
233
+
234
+ normalized: list[dict[str, Any]] = []
235
+ for idx, asset in enumerate(assets):
236
+ token_id = asset.get("token_id")
237
+ token_address = asset.get("token_address")
238
+ chain_id = asset.get("chain_id")
239
+ req_wallet = asset.get("wallet_address") or base_wallet
240
+
241
+ if not req_wallet:
242
+ results[idx] = {
243
+ "success": False,
244
+ "error": "wallet_address not provided and no strategy_wallet configured",
245
+ "token_id": token_id,
246
+ "token_address": token_address,
247
+ "chain_id": chain_id,
248
+ }
249
+ all_success = False
250
+ continue
251
+
252
+ # Optionally resolve missing chain/token address via TokenClient.
253
+ if token_id and (token_address is None or chain_id is None):
254
+ try:
255
+ token_info = await self.token_client.get_token_details(token_id)
256
+ except Exception as exc: # noqa: BLE001
257
+ token_info = None
258
+ self.logger.warning(
259
+ f"TokenClient lookup failed for {token_id}: {exc}"
260
+ )
261
+
262
+ if not token_info:
263
+ results[idx] = {
264
+ "success": False,
265
+ "error": f"Token not found: {token_id}",
266
+ "token_id": token_id,
267
+ "token_address": token_address,
268
+ "chain_id": chain_id,
269
+ "wallet_address": req_wallet,
270
+ }
271
+ all_success = False
272
+ continue
273
+
274
+ token_address = token_address or token_info.get("address")
275
+ chain_id = chain_id or resolve_chain_id(token_info)
276
+
277
+ if chain_id is None:
278
+ results[idx] = {
279
+ "success": False,
280
+ "error": "chain_id is required",
281
+ "token_id": token_id,
282
+ "token_address": token_address,
283
+ "chain_id": chain_id,
284
+ "wallet_address": req_wallet,
285
+ }
286
+ all_success = False
287
+ continue
288
+
289
+ token_addr_str = (
290
+ str(token_address).strip() if token_address is not None else None
291
+ )
292
+ is_native = (
293
+ token_addr_str is None
294
+ or token_addr_str == ""
295
+ or token_addr_str.lower() == "native"
296
+ or token_addr_str.lower() == ZERO_ADDRESS.lower()
297
+ )
298
+
299
+ normalized.append(
300
+ {
301
+ "index": idx,
302
+ "token_id": token_id,
303
+ "token_address": token_addr_str,
304
+ "chain_id": int(chain_id),
305
+ "wallet_address": str(req_wallet),
306
+ "is_native": bool(is_native),
307
+ }
308
+ )
309
+
310
+ # Group by chain id for separate multicall aggregates.
311
+ by_chain: dict[int, list[dict[str, Any]]] = {}
312
+ for entry in normalized:
313
+ by_chain.setdefault(entry["chain_id"], []).append(entry)
314
+
315
+ async def _process_chain(chain_id: int, entries: list[dict[str, Any]]) -> None:
316
+ nonlocal all_success
317
+ try:
318
+ async with web3_from_chain_id(chain_id) as w3:
319
+ multicall = MulticallAdapter(web3=w3, chain_id=chain_id)
320
+
321
+ # Deduplicate decimals calls per token (per chain).
322
+ token_set: set[str] = {
323
+ w3.to_checksum_address(e["token_address"])
324
+ for e in entries
325
+ if not e["is_native"] and e["token_address"]
326
+ }
327
+ sorted_tokens = sorted(token_set)
328
+
329
+ calls: list[Any] = []
330
+ decimals_call_index: dict[str, int] = {}
331
+ for token in sorted_tokens:
332
+ erc20 = w3.eth.contract(address=token, abi=ERC20_ABI)
333
+ calldata = erc20.encode_abi("decimals")
334
+ decimals_call_index[token] = len(calls)
335
+ calls.append(multicall.build_call(token, calldata))
336
+
337
+ balance_call_index: dict[int, int] = {}
338
+ for entry in entries:
339
+ if entry["is_native"]:
340
+ call = multicall.encode_eth_balance(entry["wallet_address"])
341
+ else:
342
+ token = w3.to_checksum_address(entry["token_address"])
343
+ call = multicall.encode_erc20_balance(
344
+ token, entry["wallet_address"]
345
+ )
346
+ balance_call_index[entry["index"]] = len(calls)
347
+ calls.append(call)
348
+
349
+ mc_res = await multicall.aggregate(calls)
350
+
351
+ decimals_by_token: dict[str, int] = {}
352
+ for token, call_idx in decimals_call_index.items():
353
+ raw_decimals = multicall.decode_uint256(
354
+ mc_res.return_data[call_idx]
355
+ )
356
+ decimals_by_token[token] = int(raw_decimals)
357
+
358
+ for entry in entries:
359
+ out_idx = entry["index"]
360
+ bal_idx = balance_call_index[out_idx]
361
+ raw_balance = multicall.decode_uint256(
362
+ mc_res.return_data[bal_idx]
363
+ )
364
+ if entry["is_native"]:
365
+ decimals = int(default_native_decimals)
366
+ token_address_out = None
367
+ else:
368
+ token = w3.to_checksum_address(entry["token_address"])
369
+ decimals = int(
370
+ decimals_by_token.get(token, default_native_decimals)
371
+ )
372
+ token_address_out = token
373
+
374
+ balance_decimal = (
375
+ float(raw_balance) / (10**decimals)
376
+ if decimals >= 0
377
+ else None
378
+ )
379
+
380
+ results[out_idx] = {
381
+ "success": True,
382
+ "token_id": entry.get("token_id"),
383
+ "token_address": token_address_out,
384
+ "chain_id": chain_id,
385
+ "wallet_address": entry["wallet_address"],
386
+ "balance_raw": int(raw_balance),
387
+ "decimals": int(decimals),
388
+ "balance_decimal": float(balance_decimal)
389
+ if balance_decimal is not None
390
+ else None,
391
+ "block_number": mc_res.block_number,
392
+ }
393
+
394
+ except Exception as exc: # noqa: BLE001
395
+ all_success = False
396
+ err = str(exc)
397
+ for entry in entries:
398
+ out_idx = entry["index"]
399
+ results[out_idx] = {
400
+ "success": False,
401
+ "error": err,
402
+ "token_id": entry.get("token_id"),
403
+ "token_address": entry.get("token_address"),
404
+ "chain_id": chain_id,
405
+ "wallet_address": entry.get("wallet_address"),
406
+ }
407
+
408
+ await asyncio.gather(
409
+ *[
410
+ _process_chain(chain_id, entries)
411
+ for chain_id, entries in by_chain.items()
412
+ ]
413
+ )
414
+
415
+ # Ensure any leftover placeholder entries are marked failed.
416
+ for idx, out in enumerate(results):
417
+ if out.get("success") is True:
418
+ continue
419
+ if "error" not in out:
420
+ all_success = False
421
+ out.setdefault("error", "Unknown error")
422
+ out.setdefault("token_id", assets[idx].get("token_id"))
423
+ out.setdefault("token_address", assets[idx].get("token_address"))
424
+ out.setdefault("chain_id", assets[idx].get("chain_id"))
425
+ out.setdefault(
426
+ "wallet_address", assets[idx].get("wallet_address") or base_wallet
427
+ )
428
+
429
+ return all_success, results
@@ -0,0 +1,8 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "adapters.balance_adapter.adapter.BalanceAdapter"
3
+ capabilities:
4
+ - "balance.read"
5
+ - "transfer.main_to_strategy"
6
+ - "transfer.strategy_to_main"
7
+ - "transfer.send"
8
+ dependencies: []
@@ -18,17 +18,6 @@ class TestBalanceAdapter:
18
18
  ):
19
19
  return BalanceAdapter(config={})
20
20
 
21
- @pytest.mark.asyncio
22
- async def test_health_check(self, adapter):
23
- health = await adapter.health_check()
24
- assert isinstance(health, dict)
25
- assert health.get("status") in {"healthy", "unhealthy", "error"}
26
-
27
- @pytest.mark.asyncio
28
- async def test_connect(self, adapter):
29
- ok = await adapter.connect()
30
- assert isinstance(ok, bool)
31
-
32
21
  def test_adapter_type(self, adapter):
33
22
  assert adapter.adapter_type == "BALANCE"
34
23
 
@@ -0,0 +1,17 @@
1
+ """Boros Adapter - wraps Boros API for fixed-rate market operations."""
2
+
3
+ from .adapter import BorosAdapter
4
+ from .types import (
5
+ BorosLimitOrder,
6
+ BorosMarketQuote,
7
+ BorosTenorQuote,
8
+ MarginHealth,
9
+ )
10
+
11
+ __all__ = [
12
+ "BorosAdapter",
13
+ "BorosMarketQuote",
14
+ "BorosTenorQuote",
15
+ "BorosLimitOrder",
16
+ "MarginHealth",
17
+ ]