wayfinder-paths 0.1.23__py3-none-any.whl → 0.1.25__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 (124) hide show
  1. wayfinder_paths/__init__.py +2 -0
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +250 -0
  3. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  4. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -11
  5. wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
  6. wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
  7. wayfinder_paths/adapters/boros_adapter/client.py +476 -0
  8. wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
  9. wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
  10. wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
  11. wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
  12. wayfinder_paths/adapters/boros_adapter/types.py +70 -0
  13. wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
  14. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -1
  15. wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
  16. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +161 -26
  17. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
  18. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +77 -13
  19. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
  20. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +585 -61
  21. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
  22. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
  23. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
  24. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
  27. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
  28. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
  29. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
  30. wayfinder_paths/adapters/moonwell_adapter/adapter.py +592 -400
  31. wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
  32. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +126 -219
  33. wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
  34. wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
  35. wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
  36. wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
  37. wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
  38. wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
  39. wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
  40. wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
  41. wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
  42. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
  43. wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
  44. wayfinder_paths/adapters/token_adapter/examples.json +0 -4
  45. wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
  46. wayfinder_paths/conftest.py +24 -17
  47. wayfinder_paths/core/__init__.py +2 -0
  48. wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
  49. wayfinder_paths/core/adapters/models.py +17 -7
  50. wayfinder_paths/core/clients/BRAPClient.py +1 -1
  51. wayfinder_paths/core/clients/TokenClient.py +47 -1
  52. wayfinder_paths/core/clients/WayfinderClient.py +1 -2
  53. wayfinder_paths/core/clients/protocols.py +21 -22
  54. wayfinder_paths/core/clients/test_ledger_client.py +448 -0
  55. wayfinder_paths/core/config.py +12 -0
  56. wayfinder_paths/core/constants/__init__.py +15 -0
  57. wayfinder_paths/core/constants/base.py +6 -1
  58. wayfinder_paths/core/constants/contracts.py +39 -26
  59. wayfinder_paths/core/constants/erc20_abi.py +0 -1
  60. wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
  61. wayfinder_paths/core/constants/hyperliquid.py +16 -0
  62. wayfinder_paths/core/constants/moonwell_abi.py +0 -15
  63. wayfinder_paths/core/engine/manifest.py +66 -0
  64. wayfinder_paths/core/strategies/Strategy.py +0 -61
  65. wayfinder_paths/core/strategies/__init__.py +10 -1
  66. wayfinder_paths/core/strategies/opa_loop.py +167 -0
  67. wayfinder_paths/core/utils/test_transaction.py +289 -0
  68. wayfinder_paths/core/utils/transaction.py +44 -1
  69. wayfinder_paths/core/utils/web3.py +3 -0
  70. wayfinder_paths/mcp/__init__.py +5 -0
  71. wayfinder_paths/mcp/preview.py +185 -0
  72. wayfinder_paths/mcp/scripting.py +84 -0
  73. wayfinder_paths/mcp/server.py +52 -0
  74. wayfinder_paths/mcp/state/profile_store.py +195 -0
  75. wayfinder_paths/mcp/state/store.py +89 -0
  76. wayfinder_paths/mcp/test_scripting.py +267 -0
  77. wayfinder_paths/mcp/tools/__init__.py +0 -0
  78. wayfinder_paths/mcp/tools/balances.py +290 -0
  79. wayfinder_paths/mcp/tools/discovery.py +158 -0
  80. wayfinder_paths/mcp/tools/execute.py +770 -0
  81. wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
  82. wayfinder_paths/mcp/tools/quotes.py +288 -0
  83. wayfinder_paths/mcp/tools/run_script.py +286 -0
  84. wayfinder_paths/mcp/tools/strategies.py +188 -0
  85. wayfinder_paths/mcp/tools/tokens.py +46 -0
  86. wayfinder_paths/mcp/tools/wallets.py +354 -0
  87. wayfinder_paths/mcp/utils.py +129 -0
  88. wayfinder_paths/policies/hyperliquid.py +1 -1
  89. wayfinder_paths/policies/lifi.py +18 -0
  90. wayfinder_paths/policies/util.py +8 -2
  91. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +28 -119
  92. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
  93. wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
  94. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
  95. wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
  96. wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
  97. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
  98. wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
  99. wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
  100. wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
  101. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
  102. wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
  103. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
  104. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
  105. wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +202 -0
  106. wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
  107. wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
  108. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +3 -12
  109. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +7 -29
  110. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +63 -40
  111. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
  112. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -34
  113. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +11 -34
  114. wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
  115. wayfinder_paths/tests/test_test_coverage.py +1 -4
  116. wayfinder_paths-0.1.25.dist-info/METADATA +377 -0
  117. wayfinder_paths-0.1.25.dist-info/RECORD +185 -0
  118. wayfinder_paths/scripts/create_strategy.py +0 -139
  119. wayfinder_paths/scripts/make_wallets.py +0 -142
  120. wayfinder_paths-0.1.23.dist-info/METADATA +0 -354
  121. wayfinder_paths-0.1.23.dist-info/RECORD +0 -120
  122. /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
  123. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/LICENSE +0 -0
  124. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/WHEEL +0 -0
@@ -0,0 +1,7 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "adapters.token_adapter.adapter.TokenAdapter"
3
+ capabilities:
4
+ - "token.read"
5
+ - "token.price"
6
+ - "token.gas"
7
+ dependencies: []
@@ -1,23 +1,30 @@
1
1
  import sys
2
2
  from pathlib import Path
3
3
 
4
- # This needs to be at index 0 to take precedence over repo root 'tests/' directory
5
- _wayfinder_path_dir = Path(__file__).parent
6
- _wayfinder_path_str = str(_wayfinder_path_dir)
4
+ import pytest
5
+
6
+ # Add repo root to path so tests.test_utils can be imported
7
+ _repo_root = Path(__file__).parent.parent
8
+ _repo_root_str = str(_repo_root)
7
9
 
8
10
 
9
11
  def pytest_configure(config):
10
- if _wayfinder_path_str not in sys.path:
11
- sys.path.insert(0, _wayfinder_path_str)
12
- elif sys.path.index(_wayfinder_path_str) > 0:
13
- # Move to front if it exists but isn't first
14
- sys.path.remove(_wayfinder_path_str)
15
- sys.path.insert(0, _wayfinder_path_str)
16
-
17
-
18
- # Also set it immediately (in case pytest_configure hasn't run yet)
19
- if _wayfinder_path_str not in sys.path:
20
- sys.path.insert(0, _wayfinder_path_str)
21
- elif sys.path.index(_wayfinder_path_str) > 0:
22
- sys.path.remove(_wayfinder_path_str)
23
- sys.path.insert(0, _wayfinder_path_str)
12
+ config.addinivalue_line("markers", "smoke: mark test as a smoke test")
13
+ if _repo_root_str not in sys.path:
14
+ sys.path.insert(0, _repo_root_str)
15
+ elif sys.path.index(_repo_root_str) > 0:
16
+ sys.path.remove(_repo_root_str)
17
+ sys.path.insert(0, _repo_root_str)
18
+
19
+
20
+ def pytest_collection_modifyitems(config, items):
21
+ for item in items:
22
+ if "smoke" in item.nodeid:
23
+ item.add_marker(pytest.mark.smoke)
24
+
25
+
26
+ if _repo_root_str not in sys.path:
27
+ sys.path.insert(0, _repo_root_str)
28
+ elif sys.path.index(_repo_root_str) > 0:
29
+ sys.path.remove(_repo_root_str)
30
+ sys.path.insert(0, _repo_root_str)
@@ -1,5 +1,6 @@
1
1
  from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
2
2
  from wayfinder_paths.core.strategies.Strategy import (
3
+ LiquidationResult,
3
4
  StatusDict,
4
5
  StatusTuple,
5
6
  Strategy,
@@ -7,6 +8,7 @@ from wayfinder_paths.core.strategies.Strategy import (
7
8
 
8
9
  __all__ = [
9
10
  "Strategy",
11
+ "LiquidationResult",
10
12
  "StatusDict",
11
13
  "StatusTuple",
12
14
  "BaseAdapter",
@@ -14,30 +14,5 @@ class BaseAdapter(ABC):
14
14
  self.config = config or {}
15
15
  self.logger = logger.bind(adapter=self.__class__.__name__)
16
16
 
17
- async def connect(self) -> bool:
18
- return True
19
-
20
- async def get_balance(self, asset: str) -> dict[str, Any]:
21
- if not asset or not isinstance(asset, str) or not asset.strip():
22
- raise ValueError("asset must be a non-empty string")
23
- raise NotImplementedError(
24
- f"get_balance not supported by {self.__class__.__name__}"
25
- )
26
-
27
- async def health_check(self) -> dict[str, Any]:
28
- try:
29
- connected = await self.connect()
30
- return {
31
- "status": "healthy" if connected else "unhealthy",
32
- "connected": connected,
33
- "adapter": self.adapter_type or self.__class__.__name__,
34
- }
35
- except Exception as e:
36
- return {
37
- "status": "error",
38
- "error": str(e),
39
- "adapter": self.adapter_type or self.__class__.__name__,
40
- }
41
-
42
17
  async def close(self) -> None:
43
18
  pass
@@ -4,9 +4,11 @@ from pydantic import BaseModel, Field
4
4
 
5
5
 
6
6
  class OperationBase(BaseModel):
7
- adapter: str
8
- transaction_hash: str | None
9
- transaction_chain_id: int | None
7
+ # These are provided by adapters at execution time, but tests and callers may
8
+ # construct operations without them (e.g., for bookkeeping payloads).
9
+ adapter: str = "unknown"
10
+ transaction_hash: str | None = None
11
+ transaction_chain_id: int | None = None
10
12
 
11
13
 
12
14
  class SWAP(OperationBase):
@@ -23,14 +25,22 @@ class SWAP(OperationBase):
23
25
 
24
26
  class LEND(OperationBase):
25
27
  type: Literal["LEND"] = "LEND"
26
- contract: str
27
- amount: int
28
+ token_address: str
29
+ pool_address: str
30
+ amount: str
31
+ amount_usd: float
32
+ transaction_status: str | None = None
33
+ transaction_receipt: dict[str, Any] | None = None
28
34
 
29
35
 
30
36
  class UNLEND(OperationBase):
31
37
  type: Literal["UNLEND"] = "UNLEND"
32
- contract: str
33
- amount: int
38
+ token_address: str
39
+ pool_address: str
40
+ amount: str
41
+ amount_usd: float
42
+ transaction_status: str | None = None
43
+ transaction_receipt: dict[str, Any] | None = None
34
44
 
35
45
 
36
46
  # Type alias for operation types (currently only SWAP is used)
@@ -72,7 +72,7 @@ class BRAPQuoteResponse(TypedDict):
72
72
  class BRAPClient(WayfinderClient):
73
73
  def __init__(self):
74
74
  super().__init__()
75
- self.api_base_url = f"{get_api_base_url()}/v1/blockchain/braps"
75
+ self.api_base_url = f"{get_api_base_url()}/blockchain/braps"
76
76
 
77
77
  async def get_quote(
78
78
  self,
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import xml.etree.ElementTree as ET
3
4
  from typing import NotRequired, Required, TypedDict
4
5
 
5
6
  from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
@@ -76,10 +77,20 @@ class GasToken(TypedDict):
76
77
  chain: NotRequired[ChainInfo]
77
78
 
78
79
 
80
+ class FuzzyTokenResult(TypedDict):
81
+ coingecko_id: NotRequired[str]
82
+ address: NotRequired[str]
83
+ chain: NotRequired[str]
84
+ name: NotRequired[str]
85
+ symbol: NotRequired[str]
86
+ price: NotRequired[float]
87
+ confidence: NotRequired[int]
88
+
89
+
79
90
  class TokenClient(WayfinderClient):
80
91
  def __init__(self):
81
92
  super().__init__()
82
- self.api_base_url = f"{get_api_base_url()}/v1/blockchain/tokens"
93
+ self.api_base_url = f"{get_api_base_url()}/blockchain/tokens"
83
94
 
84
95
  async def get_token_details(
85
96
  self, query: str, market_data: bool = False, chain_id: int | None = None
@@ -103,3 +114,38 @@ class TokenClient(WayfinderClient):
103
114
  response.raise_for_status()
104
115
  data = response.json()
105
116
  return data.get("data", data)
117
+
118
+ async def fuzzy_search(
119
+ self, query: str, chain: str | None = None
120
+ ) -> dict[str, list[FuzzyTokenResult] | str | None]:
121
+ url = f"{self.api_base_url}/fuzzy/"
122
+ params: dict[str, str] = {"query": query}
123
+ if chain:
124
+ params["chain"] = chain
125
+ response = await self._authed_request("GET", url, params=params)
126
+ response.raise_for_status()
127
+ trace_id = response.headers.get("X-Token-Fuzzy-Trace")
128
+ tokens = self._parse_fuzzy_xml(response.text)
129
+ return {"tokens": tokens, "trace_id": trace_id}
130
+
131
+ def _parse_fuzzy_xml(self, xml_content: str) -> list[FuzzyTokenResult]:
132
+ root = ET.fromstring(xml_content)
133
+ tokens: list[FuzzyTokenResult] = []
134
+ for token_elem in root.findall("token"):
135
+ token: FuzzyTokenResult = {}
136
+ for field in ["coingecko_id", "address", "chain", "name", "symbol"]:
137
+ elem = token_elem.find(field)
138
+ if elem is not None and elem.text:
139
+ token[field] = elem.text # type: ignore[literal-required]
140
+ for num_field in ["price", "confidence"]:
141
+ elem = token_elem.find(num_field)
142
+ if elem is not None and elem.text:
143
+ try:
144
+ if num_field == "price":
145
+ token["price"] = float(elem.text)
146
+ else:
147
+ token["confidence"] = int(elem.text)
148
+ except ValueError:
149
+ pass
150
+ tokens.append(token)
151
+ return tokens
@@ -65,8 +65,7 @@ class WayfinderClient:
65
65
  logger.debug(f"Making {method} request to {url}")
66
66
  start_time = time.time()
67
67
 
68
- # Ensure API key is set in headers if available and not already set
69
- # This ensures API keys are passed to all endpoints (including public ones) for rate limiting
68
+ # Pass API key to all endpoints (including public ones) for rate limiting
70
69
  if not self.headers.get("X-API-KEY"):
71
70
  creds = self._load_config_credentials()
72
71
  api_key = creds.get("api_key")
@@ -1,27 +1,26 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import TYPE_CHECKING, Any, Protocol
4
-
5
- if TYPE_CHECKING:
6
- from wayfinder_paths.core.clients.BRAPClient import BRAPQuoteResponse
7
- from wayfinder_paths.core.clients.HyperlendClient import (
8
- AssetsView,
9
- LendRateHistory,
10
- MarketEntry,
11
- StableMarketsHeadroomResponse,
12
- )
13
- from wayfinder_paths.core.clients.LedgerClient import (
14
- StrategyTransactionList,
15
- TransactionRecord,
16
- )
17
- from wayfinder_paths.core.clients.PoolClient import (
18
- LlamaMatchesResponse,
19
- PoolList,
20
- )
21
- from wayfinder_paths.core.clients.TokenClient import (
22
- GasToken,
23
- TokenDetails,
24
- )
3
+ from typing import Any, Protocol
4
+
5
+ from wayfinder_paths.core.clients.BRAPClient import BRAPQuoteResponse
6
+ from wayfinder_paths.core.clients.HyperlendClient import (
7
+ AssetsView,
8
+ LendRateHistory,
9
+ MarketEntry,
10
+ StableMarketsHeadroomResponse,
11
+ )
12
+ from wayfinder_paths.core.clients.LedgerClient import (
13
+ StrategyTransactionList,
14
+ TransactionRecord,
15
+ )
16
+ from wayfinder_paths.core.clients.PoolClient import (
17
+ LlamaMatchesResponse,
18
+ PoolList,
19
+ )
20
+ from wayfinder_paths.core.clients.TokenClient import (
21
+ GasToken,
22
+ TokenDetails,
23
+ )
25
24
 
26
25
 
27
26
  class TokenClientProtocol(Protocol):