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.
- wayfinder_paths/CONFIG_GUIDE.md +394 -0
- wayfinder_paths/__init__.py +21 -0
- wayfinder_paths/config.example.json +20 -0
- wayfinder_paths/conftest.py +31 -0
- wayfinder_paths/core/__init__.py +13 -0
- wayfinder_paths/core/adapters/BaseAdapter.py +48 -0
- wayfinder_paths/core/adapters/__init__.py +5 -0
- wayfinder_paths/core/adapters/base.py +5 -0
- wayfinder_paths/core/clients/AuthClient.py +83 -0
- wayfinder_paths/core/clients/BRAPClient.py +90 -0
- wayfinder_paths/core/clients/ClientManager.py +231 -0
- wayfinder_paths/core/clients/HyperlendClient.py +151 -0
- wayfinder_paths/core/clients/LedgerClient.py +222 -0
- wayfinder_paths/core/clients/PoolClient.py +96 -0
- wayfinder_paths/core/clients/SimulationClient.py +180 -0
- wayfinder_paths/core/clients/TokenClient.py +73 -0
- wayfinder_paths/core/clients/TransactionClient.py +47 -0
- wayfinder_paths/core/clients/WalletClient.py +90 -0
- wayfinder_paths/core/clients/WayfinderClient.py +258 -0
- wayfinder_paths/core/clients/__init__.py +48 -0
- wayfinder_paths/core/clients/protocols.py +295 -0
- wayfinder_paths/core/clients/sdk_example.py +115 -0
- wayfinder_paths/core/config.py +369 -0
- wayfinder_paths/core/constants/__init__.py +26 -0
- wayfinder_paths/core/constants/base.py +25 -0
- wayfinder_paths/core/constants/erc20_abi.py +118 -0
- wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
- wayfinder_paths/core/engine/VaultJob.py +182 -0
- wayfinder_paths/core/engine/__init__.py +5 -0
- wayfinder_paths/core/engine/manifest.py +97 -0
- wayfinder_paths/core/services/__init__.py +0 -0
- wayfinder_paths/core/services/base.py +177 -0
- wayfinder_paths/core/services/local_evm_txn.py +429 -0
- wayfinder_paths/core/services/local_token_txn.py +231 -0
- wayfinder_paths/core/services/web3_service.py +45 -0
- wayfinder_paths/core/settings.py +61 -0
- wayfinder_paths/core/strategies/Strategy.py +183 -0
- wayfinder_paths/core/strategies/__init__.py +5 -0
- wayfinder_paths/core/strategies/base.py +7 -0
- wayfinder_paths/core/utils/__init__.py +1 -0
- wayfinder_paths/core/utils/evm_helpers.py +165 -0
- wayfinder_paths/core/utils/wallets.py +77 -0
- wayfinder_paths/core/wallets/README.md +91 -0
- wayfinder_paths/core/wallets/WalletManager.py +56 -0
- wayfinder_paths/core/wallets/__init__.py +7 -0
- wayfinder_paths/run_strategy.py +409 -0
- wayfinder_paths/scripts/__init__.py +0 -0
- wayfinder_paths/scripts/create_strategy.py +181 -0
- wayfinder_paths/scripts/make_wallets.py +160 -0
- wayfinder_paths/scripts/validate_manifests.py +213 -0
- wayfinder_paths/tests/__init__.py +0 -0
- wayfinder_paths/tests/test_smoke_manifest.py +48 -0
- wayfinder_paths/tests/test_test_coverage.py +212 -0
- wayfinder_paths/tests/test_utils.py +64 -0
- wayfinder_paths/vaults/__init__.py +0 -0
- wayfinder_paths/vaults/adapters/__init__.py +0 -0
- wayfinder_paths/vaults/adapters/balance_adapter/README.md +104 -0
- wayfinder_paths/vaults/adapters/balance_adapter/adapter.py +257 -0
- wayfinder_paths/vaults/adapters/balance_adapter/examples.json +6 -0
- wayfinder_paths/vaults/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/vaults/adapters/balance_adapter/test_adapter.py +83 -0
- wayfinder_paths/vaults/adapters/brap_adapter/README.md +249 -0
- wayfinder_paths/vaults/adapters/brap_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/brap_adapter/adapter.py +717 -0
- wayfinder_paths/vaults/adapters/brap_adapter/examples.json +175 -0
- wayfinder_paths/vaults/adapters/brap_adapter/manifest.yaml +11 -0
- wayfinder_paths/vaults/adapters/brap_adapter/test_adapter.py +288 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/adapter.py +298 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/manifest.yaml +10 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/test_adapter.py +267 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/README.md +158 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/adapter.py +286 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/examples.json +131 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/manifest.yaml +11 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/test_adapter.py +202 -0
- wayfinder_paths/vaults/adapters/pool_adapter/README.md +218 -0
- wayfinder_paths/vaults/adapters/pool_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/pool_adapter/adapter.py +289 -0
- wayfinder_paths/vaults/adapters/pool_adapter/examples.json +143 -0
- wayfinder_paths/vaults/adapters/pool_adapter/manifest.yaml +10 -0
- wayfinder_paths/vaults/adapters/pool_adapter/test_adapter.py +222 -0
- wayfinder_paths/vaults/adapters/token_adapter/README.md +101 -0
- wayfinder_paths/vaults/adapters/token_adapter/__init__.py +3 -0
- wayfinder_paths/vaults/adapters/token_adapter/adapter.py +92 -0
- wayfinder_paths/vaults/adapters/token_adapter/examples.json +26 -0
- wayfinder_paths/vaults/adapters/token_adapter/manifest.yaml +6 -0
- wayfinder_paths/vaults/adapters/token_adapter/test_adapter.py +135 -0
- wayfinder_paths/vaults/strategies/__init__.py +0 -0
- wayfinder_paths/vaults/strategies/config.py +85 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/README.md +99 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/examples.json +16 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/strategy.py +2328 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/test_strategy.py +319 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/README.md +95 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/examples.json +17 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/strategy.py +1684 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/test_strategy.py +350 -0
- wayfinder_paths/vaults/templates/adapter/README.md +105 -0
- wayfinder_paths/vaults/templates/adapter/adapter.py +26 -0
- wayfinder_paths/vaults/templates/adapter/examples.json +8 -0
- wayfinder_paths/vaults/templates/adapter/manifest.yaml +6 -0
- wayfinder_paths/vaults/templates/adapter/test_adapter.py +49 -0
- wayfinder_paths/vaults/templates/strategy/README.md +152 -0
- wayfinder_paths/vaults/templates/strategy/examples.json +11 -0
- wayfinder_paths/vaults/templates/strategy/manifest.yaml +8 -0
- wayfinder_paths/vaults/templates/strategy/strategy.py +57 -0
- wayfinder_paths/vaults/templates/strategy/test_strategy.py +197 -0
- wayfinder_paths-0.1.1.dist-info/LICENSE +21 -0
- wayfinder_paths-0.1.1.dist-info/METADATA +727 -0
- wayfinder_paths-0.1.1.dist-info/RECORD +115 -0
- 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,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"
|