wayfinder-paths 0.1.7__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 (149) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +399 -0
  2. wayfinder_paths/__init__.py +22 -0
  3. wayfinder_paths/abis/generic/erc20.json +383 -0
  4. wayfinder_paths/adapters/__init__.py +0 -0
  5. wayfinder_paths/adapters/balance_adapter/README.md +94 -0
  6. wayfinder_paths/adapters/balance_adapter/adapter.py +238 -0
  7. wayfinder_paths/adapters/balance_adapter/examples.json +6 -0
  8. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  9. wayfinder_paths/adapters/balance_adapter/test_adapter.py +59 -0
  10. wayfinder_paths/adapters/brap_adapter/README.md +249 -0
  11. wayfinder_paths/adapters/brap_adapter/__init__.py +7 -0
  12. wayfinder_paths/adapters/brap_adapter/adapter.py +726 -0
  13. wayfinder_paths/adapters/brap_adapter/examples.json +175 -0
  14. wayfinder_paths/adapters/brap_adapter/manifest.yaml +11 -0
  15. wayfinder_paths/adapters/brap_adapter/test_adapter.py +286 -0
  16. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
  17. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +305 -0
  18. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +10 -0
  19. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +274 -0
  20. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +18 -0
  21. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1093 -0
  22. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +549 -0
  23. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +8 -0
  24. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +1050 -0
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +126 -0
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +219 -0
  27. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +220 -0
  28. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +134 -0
  29. wayfinder_paths/adapters/ledger_adapter/README.md +145 -0
  30. wayfinder_paths/adapters/ledger_adapter/__init__.py +7 -0
  31. wayfinder_paths/adapters/ledger_adapter/adapter.py +289 -0
  32. wayfinder_paths/adapters/ledger_adapter/examples.json +137 -0
  33. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +11 -0
  34. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +205 -0
  35. wayfinder_paths/adapters/pool_adapter/README.md +206 -0
  36. wayfinder_paths/adapters/pool_adapter/__init__.py +7 -0
  37. wayfinder_paths/adapters/pool_adapter/adapter.py +282 -0
  38. wayfinder_paths/adapters/pool_adapter/examples.json +143 -0
  39. wayfinder_paths/adapters/pool_adapter/manifest.yaml +10 -0
  40. wayfinder_paths/adapters/pool_adapter/test_adapter.py +220 -0
  41. wayfinder_paths/adapters/token_adapter/README.md +101 -0
  42. wayfinder_paths/adapters/token_adapter/__init__.py +3 -0
  43. wayfinder_paths/adapters/token_adapter/adapter.py +96 -0
  44. wayfinder_paths/adapters/token_adapter/examples.json +26 -0
  45. wayfinder_paths/adapters/token_adapter/manifest.yaml +6 -0
  46. wayfinder_paths/adapters/token_adapter/test_adapter.py +125 -0
  47. wayfinder_paths/config.example.json +22 -0
  48. wayfinder_paths/conftest.py +31 -0
  49. wayfinder_paths/core/__init__.py +18 -0
  50. wayfinder_paths/core/adapters/BaseAdapter.py +65 -0
  51. wayfinder_paths/core/adapters/__init__.py +5 -0
  52. wayfinder_paths/core/adapters/base.py +5 -0
  53. wayfinder_paths/core/adapters/models.py +46 -0
  54. wayfinder_paths/core/analytics/__init__.py +11 -0
  55. wayfinder_paths/core/analytics/bootstrap.py +57 -0
  56. wayfinder_paths/core/analytics/stats.py +48 -0
  57. wayfinder_paths/core/analytics/test_analytics.py +170 -0
  58. wayfinder_paths/core/clients/AuthClient.py +83 -0
  59. wayfinder_paths/core/clients/BRAPClient.py +109 -0
  60. wayfinder_paths/core/clients/ClientManager.py +210 -0
  61. wayfinder_paths/core/clients/HyperlendClient.py +192 -0
  62. wayfinder_paths/core/clients/LedgerClient.py +443 -0
  63. wayfinder_paths/core/clients/PoolClient.py +128 -0
  64. wayfinder_paths/core/clients/SimulationClient.py +192 -0
  65. wayfinder_paths/core/clients/TokenClient.py +89 -0
  66. wayfinder_paths/core/clients/TransactionClient.py +63 -0
  67. wayfinder_paths/core/clients/WalletClient.py +94 -0
  68. wayfinder_paths/core/clients/WayfinderClient.py +269 -0
  69. wayfinder_paths/core/clients/__init__.py +48 -0
  70. wayfinder_paths/core/clients/protocols.py +392 -0
  71. wayfinder_paths/core/clients/sdk_example.py +110 -0
  72. wayfinder_paths/core/config.py +458 -0
  73. wayfinder_paths/core/constants/__init__.py +26 -0
  74. wayfinder_paths/core/constants/base.py +42 -0
  75. wayfinder_paths/core/constants/erc20_abi.py +118 -0
  76. wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
  77. wayfinder_paths/core/engine/StrategyJob.py +188 -0
  78. wayfinder_paths/core/engine/__init__.py +5 -0
  79. wayfinder_paths/core/engine/manifest.py +97 -0
  80. wayfinder_paths/core/services/__init__.py +0 -0
  81. wayfinder_paths/core/services/base.py +179 -0
  82. wayfinder_paths/core/services/local_evm_txn.py +430 -0
  83. wayfinder_paths/core/services/local_token_txn.py +231 -0
  84. wayfinder_paths/core/services/web3_service.py +45 -0
  85. wayfinder_paths/core/settings.py +61 -0
  86. wayfinder_paths/core/strategies/Strategy.py +280 -0
  87. wayfinder_paths/core/strategies/__init__.py +5 -0
  88. wayfinder_paths/core/strategies/base.py +7 -0
  89. wayfinder_paths/core/strategies/descriptors.py +81 -0
  90. wayfinder_paths/core/utils/__init__.py +1 -0
  91. wayfinder_paths/core/utils/evm_helpers.py +206 -0
  92. wayfinder_paths/core/utils/wallets.py +77 -0
  93. wayfinder_paths/core/wallets/README.md +91 -0
  94. wayfinder_paths/core/wallets/WalletManager.py +56 -0
  95. wayfinder_paths/core/wallets/__init__.py +7 -0
  96. wayfinder_paths/policies/enso.py +17 -0
  97. wayfinder_paths/policies/erc20.py +34 -0
  98. wayfinder_paths/policies/evm.py +21 -0
  99. wayfinder_paths/policies/hyper_evm.py +19 -0
  100. wayfinder_paths/policies/hyperlend.py +12 -0
  101. wayfinder_paths/policies/hyperliquid.py +30 -0
  102. wayfinder_paths/policies/moonwell.py +54 -0
  103. wayfinder_paths/policies/prjx.py +30 -0
  104. wayfinder_paths/policies/util.py +27 -0
  105. wayfinder_paths/run_strategy.py +411 -0
  106. wayfinder_paths/scripts/__init__.py +0 -0
  107. wayfinder_paths/scripts/create_strategy.py +181 -0
  108. wayfinder_paths/scripts/make_wallets.py +169 -0
  109. wayfinder_paths/scripts/run_strategy.py +124 -0
  110. wayfinder_paths/scripts/validate_manifests.py +213 -0
  111. wayfinder_paths/strategies/__init__.py +0 -0
  112. wayfinder_paths/strategies/basis_trading_strategy/README.md +213 -0
  113. wayfinder_paths/strategies/basis_trading_strategy/__init__.py +3 -0
  114. wayfinder_paths/strategies/basis_trading_strategy/constants.py +1 -0
  115. wayfinder_paths/strategies/basis_trading_strategy/examples.json +16 -0
  116. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +23 -0
  117. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1011 -0
  118. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +4522 -0
  119. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +727 -0
  120. wayfinder_paths/strategies/basis_trading_strategy/types.py +39 -0
  121. wayfinder_paths/strategies/config.py +85 -0
  122. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +100 -0
  123. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +8 -0
  124. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
  125. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +2270 -0
  126. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +352 -0
  127. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +96 -0
  128. wayfinder_paths/strategies/stablecoin_yield_strategy/examples.json +17 -0
  129. wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
  130. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1810 -0
  131. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +520 -0
  132. wayfinder_paths/templates/adapter/README.md +105 -0
  133. wayfinder_paths/templates/adapter/adapter.py +26 -0
  134. wayfinder_paths/templates/adapter/examples.json +8 -0
  135. wayfinder_paths/templates/adapter/manifest.yaml +6 -0
  136. wayfinder_paths/templates/adapter/test_adapter.py +49 -0
  137. wayfinder_paths/templates/strategy/README.md +153 -0
  138. wayfinder_paths/templates/strategy/examples.json +11 -0
  139. wayfinder_paths/templates/strategy/manifest.yaml +8 -0
  140. wayfinder_paths/templates/strategy/strategy.py +57 -0
  141. wayfinder_paths/templates/strategy/test_strategy.py +197 -0
  142. wayfinder_paths/tests/__init__.py +0 -0
  143. wayfinder_paths/tests/test_smoke_manifest.py +48 -0
  144. wayfinder_paths/tests/test_test_coverage.py +212 -0
  145. wayfinder_paths/tests/test_utils.py +64 -0
  146. wayfinder_paths-0.1.7.dist-info/LICENSE +21 -0
  147. wayfinder_paths-0.1.7.dist-info/METADATA +777 -0
  148. wayfinder_paths-0.1.7.dist-info/RECORD +149 -0
  149. wayfinder_paths-0.1.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,212 @@
1
+ """Test to ensure all adapters and strategies have corresponding test files."""
2
+
3
+ from pathlib import Path
4
+
5
+ import pytest
6
+
7
+
8
+ def test_all_adapters_have_tests():
9
+ """Verify that all adapters have a test_adapter.py file."""
10
+ adapters_dir = Path(__file__).parent.parent / "adapters"
11
+
12
+ if not adapters_dir.exists():
13
+ pytest.skip("Adapters directory not found")
14
+
15
+ missing_tests = []
16
+
17
+ # Find all adapter directories (directories containing adapter.py)
18
+ for adapter_dir in adapters_dir.iterdir():
19
+ if not adapter_dir.is_dir() or adapter_dir.name.startswith("_"):
20
+ continue
21
+
22
+ adapter_py = adapter_dir / "adapter.py"
23
+ test_adapter_py = adapter_dir / "test_adapter.py"
24
+
25
+ # If adapter.py exists, test_adapter_py must exist
26
+ if adapter_py.exists() and not test_adapter_py.exists():
27
+ missing_tests.append(adapter_dir.name)
28
+
29
+ if missing_tests:
30
+ pytest.fail(
31
+ f"The following adapters are missing test files:\n"
32
+ f"{', '.join(missing_tests)}\n"
33
+ f"Please create test_adapter.py for each adapter."
34
+ )
35
+
36
+
37
+ def test_all_strategies_have_tests():
38
+ """Verify that all strategies have a test_strategy.py file."""
39
+ strategies_dir = Path(__file__).parent.parent / "strategies"
40
+
41
+ if not strategies_dir.exists():
42
+ pytest.skip("Strategies directory not found")
43
+
44
+ missing_tests = []
45
+
46
+ # Find all strategy directories (directories containing strategy.py)
47
+ for strategy_dir in strategies_dir.iterdir():
48
+ if not strategy_dir.is_dir() or strategy_dir.name.startswith("_"):
49
+ continue
50
+
51
+ strategy_py = strategy_dir / "strategy.py"
52
+ test_strategy_py = strategy_dir / "test_strategy.py"
53
+
54
+ # If strategy.py exists, test_strategy.py must exist
55
+ if strategy_py.exists() and not test_strategy_py.exists():
56
+ missing_tests.append(strategy_dir.name)
57
+
58
+ if missing_tests:
59
+ pytest.fail(
60
+ f"The following strategies are missing test files:\n"
61
+ f"{', '.join(missing_tests)}\n"
62
+ f"Please create test_strategy.py for each strategy."
63
+ )
64
+
65
+
66
+ def test_all_strategies_have_examples_json():
67
+ """Verify that all strategies have an examples.json file (REQUIRED)."""
68
+ strategies_dir = Path(__file__).parent.parent / "strategies"
69
+
70
+ if not strategies_dir.exists():
71
+ pytest.skip("Strategies directory not found")
72
+
73
+ missing_examples = []
74
+
75
+ # Find all strategy directories (directories containing strategy.py)
76
+ for strategy_dir in strategies_dir.iterdir():
77
+ if not strategy_dir.is_dir() or strategy_dir.name.startswith("_"):
78
+ continue
79
+
80
+ strategy_py = strategy_dir / "strategy.py"
81
+ examples_json = strategy_dir / "examples.json"
82
+
83
+ # If strategy.py exists, examples.json must exist
84
+ if strategy_py.exists() and not examples_json.exists():
85
+ missing_examples.append(strategy_dir.name)
86
+
87
+ if missing_examples:
88
+ pytest.fail(
89
+ f"The following strategies are missing examples.json files:\n"
90
+ f"{', '.join(missing_examples)}\n"
91
+ f"examples.json is REQUIRED for all strategies.\n"
92
+ f"See TESTING.md for the required structure."
93
+ )
94
+
95
+
96
+ def test_strategy_tests_use_examples_json():
97
+ """Verify that strategy test files load examples.json using the shared utility."""
98
+ strategies_dir = Path(__file__).parent.parent / "strategies"
99
+
100
+ if not strategies_dir.exists():
101
+ pytest.skip("Strategies directory not found")
102
+
103
+ violations = []
104
+
105
+ # Find all strategy test files
106
+ for strategy_dir in strategies_dir.iterdir():
107
+ if not strategy_dir.is_dir() or strategy_dir.name.startswith("_"):
108
+ continue
109
+
110
+ test_file = strategy_dir / "test_strategy.py"
111
+ if not test_file.exists():
112
+ continue
113
+
114
+ # Read the test file content
115
+ try:
116
+ content = test_file.read_text()
117
+
118
+ # Check if it imports load_strategy_examples from tests.test_utils
119
+ # (wayfinder_paths/ is added to path by conftest.py or inline)
120
+ has_import = (
121
+ "from tests.test_utils import load_strategy_examples" in content
122
+ or "from wayfinder_paths.tests.test_utils import load_strategy_examples"
123
+ in content # alternative
124
+ or "import tests.test_utils" in content
125
+ or "import wayfinder_paths.tests.test_utils" in content # alternative
126
+ or (
127
+ "tests.test_utils" in content
128
+ and "load_strategy_examples" in content
129
+ ) # fallback importlib pattern
130
+ )
131
+
132
+ # Check if it calls load_strategy_examples
133
+ has_usage = "load_strategy_examples" in content
134
+
135
+ # If it doesn't use the shared utility, check for alternative patterns
136
+ if not (has_import and has_usage):
137
+ # Check for hardcoded examples.json loading (old pattern)
138
+ has_hardcoded = (
139
+ 'Path(__file__).parent / "examples.json"' in content
140
+ or "examples.json" in content
141
+ ) and "load_strategy_examples" not in content
142
+
143
+ if has_hardcoded:
144
+ violations.append(
145
+ f"{strategy_dir.name}: Uses hardcoded examples.json loading "
146
+ f"instead of load_strategy_examples() from tests.test_utils"
147
+ )
148
+ elif has_usage and not has_import:
149
+ violations.append(
150
+ f"{strategy_dir.name}: Uses load_strategy_examples but missing import"
151
+ )
152
+ elif not has_usage:
153
+ violations.append(
154
+ f"{strategy_dir.name}: Test file does not appear to load examples.json"
155
+ )
156
+ except Exception as e:
157
+ violations.append(f"{strategy_dir.name}: Error reading test file: {e}")
158
+
159
+ if violations:
160
+ pytest.fail(
161
+ f"The following strategy tests need to use load_strategy_examples():\n"
162
+ f"{chr(10).join(violations)}\n"
163
+ f"All strategy tests MUST use load_strategy_examples() from tests.test_utils.\n"
164
+ f"See TESTING.md for examples."
165
+ )
166
+
167
+
168
+ def test_strategy_examples_have_smoke():
169
+ """Verify that all strategy examples.json files have a 'smoke' entry."""
170
+ strategies_dir = Path(__file__).parent.parent / "strategies"
171
+
172
+ if not strategies_dir.exists():
173
+ pytest.skip("Strategies directory not found")
174
+
175
+ import json
176
+
177
+ missing_smoke = []
178
+
179
+ # Find all strategy directories
180
+ for strategy_dir in strategies_dir.iterdir():
181
+ if not strategy_dir.is_dir() or strategy_dir.name.startswith("_"):
182
+ continue
183
+
184
+ strategy_py = strategy_dir / "strategy.py"
185
+ examples_json = strategy_dir / "examples.json"
186
+
187
+ # Only check strategies that exist
188
+ if not strategy_py.exists():
189
+ continue
190
+
191
+ if not examples_json.exists():
192
+ # This will be caught by test_all_strategies_have_examples_json
193
+ continue
194
+
195
+ try:
196
+ with open(examples_json) as f:
197
+ examples = json.load(f)
198
+
199
+ if "smoke" not in examples:
200
+ missing_smoke.append(strategy_dir.name)
201
+ except json.JSONDecodeError:
202
+ missing_smoke.append(f"{strategy_dir.name} (invalid JSON)")
203
+ except Exception as e:
204
+ missing_smoke.append(f"{strategy_dir.name} (error: {e})")
205
+
206
+ if missing_smoke:
207
+ pytest.fail(
208
+ f"The following strategies' examples.json are missing 'smoke' entry:\n"
209
+ f"{', '.join(missing_smoke)}\n"
210
+ f"All strategies MUST have a 'smoke' example in examples.json.\n"
211
+ f"See TESTING.md for the required structure."
212
+ )
@@ -0,0 +1,64 @@
1
+ """Shared utilities for testing strategies and adapters."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+
8
+ def load_strategy_examples(strategy_test_file: Path) -> dict[str, Any]:
9
+ """Load examples.json for a strategy test file.
10
+
11
+ This is REQUIRED for all strategy tests. The examples.json file serves
12
+ as both documentation and test data, ensuring tests stay in sync with examples.
13
+
14
+ Args:
15
+ strategy_test_file: Path to the test_strategy.py file
16
+
17
+ Returns:
18
+ Dictionary containing examples from examples.json
19
+
20
+ Raises:
21
+ FileNotFoundError: If examples.json does not exist
22
+ json.JSONDecodeError: If examples.json is invalid JSON
23
+ """
24
+ examples_path = strategy_test_file.parent / "examples.json"
25
+
26
+ if not examples_path.exists():
27
+ raise FileNotFoundError(
28
+ f"examples.json is REQUIRED for strategy tests. "
29
+ f"Create it at: {examples_path}\n"
30
+ f"See TESTING.md for the required structure."
31
+ )
32
+
33
+ with open(examples_path) as f:
34
+ return json.load(f)
35
+
36
+
37
+ def get_canonical_examples(examples: dict[str, Any]) -> dict[str, Any]:
38
+ """Extract canonical usage examples from examples.json.
39
+
40
+ Canonical usage is defined as the primary, documented usage patterns
41
+ that demonstrate how the strategy should be used. This includes:
42
+ - 'smoke' example: The basic lifecycle test (deposit → update → status → withdraw)
43
+ - Any examples without 'expect' fields (positive usage patterns)
44
+
45
+ Args:
46
+ examples: The full examples.json dictionary
47
+
48
+ Returns:
49
+ Dictionary of canonical examples keyed by their example name
50
+ """
51
+ canonical = {}
52
+
53
+ # 'smoke' is always canonical
54
+ if "smoke" in examples:
55
+ canonical["smoke"] = examples["smoke"]
56
+
57
+ # Any example without 'expect' is considered canonical usage
58
+ for name, example_data in examples.items():
59
+ if name == "smoke":
60
+ continue # Already added
61
+ if isinstance(example_data, dict) and "expect" not in example_data:
62
+ canonical[name] = example_data
63
+
64
+ return canonical
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Wayfinder
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.