wayfinder-paths 0.1.1__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 (115) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +394 -0
  2. wayfinder_paths/__init__.py +21 -0
  3. wayfinder_paths/config.example.json +20 -0
  4. wayfinder_paths/conftest.py +31 -0
  5. wayfinder_paths/core/__init__.py +13 -0
  6. wayfinder_paths/core/adapters/BaseAdapter.py +48 -0
  7. wayfinder_paths/core/adapters/__init__.py +5 -0
  8. wayfinder_paths/core/adapters/base.py +5 -0
  9. wayfinder_paths/core/clients/AuthClient.py +83 -0
  10. wayfinder_paths/core/clients/BRAPClient.py +90 -0
  11. wayfinder_paths/core/clients/ClientManager.py +231 -0
  12. wayfinder_paths/core/clients/HyperlendClient.py +151 -0
  13. wayfinder_paths/core/clients/LedgerClient.py +222 -0
  14. wayfinder_paths/core/clients/PoolClient.py +96 -0
  15. wayfinder_paths/core/clients/SimulationClient.py +180 -0
  16. wayfinder_paths/core/clients/TokenClient.py +73 -0
  17. wayfinder_paths/core/clients/TransactionClient.py +47 -0
  18. wayfinder_paths/core/clients/WalletClient.py +90 -0
  19. wayfinder_paths/core/clients/WayfinderClient.py +258 -0
  20. wayfinder_paths/core/clients/__init__.py +48 -0
  21. wayfinder_paths/core/clients/protocols.py +295 -0
  22. wayfinder_paths/core/clients/sdk_example.py +115 -0
  23. wayfinder_paths/core/config.py +369 -0
  24. wayfinder_paths/core/constants/__init__.py +26 -0
  25. wayfinder_paths/core/constants/base.py +25 -0
  26. wayfinder_paths/core/constants/erc20_abi.py +118 -0
  27. wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
  28. wayfinder_paths/core/engine/VaultJob.py +182 -0
  29. wayfinder_paths/core/engine/__init__.py +5 -0
  30. wayfinder_paths/core/engine/manifest.py +97 -0
  31. wayfinder_paths/core/services/__init__.py +0 -0
  32. wayfinder_paths/core/services/base.py +177 -0
  33. wayfinder_paths/core/services/local_evm_txn.py +429 -0
  34. wayfinder_paths/core/services/local_token_txn.py +231 -0
  35. wayfinder_paths/core/services/web3_service.py +45 -0
  36. wayfinder_paths/core/settings.py +61 -0
  37. wayfinder_paths/core/strategies/Strategy.py +183 -0
  38. wayfinder_paths/core/strategies/__init__.py +5 -0
  39. wayfinder_paths/core/strategies/base.py +7 -0
  40. wayfinder_paths/core/utils/__init__.py +1 -0
  41. wayfinder_paths/core/utils/evm_helpers.py +165 -0
  42. wayfinder_paths/core/utils/wallets.py +77 -0
  43. wayfinder_paths/core/wallets/README.md +91 -0
  44. wayfinder_paths/core/wallets/WalletManager.py +56 -0
  45. wayfinder_paths/core/wallets/__init__.py +7 -0
  46. wayfinder_paths/run_strategy.py +409 -0
  47. wayfinder_paths/scripts/__init__.py +0 -0
  48. wayfinder_paths/scripts/create_strategy.py +181 -0
  49. wayfinder_paths/scripts/make_wallets.py +160 -0
  50. wayfinder_paths/scripts/validate_manifests.py +213 -0
  51. wayfinder_paths/tests/__init__.py +0 -0
  52. wayfinder_paths/tests/test_smoke_manifest.py +48 -0
  53. wayfinder_paths/tests/test_test_coverage.py +212 -0
  54. wayfinder_paths/tests/test_utils.py +64 -0
  55. wayfinder_paths/vaults/__init__.py +0 -0
  56. wayfinder_paths/vaults/adapters/__init__.py +0 -0
  57. wayfinder_paths/vaults/adapters/balance_adapter/README.md +104 -0
  58. wayfinder_paths/vaults/adapters/balance_adapter/adapter.py +257 -0
  59. wayfinder_paths/vaults/adapters/balance_adapter/examples.json +6 -0
  60. wayfinder_paths/vaults/adapters/balance_adapter/manifest.yaml +8 -0
  61. wayfinder_paths/vaults/adapters/balance_adapter/test_adapter.py +83 -0
  62. wayfinder_paths/vaults/adapters/brap_adapter/README.md +249 -0
  63. wayfinder_paths/vaults/adapters/brap_adapter/__init__.py +7 -0
  64. wayfinder_paths/vaults/adapters/brap_adapter/adapter.py +717 -0
  65. wayfinder_paths/vaults/adapters/brap_adapter/examples.json +175 -0
  66. wayfinder_paths/vaults/adapters/brap_adapter/manifest.yaml +11 -0
  67. wayfinder_paths/vaults/adapters/brap_adapter/test_adapter.py +288 -0
  68. wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +7 -0
  69. wayfinder_paths/vaults/adapters/hyperlend_adapter/adapter.py +298 -0
  70. wayfinder_paths/vaults/adapters/hyperlend_adapter/manifest.yaml +10 -0
  71. wayfinder_paths/vaults/adapters/hyperlend_adapter/test_adapter.py +267 -0
  72. wayfinder_paths/vaults/adapters/ledger_adapter/README.md +158 -0
  73. wayfinder_paths/vaults/adapters/ledger_adapter/__init__.py +7 -0
  74. wayfinder_paths/vaults/adapters/ledger_adapter/adapter.py +286 -0
  75. wayfinder_paths/vaults/adapters/ledger_adapter/examples.json +131 -0
  76. wayfinder_paths/vaults/adapters/ledger_adapter/manifest.yaml +11 -0
  77. wayfinder_paths/vaults/adapters/ledger_adapter/test_adapter.py +202 -0
  78. wayfinder_paths/vaults/adapters/pool_adapter/README.md +218 -0
  79. wayfinder_paths/vaults/adapters/pool_adapter/__init__.py +7 -0
  80. wayfinder_paths/vaults/adapters/pool_adapter/adapter.py +289 -0
  81. wayfinder_paths/vaults/adapters/pool_adapter/examples.json +143 -0
  82. wayfinder_paths/vaults/adapters/pool_adapter/manifest.yaml +10 -0
  83. wayfinder_paths/vaults/adapters/pool_adapter/test_adapter.py +222 -0
  84. wayfinder_paths/vaults/adapters/token_adapter/README.md +101 -0
  85. wayfinder_paths/vaults/adapters/token_adapter/__init__.py +3 -0
  86. wayfinder_paths/vaults/adapters/token_adapter/adapter.py +92 -0
  87. wayfinder_paths/vaults/adapters/token_adapter/examples.json +26 -0
  88. wayfinder_paths/vaults/adapters/token_adapter/manifest.yaml +6 -0
  89. wayfinder_paths/vaults/adapters/token_adapter/test_adapter.py +135 -0
  90. wayfinder_paths/vaults/strategies/__init__.py +0 -0
  91. wayfinder_paths/vaults/strategies/config.py +85 -0
  92. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/README.md +99 -0
  93. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/examples.json +16 -0
  94. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
  95. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/strategy.py +2328 -0
  96. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/test_strategy.py +319 -0
  97. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/README.md +95 -0
  98. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/examples.json +17 -0
  99. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
  100. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/strategy.py +1684 -0
  101. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/test_strategy.py +350 -0
  102. wayfinder_paths/vaults/templates/adapter/README.md +105 -0
  103. wayfinder_paths/vaults/templates/adapter/adapter.py +26 -0
  104. wayfinder_paths/vaults/templates/adapter/examples.json +8 -0
  105. wayfinder_paths/vaults/templates/adapter/manifest.yaml +6 -0
  106. wayfinder_paths/vaults/templates/adapter/test_adapter.py +49 -0
  107. wayfinder_paths/vaults/templates/strategy/README.md +152 -0
  108. wayfinder_paths/vaults/templates/strategy/examples.json +11 -0
  109. wayfinder_paths/vaults/templates/strategy/manifest.yaml +8 -0
  110. wayfinder_paths/vaults/templates/strategy/strategy.py +57 -0
  111. wayfinder_paths/vaults/templates/strategy/test_strategy.py +197 -0
  112. wayfinder_paths-0.1.1.dist-info/LICENSE +21 -0
  113. wayfinder_paths-0.1.1.dist-info/METADATA +727 -0
  114. wayfinder_paths-0.1.1.dist-info/RECORD +115 -0
  115. wayfinder_paths-0.1.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,289 @@
1
+ from typing import Any
2
+
3
+ from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
4
+ from wayfinder_paths.core.clients.PoolClient import PoolClient
5
+
6
+
7
+ class PoolAdapter(BaseAdapter):
8
+ """
9
+ Pool adapter for DeFi pool data and analytics operations.
10
+
11
+ Provides high-level operations for:
12
+ - Fetching pool information and metadata
13
+ - Getting pool analytics and reports
14
+ - Accessing Llama protocol data
15
+ - Pool discovery and filtering
16
+ """
17
+
18
+ adapter_type: str = "POOL"
19
+
20
+ def __init__(
21
+ self,
22
+ config: dict[str, Any] | None = None,
23
+ pool_client: PoolClient | None = None,
24
+ ):
25
+ super().__init__("pool_adapter", config)
26
+ self.pool_client = pool_client or PoolClient()
27
+
28
+ async def get_pools_by_ids(
29
+ self, pool_ids: list[str], merge_external: bool | None = None
30
+ ) -> tuple[bool, Any]:
31
+ """
32
+ Get pool information by pool IDs.
33
+
34
+ Args:
35
+ pool_ids: List of pool identifiers
36
+ merge_external: Whether to merge external data
37
+
38
+ Returns:
39
+ Tuple of (success, data) where data is pool information or error message
40
+ """
41
+ try:
42
+ pool_ids_str = ",".join(pool_ids)
43
+ data = await self.pool_client.get_pools_by_ids(
44
+ pool_ids=pool_ids_str, merge_external=merge_external
45
+ )
46
+ return (True, data)
47
+ except Exception as e:
48
+ self.logger.error(f"Error fetching pools by IDs: {e}")
49
+ return (False, str(e))
50
+
51
+ async def get_all_pools(
52
+ self, merge_external: bool | None = None
53
+ ) -> tuple[bool, Any]:
54
+ """
55
+ Get all available pools.
56
+
57
+ Args:
58
+ merge_external: Whether to merge external data
59
+
60
+ Returns:
61
+ Tuple of (success, data) where data is all pools or error message
62
+ """
63
+ try:
64
+ data = await self.pool_client.get_all_pools(merge_external=merge_external)
65
+ return (True, data)
66
+ except Exception as e:
67
+ self.logger.error(f"Error fetching all pools: {e}")
68
+ return (False, str(e))
69
+
70
+ async def get_combined_pool_reports(self) -> tuple[bool, Any]:
71
+ """
72
+ Get combined pool reports with analytics.
73
+
74
+ Returns:
75
+ Tuple of (success, data) where data is combined reports or error message
76
+ """
77
+ try:
78
+ data = await self.pool_client.get_combined_pool_reports()
79
+ return (True, data)
80
+ except Exception as e:
81
+ self.logger.error(f"Error fetching combined pool reports: {e}")
82
+ return (False, str(e))
83
+
84
+ async def get_llama_matches(self) -> tuple[bool, Any]:
85
+ """
86
+ Get Llama protocol matches for pools.
87
+
88
+ Returns:
89
+ Tuple of (success, data) where data is Llama matches or error message
90
+ """
91
+ try:
92
+ data = await self.pool_client.get_llama_matches()
93
+ return (True, data)
94
+ except Exception as e:
95
+ self.logger.error(f"Error fetching Llama matches: {e}")
96
+ return (False, str(e))
97
+
98
+ async def get_llama_reports(self, identifiers: list[str]) -> tuple[bool, Any]:
99
+ """
100
+ Get Llama reports for specific identifiers.
101
+
102
+ Args:
103
+ identifiers: List of identifiers (token IDs, addresses, pool IDs)
104
+
105
+ Returns:
106
+ Tuple of (success, data) where data is Llama reports or error message
107
+ """
108
+ try:
109
+ identifiers_str = ",".join(identifiers)
110
+ data = await self.pool_client.get_llama_reports(identifiers=identifiers_str)
111
+ return (True, data)
112
+ except Exception as e:
113
+ self.logger.error(f"Error fetching Llama reports: {e}")
114
+ return (False, str(e))
115
+
116
+ async def find_high_yield_pools(
117
+ self,
118
+ min_apy: float = 0.01,
119
+ min_tvl: float = 1000000,
120
+ stablecoin_only: bool = True,
121
+ network_codes: list[str] | None = None,
122
+ ) -> tuple[bool, Any]:
123
+ """
124
+ Find high-yield pools based on criteria.
125
+
126
+ Args:
127
+ min_apy: Minimum APY threshold (as decimal, e.g., 0.01 for 1%)
128
+ min_tvl: Minimum TVL threshold in USD
129
+ stablecoin_only: Whether to filter for stablecoin pools only
130
+ network_codes: List of network codes to filter by
131
+
132
+ Returns:
133
+ Tuple of (success, data) where data is filtered pools or error message
134
+ """
135
+ try:
136
+ # Get Llama matches for yield data
137
+ success, llama_data = await self.get_llama_matches()
138
+ if not success:
139
+ return (False, f"Failed to fetch Llama data: {llama_data}")
140
+
141
+ matches = llama_data.get("matches", [])
142
+ filtered_pools = []
143
+
144
+ for pool in matches:
145
+ # Apply filters
146
+ if stablecoin_only and not pool.get("llama_stablecoin", False):
147
+ continue
148
+
149
+ if pool.get("llama_tvl_usd", 0) < min_tvl:
150
+ continue
151
+
152
+ if (
153
+ pool.get("llama_apy_pct", 0) < min_apy * 100
154
+ ): # Convert to percentage
155
+ continue
156
+
157
+ if network_codes and pool.get("network", "").lower() not in [
158
+ nc.lower() for nc in network_codes
159
+ ]:
160
+ continue
161
+
162
+ filtered_pools.append(pool)
163
+
164
+ # Sort by APY descending
165
+ filtered_pools.sort(key=lambda x: x.get("llama_apy_pct", 0), reverse=True)
166
+
167
+ return (
168
+ True,
169
+ {
170
+ "pools": filtered_pools,
171
+ "total_found": len(filtered_pools),
172
+ "filters_applied": {
173
+ "min_apy": min_apy,
174
+ "min_tvl": min_tvl,
175
+ "stablecoin_only": stablecoin_only,
176
+ "network_codes": network_codes,
177
+ },
178
+ },
179
+ )
180
+ except Exception as e:
181
+ self.logger.error(f"Error finding high yield pools: {e}")
182
+ return (False, str(e))
183
+
184
+ async def get_pool_analytics(self, pool_ids: list[str]) -> tuple[bool, Any]:
185
+ """
186
+ Get comprehensive analytics for specific pools.
187
+
188
+ Args:
189
+ pool_ids: List of pool identifiers
190
+
191
+ Returns:
192
+ Tuple of (success, data) where data is pool analytics or error message
193
+ """
194
+ try:
195
+ # Get pool data
196
+ success, pool_data = await self.get_pools_by_ids(pool_ids)
197
+ if not success:
198
+ return (False, f"Failed to fetch pool data: {pool_data}")
199
+
200
+ # Get Llama reports
201
+ success, llama_data = await self.get_llama_reports(pool_ids)
202
+ if not success:
203
+ self.logger.warning(f"Failed to fetch Llama data: {llama_data}")
204
+ llama_data = {}
205
+
206
+ pools = pool_data.get("pools", [])
207
+ llama_reports = llama_data
208
+
209
+ # Combine data
210
+ analytics = []
211
+ for pool in pools:
212
+ pool_id = pool.get("id")
213
+ llama_report = llama_reports.get(pool_id.lower()) if pool_id else None
214
+
215
+ analytics.append(
216
+ {
217
+ "pool": pool,
218
+ "llama_data": llama_report,
219
+ "combined_apy": (
220
+ llama_report.get("llama_combined_apy_pct", 0) / 100
221
+ if llama_report
222
+ and llama_report.get("llama_combined_apy_pct") is not None
223
+ else pool.get("apy", 0)
224
+ ),
225
+ "tvl_usd": (
226
+ llama_report.get("llama_tvl_usd", 0)
227
+ if llama_report and llama_report.get("llama_tvl_usd")
228
+ else pool.get("tvl", 0)
229
+ ),
230
+ }
231
+ )
232
+
233
+ return (True, {"analytics": analytics, "total_pools": len(analytics)})
234
+ except Exception as e:
235
+ self.logger.error(f"Error getting pool analytics: {e}")
236
+ return (False, str(e))
237
+
238
+ async def search_pools(self, query: str, limit: int = 10) -> tuple[bool, Any]:
239
+ """
240
+ Search pools by name, symbol, or other criteria.
241
+
242
+ Args:
243
+ query: Search query string
244
+ limit: Maximum number of results
245
+
246
+ Returns:
247
+ Tuple of (success, data) where data is search results or error message
248
+ """
249
+ try:
250
+ success, all_pools_data = await self.get_all_pools()
251
+ if not success:
252
+ return (False, f"Failed to fetch pools: {all_pools_data}")
253
+
254
+ pools = all_pools_data.get("pools", [])
255
+ query_lower = query.lower()
256
+
257
+ # Simple text search
258
+ matching_pools = []
259
+ for pool in pools:
260
+ name = pool.get("name", "").lower()
261
+ symbol = pool.get("symbol", "").lower()
262
+ description = pool.get("description", "").lower()
263
+
264
+ if (
265
+ query_lower in name
266
+ or query_lower in symbol
267
+ or query_lower in description
268
+ ):
269
+ matching_pools.append(pool)
270
+
271
+ # Sort by relevance (exact matches first)
272
+ matching_pools.sort(
273
+ key=lambda x: (
274
+ query_lower not in x.get("name", "").lower(),
275
+ query_lower not in x.get("symbol", "").lower(),
276
+ )
277
+ )
278
+
279
+ return (
280
+ True,
281
+ {
282
+ "pools": matching_pools[:limit],
283
+ "total_found": len(matching_pools),
284
+ "query": query,
285
+ },
286
+ )
287
+ except Exception as e:
288
+ self.logger.error(f"Error searching pools: {e}")
289
+ return (False, str(e))
@@ -0,0 +1,143 @@
1
+ {
2
+ "get_pools_by_ids": {
3
+ "description": "Get pool information by pool IDs",
4
+ "input": {
5
+ "pool_ids": ["pool-123", "pool-456"],
6
+ "merge_external": true
7
+ },
8
+ "output": {
9
+ "success": true,
10
+ "data": {
11
+ "pools": [
12
+ {
13
+ "id": "pool-123",
14
+ "name": "USDC/USDT Pool",
15
+ "symbol": "USDC-USDT",
16
+ "apy": 0.05,
17
+ "tvl": 1000000,
18
+ "chain": "base"
19
+ }
20
+ ]
21
+ }
22
+ }
23
+ },
24
+ "get_all_pools": {
25
+ "description": "Get all available pools",
26
+ "input": {
27
+ "merge_external": false
28
+ },
29
+ "output": {
30
+ "success": true,
31
+ "data": {
32
+ "pools": [
33
+ {
34
+ "id": "pool-123",
35
+ "name": "USDC/USDT Pool",
36
+ "symbol": "USDC-USDT",
37
+ "apy": 0.05,
38
+ "tvl": 1000000
39
+ }
40
+ ],
41
+ "total": 1
42
+ }
43
+ }
44
+ },
45
+ "get_llama_matches": {
46
+ "description": "Get Llama protocol matches for pools",
47
+ "input": {},
48
+ "output": {
49
+ "success": true,
50
+ "data": {
51
+ "matches": [
52
+ {
53
+ "id": "pool-123",
54
+ "llama_apy_pct": 5.2,
55
+ "llama_tvl_usd": 1000000,
56
+ "llama_stablecoin": true,
57
+ "llama_il_risk": "no",
58
+ "network": "base"
59
+ }
60
+ ]
61
+ }
62
+ }
63
+ },
64
+ "find_high_yield_pools": {
65
+ "description": "Find high-yield pools based on criteria",
66
+ "input": {
67
+ "min_apy": 0.03,
68
+ "min_tvl": 500000,
69
+ "stablecoin_only": true,
70
+ "network_codes": ["base", "ethereum"]
71
+ },
72
+ "output": {
73
+ "success": true,
74
+ "data": {
75
+ "pools": [
76
+ {
77
+ "id": "pool-123",
78
+ "llama_apy_pct": 5.2,
79
+ "llama_tvl_usd": 1000000,
80
+ "llama_stablecoin": true,
81
+ "network": "base"
82
+ }
83
+ ],
84
+ "total_found": 1,
85
+ "filters_applied": {
86
+ "min_apy": 0.03,
87
+ "min_tvl": 500000,
88
+ "stablecoin_only": true,
89
+ "network_codes": ["base", "ethereum"]
90
+ }
91
+ }
92
+ }
93
+ },
94
+ "get_pool_analytics": {
95
+ "description": "Get comprehensive analytics for specific pools",
96
+ "input": {
97
+ "pool_ids": ["pool-123"]
98
+ },
99
+ "output": {
100
+ "success": true,
101
+ "data": {
102
+ "analytics": [
103
+ {
104
+ "pool": {
105
+ "id": "pool-123",
106
+ "name": "USDC/USDT Pool",
107
+ "symbol": "USDC-USDT"
108
+ },
109
+ "llama_data": {
110
+ "llama_apy_pct": 5.2,
111
+ "llama_tvl_usd": 1000000
112
+ },
113
+ "combined_apy": 0.052,
114
+ "tvl_usd": 1000000
115
+ }
116
+ ],
117
+ "total_pools": 1
118
+ }
119
+ }
120
+ },
121
+ "search_pools": {
122
+ "description": "Search pools by name, symbol, or other criteria",
123
+ "input": {
124
+ "query": "USDC",
125
+ "limit": 5
126
+ },
127
+ "output": {
128
+ "success": true,
129
+ "data": {
130
+ "pools": [
131
+ {
132
+ "id": "pool-123",
133
+ "name": "USDC/USDT Pool",
134
+ "symbol": "USDC-USDT",
135
+ "description": "Stablecoin pool on Base"
136
+ }
137
+ ],
138
+ "total_found": 1,
139
+ "query": "USDC"
140
+ }
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,10 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "vaults.adapters.pool_adapter.adapter.PoolAdapter"
3
+ capabilities:
4
+ - "pool.read"
5
+ - "pool.analytics"
6
+ - "pool.discovery"
7
+ - "llama.data"
8
+ - "pool.reports"
9
+ dependencies:
10
+ - "PoolClient"
@@ -0,0 +1,222 @@
1
+ from unittest.mock import AsyncMock, patch
2
+
3
+ import pytest
4
+
5
+ from wayfinder_paths.vaults.adapters.pool_adapter.adapter import PoolAdapter
6
+
7
+
8
+ class TestPoolAdapter:
9
+ """Test cases for PoolAdapter"""
10
+
11
+ @pytest.fixture
12
+ def mock_pool_client(self):
13
+ """Mock PoolClient for testing"""
14
+ mock_client = AsyncMock()
15
+ return mock_client
16
+
17
+ @pytest.fixture
18
+ def adapter(self, mock_pool_client):
19
+ """Create a PoolAdapter instance with mocked client for testing"""
20
+ with patch(
21
+ "vaults.adapters.pool_adapter.adapter.PoolClient",
22
+ return_value=mock_pool_client,
23
+ ):
24
+ return PoolAdapter()
25
+
26
+ @pytest.mark.asyncio
27
+ async def test_get_pools_by_ids_success(self, adapter, mock_pool_client):
28
+ """Test successful pool retrieval by IDs"""
29
+ mock_response = {
30
+ "pools": [
31
+ {
32
+ "id": "pool-123",
33
+ "name": "USDC/USDT Pool",
34
+ "symbol": "USDC-USDT",
35
+ "apy": 0.05,
36
+ "tvl": 1000000,
37
+ }
38
+ ]
39
+ }
40
+ mock_pool_client.get_pools_by_ids = AsyncMock(return_value=mock_response)
41
+
42
+ success, data = await adapter.get_pools_by_ids(
43
+ pool_ids=["pool-123", "pool-456"], merge_external=True
44
+ )
45
+
46
+ assert success is True
47
+ assert data == mock_response
48
+ mock_pool_client.get_pools_by_ids.assert_called_once_with(
49
+ pool_ids="pool-123,pool-456", merge_external=True
50
+ )
51
+
52
+ @pytest.mark.asyncio
53
+ async def test_get_all_pools_success(self, adapter, mock_pool_client):
54
+ """Test successful retrieval of all pools"""
55
+ # Mock response
56
+ mock_response = {
57
+ "pools": [
58
+ {"id": "pool-123", "name": "Pool 1"},
59
+ {"id": "pool-456", "name": "Pool 2"},
60
+ ],
61
+ "total": 2,
62
+ }
63
+ mock_pool_client.get_all_pools = AsyncMock(return_value=mock_response)
64
+
65
+ success, data = await adapter.get_all_pools(merge_external=False)
66
+
67
+ assert success is True
68
+ assert data == mock_response
69
+ mock_pool_client.get_all_pools.assert_called_once_with(merge_external=False)
70
+
71
+ @pytest.mark.asyncio
72
+ async def test_get_llama_matches_success(self, adapter, mock_pool_client):
73
+ """Test successful Llama matches retrieval"""
74
+ # Mock response
75
+ mock_response = {
76
+ "matches": [
77
+ {
78
+ "id": "pool-123",
79
+ "llama_apy_pct": 5.2,
80
+ "llama_tvl_usd": 1000000,
81
+ "llama_stablecoin": True,
82
+ "network": "base",
83
+ }
84
+ ]
85
+ }
86
+ mock_pool_client.get_llama_matches = AsyncMock(return_value=mock_response)
87
+
88
+ success, data = await adapter.get_llama_matches()
89
+
90
+ assert success is True
91
+ assert data == mock_response
92
+
93
+ @pytest.mark.asyncio
94
+ async def test_find_high_yield_pools_success(self, adapter, mock_pool_client):
95
+ """Test successful high yield pool discovery"""
96
+ mock_llama_response = {
97
+ "matches": [
98
+ {
99
+ "pool_id": "pool-123",
100
+ "llama_apy_pct": 5.2,
101
+ "llama_tvl_usd": 1000000,
102
+ "llama_stablecoin": True,
103
+ "network": "base",
104
+ },
105
+ {
106
+ "pool_id": "pool-456",
107
+ "llama_apy_pct": 2.0,
108
+ "llama_tvl_usd": 500000,
109
+ "llama_stablecoin": True,
110
+ "network": "ethereum",
111
+ },
112
+ {
113
+ "pool_id": "pool-789",
114
+ "llama_apy_pct": 6.0,
115
+ "llama_tvl_usd": 2000000,
116
+ "llama_stablecoin": False,
117
+ "network": "base",
118
+ },
119
+ ]
120
+ }
121
+ mock_pool_client.get_llama_matches = AsyncMock(return_value=mock_llama_response)
122
+
123
+ success, data = await adapter.find_high_yield_pools(
124
+ min_apy=0.03, min_tvl=500000, stablecoin_only=True, network_codes=["base"]
125
+ )
126
+
127
+ assert success is True
128
+ assert len(data["pools"]) == 1 # Only pool-123 meets criteria
129
+ assert (
130
+ data["pools"][0].get("pool_id") == "pool-123"
131
+ or data["pools"][0].get("id") == "pool-123"
132
+ )
133
+ assert data["total_found"] == 1
134
+ assert data["filters_applied"]["min_apy"] == 0.03
135
+ assert data["filters_applied"]["stablecoin_only"] is True
136
+
137
+ @pytest.mark.asyncio
138
+ async def test_get_pool_analytics_success(self, adapter, mock_pool_client):
139
+ """Test successful pool analytics generation"""
140
+ mock_pool_data = {
141
+ "pools": [
142
+ {"id": "pool-123", "name": "USDC/USDT Pool", "symbol": "USDC-USDT"}
143
+ ]
144
+ }
145
+ mock_pool_client.get_pools_by_ids = AsyncMock(return_value=mock_pool_data)
146
+
147
+ mock_llama_data = {
148
+ "pool-123": {
149
+ "llama_apy_pct": 5.2,
150
+ "llama_combined_apy_pct": 5.2,
151
+ "llama_tvl_usd": 1000000,
152
+ }
153
+ }
154
+ mock_pool_client.get_llama_reports = AsyncMock(return_value=mock_llama_data)
155
+
156
+ success, data = await adapter.get_pool_analytics(["pool-123"])
157
+
158
+ assert success is True
159
+ assert len(data["analytics"]) == 1
160
+ assert data["analytics"][0]["pool"]["id"] == "pool-123"
161
+ assert round(data["analytics"][0]["combined_apy"], 6) == round(0.052, 6)
162
+ assert data["analytics"][0]["tvl_usd"] == 1000000
163
+
164
+ @pytest.mark.asyncio
165
+ async def test_search_pools_success(self, adapter, mock_pool_client):
166
+ """Test successful pool search"""
167
+ mock_all_pools = {
168
+ "pools": [
169
+ {
170
+ "id": "pool-123",
171
+ "name": "USDC/USDT Pool",
172
+ "symbol": "USDC-USDT",
173
+ "description": "Stablecoin pool on Base",
174
+ },
175
+ {
176
+ "id": "pool-456",
177
+ "name": "ETH/WETH Pool",
178
+ "symbol": "ETH-WETH",
179
+ "description": "Ethereum pool",
180
+ },
181
+ ]
182
+ }
183
+ mock_pool_client.get_all_pools = AsyncMock(return_value=mock_all_pools)
184
+
185
+ success, data = await adapter.search_pools("USDC", limit=5)
186
+
187
+ assert success is True
188
+ assert len(data["pools"]) == 1
189
+ assert data["pools"][0]["id"] == "pool-123"
190
+ assert data["total_found"] == 1
191
+ assert data["query"] == "USDC"
192
+
193
+ @pytest.mark.asyncio
194
+ async def test_get_pools_by_ids_failure(self, adapter, mock_pool_client):
195
+ """Test pool retrieval failure"""
196
+ mock_pool_client.get_pools_by_ids = AsyncMock(
197
+ side_effect=Exception("API Error")
198
+ )
199
+
200
+ success, data = await adapter.get_pools_by_ids(["pool-123"])
201
+
202
+ assert success is False
203
+ assert "API Error" in data
204
+
205
+ @pytest.mark.asyncio
206
+ async def test_find_high_yield_pools_no_matches(self, adapter, mock_pool_client):
207
+ """Test high yield pool discovery with no matches"""
208
+ mock_llama_response = {"matches": []}
209
+ mock_pool_client.get_llama_matches.return_value = mock_llama_response
210
+
211
+ success, data = await adapter.find_high_yield_pools(
212
+ min_apy=0.10,
213
+ min_tvl=10000000,
214
+ )
215
+
216
+ assert success is True
217
+ assert len(data["pools"]) == 0
218
+ assert data["total_found"] == 0
219
+
220
+ def test_adapter_type(self, adapter):
221
+ """Test adapter has adapter_type"""
222
+ assert adapter.adapter_type == "POOL"