wayfinder-paths 0.1.7__py3-none-any.whl → 0.1.9__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.
- wayfinder_paths/CONFIG_GUIDE.md +5 -14
- wayfinder_paths/adapters/brap_adapter/README.md +1 -1
- wayfinder_paths/adapters/brap_adapter/adapter.py +1 -53
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +5 -7
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
- wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +3 -0
- wayfinder_paths/adapters/pool_adapter/README.md +3 -104
- wayfinder_paths/adapters/pool_adapter/adapter.py +0 -194
- wayfinder_paths/adapters/pool_adapter/examples.json +0 -100
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -134
- wayfinder_paths/adapters/token_adapter/README.md +1 -1
- wayfinder_paths/core/clients/AuthClient.py +0 -3
- wayfinder_paths/core/clients/BRAPClient.py +1 -0
- wayfinder_paths/core/clients/ClientManager.py +1 -22
- wayfinder_paths/core/clients/PoolClient.py +0 -16
- wayfinder_paths/core/clients/WalletClient.py +0 -8
- wayfinder_paths/core/clients/WayfinderClient.py +9 -14
- wayfinder_paths/core/clients/__init__.py +0 -8
- wayfinder_paths/core/clients/protocols.py +0 -64
- wayfinder_paths/core/config.py +5 -45
- wayfinder_paths/core/engine/StrategyJob.py +0 -3
- wayfinder_paths/core/services/base.py +0 -49
- wayfinder_paths/core/services/local_evm_txn.py +3 -82
- wayfinder_paths/core/services/local_token_txn.py +61 -70
- wayfinder_paths/core/services/web3_service.py +0 -2
- wayfinder_paths/core/settings.py +8 -8
- wayfinder_paths/core/strategies/Strategy.py +1 -5
- wayfinder_paths/core/utils/evm_helpers.py +7 -12
- wayfinder_paths/core/wallets/README.md +3 -6
- wayfinder_paths/run_strategy.py +29 -32
- wayfinder_paths/scripts/make_wallets.py +1 -25
- wayfinder_paths/scripts/run_strategy.py +0 -2
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +86 -137
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +106 -28
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1 -6
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +40 -17
- wayfinder_paths/templates/strategy/test_strategy.py +0 -4
- {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/METADATA +33 -15
- {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/RECORD +49 -51
- wayfinder_paths/core/clients/SimulationClient.py +0 -192
- wayfinder_paths/core/clients/TransactionClient.py +0 -63
- {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/WHEEL +0 -0
|
@@ -53,25 +53,6 @@ class PoolAdapter(BaseAdapter):
|
|
|
53
53
|
self.logger.error(f"Error fetching pools by IDs: {e}")
|
|
54
54
|
return (False, str(e))
|
|
55
55
|
|
|
56
|
-
async def get_all_pools(
|
|
57
|
-
self, merge_external: bool | None = None
|
|
58
|
-
) -> tuple[bool, PoolList | str]:
|
|
59
|
-
"""
|
|
60
|
-
Get all available pools.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
merge_external: Whether to merge external data
|
|
64
|
-
|
|
65
|
-
Returns:
|
|
66
|
-
Tuple of (success, data) where data is all pools or error message
|
|
67
|
-
"""
|
|
68
|
-
try:
|
|
69
|
-
data = await self.pool_client.get_all_pools(merge_external=merge_external)
|
|
70
|
-
return (True, data)
|
|
71
|
-
except Exception as e:
|
|
72
|
-
self.logger.error(f"Error fetching all pools: {e}")
|
|
73
|
-
return (False, str(e))
|
|
74
|
-
|
|
75
56
|
async def get_llama_matches(self) -> tuple[bool, dict[str, LlamaMatch] | str]:
|
|
76
57
|
"""
|
|
77
58
|
Get Llama protocol matches for pools.
|
|
@@ -105,178 +86,3 @@ class PoolAdapter(BaseAdapter):
|
|
|
105
86
|
except Exception as e:
|
|
106
87
|
self.logger.error(f"Error fetching Llama reports: {e}")
|
|
107
88
|
return (False, str(e))
|
|
108
|
-
|
|
109
|
-
async def find_high_yield_pools(
|
|
110
|
-
self,
|
|
111
|
-
min_apy: float = 0.01,
|
|
112
|
-
min_tvl: float = 1000000,
|
|
113
|
-
stablecoin_only: bool = True,
|
|
114
|
-
network_codes: list[str] | None = None,
|
|
115
|
-
) -> tuple[bool, Any]:
|
|
116
|
-
"""
|
|
117
|
-
Find high-yield pools based on criteria.
|
|
118
|
-
|
|
119
|
-
Args:
|
|
120
|
-
min_apy: Minimum APY threshold (as decimal, e.g., 0.01 for 1%)
|
|
121
|
-
min_tvl: Minimum TVL threshold in USD
|
|
122
|
-
stablecoin_only: Whether to filter for stablecoin pools only
|
|
123
|
-
network_codes: List of network codes to filter by
|
|
124
|
-
|
|
125
|
-
Returns:
|
|
126
|
-
Tuple of (success, data) where data is filtered pools or error message
|
|
127
|
-
"""
|
|
128
|
-
try:
|
|
129
|
-
# Get Llama matches for yield data
|
|
130
|
-
success, llama_data = await self.get_llama_matches()
|
|
131
|
-
if not success:
|
|
132
|
-
return (False, f"Failed to fetch Llama data: {llama_data}")
|
|
133
|
-
|
|
134
|
-
matches = llama_data.get("matches", [])
|
|
135
|
-
filtered_pools = []
|
|
136
|
-
|
|
137
|
-
for pool in matches:
|
|
138
|
-
# Apply filters
|
|
139
|
-
if stablecoin_only and not pool.get("llama_stablecoin", False):
|
|
140
|
-
continue
|
|
141
|
-
|
|
142
|
-
if pool.get("llama_tvl_usd", 0) < min_tvl:
|
|
143
|
-
continue
|
|
144
|
-
|
|
145
|
-
if (
|
|
146
|
-
pool.get("llama_apy_pct", 0) < min_apy * 100
|
|
147
|
-
): # Convert to percentage
|
|
148
|
-
continue
|
|
149
|
-
|
|
150
|
-
if network_codes and pool.get("network", "").lower() not in [
|
|
151
|
-
nc.lower() for nc in network_codes
|
|
152
|
-
]:
|
|
153
|
-
continue
|
|
154
|
-
|
|
155
|
-
filtered_pools.append(pool)
|
|
156
|
-
|
|
157
|
-
# Sort by APY descending
|
|
158
|
-
filtered_pools.sort(key=lambda x: x.get("llama_apy_pct", 0), reverse=True)
|
|
159
|
-
|
|
160
|
-
return (
|
|
161
|
-
True,
|
|
162
|
-
{
|
|
163
|
-
"pools": filtered_pools,
|
|
164
|
-
"total_found": len(filtered_pools),
|
|
165
|
-
"filters_applied": {
|
|
166
|
-
"min_apy": min_apy,
|
|
167
|
-
"min_tvl": min_tvl,
|
|
168
|
-
"stablecoin_only": stablecoin_only,
|
|
169
|
-
"network_codes": network_codes,
|
|
170
|
-
},
|
|
171
|
-
},
|
|
172
|
-
)
|
|
173
|
-
except Exception as e:
|
|
174
|
-
self.logger.error(f"Error finding high yield pools: {e}")
|
|
175
|
-
return (False, str(e))
|
|
176
|
-
|
|
177
|
-
async def get_pool_analytics(self, pool_ids: list[str]) -> tuple[bool, Any]:
|
|
178
|
-
"""
|
|
179
|
-
Get comprehensive analytics for specific pools.
|
|
180
|
-
|
|
181
|
-
Args:
|
|
182
|
-
pool_ids: List of pool identifiers
|
|
183
|
-
|
|
184
|
-
Returns:
|
|
185
|
-
Tuple of (success, data) where data is pool analytics or error message
|
|
186
|
-
"""
|
|
187
|
-
try:
|
|
188
|
-
# Get pool data
|
|
189
|
-
success, pool_data = await self.get_pools_by_ids(pool_ids)
|
|
190
|
-
if not success:
|
|
191
|
-
return (False, f"Failed to fetch pool data: {pool_data}")
|
|
192
|
-
|
|
193
|
-
# Get Llama reports
|
|
194
|
-
success, llama_data = await self.get_llama_reports(pool_ids)
|
|
195
|
-
if not success:
|
|
196
|
-
self.logger.warning(f"Failed to fetch Llama data: {llama_data}")
|
|
197
|
-
llama_data = {}
|
|
198
|
-
|
|
199
|
-
pools = pool_data.get("pools", [])
|
|
200
|
-
llama_reports = llama_data
|
|
201
|
-
|
|
202
|
-
# Combine data
|
|
203
|
-
analytics = []
|
|
204
|
-
for pool in pools:
|
|
205
|
-
pool_id = pool.get("id")
|
|
206
|
-
llama_report = llama_reports.get(pool_id.lower()) if pool_id else None
|
|
207
|
-
|
|
208
|
-
analytics.append(
|
|
209
|
-
{
|
|
210
|
-
"pool": pool,
|
|
211
|
-
"llama_data": llama_report,
|
|
212
|
-
"combined_apy": (
|
|
213
|
-
llama_report.get("llama_combined_apy_pct", 0) / 100
|
|
214
|
-
if llama_report
|
|
215
|
-
and llama_report.get("llama_combined_apy_pct") is not None
|
|
216
|
-
else pool.get("apy", 0)
|
|
217
|
-
),
|
|
218
|
-
"tvl_usd": (
|
|
219
|
-
llama_report.get("llama_tvl_usd", 0)
|
|
220
|
-
if llama_report and llama_report.get("llama_tvl_usd")
|
|
221
|
-
else pool.get("tvl", 0)
|
|
222
|
-
),
|
|
223
|
-
}
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
return (True, {"analytics": analytics, "total_pools": len(analytics)})
|
|
227
|
-
except Exception as e:
|
|
228
|
-
self.logger.error(f"Error getting pool analytics: {e}")
|
|
229
|
-
return (False, str(e))
|
|
230
|
-
|
|
231
|
-
async def search_pools(self, query: str, limit: int = 10) -> tuple[bool, Any]:
|
|
232
|
-
"""
|
|
233
|
-
Search pools by name, symbol, or other criteria.
|
|
234
|
-
|
|
235
|
-
Args:
|
|
236
|
-
query: Search query string
|
|
237
|
-
limit: Maximum number of results
|
|
238
|
-
|
|
239
|
-
Returns:
|
|
240
|
-
Tuple of (success, data) where data is search results or error message
|
|
241
|
-
"""
|
|
242
|
-
try:
|
|
243
|
-
success, all_pools_data = await self.get_all_pools()
|
|
244
|
-
if not success:
|
|
245
|
-
return (False, f"Failed to fetch pools: {all_pools_data}")
|
|
246
|
-
|
|
247
|
-
pools = all_pools_data.get("pools", [])
|
|
248
|
-
query_lower = query.lower()
|
|
249
|
-
|
|
250
|
-
# Simple text search
|
|
251
|
-
matching_pools = []
|
|
252
|
-
for pool in pools:
|
|
253
|
-
name = pool.get("name", "").lower()
|
|
254
|
-
symbol = pool.get("symbol", "").lower()
|
|
255
|
-
description = pool.get("description", "").lower()
|
|
256
|
-
|
|
257
|
-
if (
|
|
258
|
-
query_lower in name
|
|
259
|
-
or query_lower in symbol
|
|
260
|
-
or query_lower in description
|
|
261
|
-
):
|
|
262
|
-
matching_pools.append(pool)
|
|
263
|
-
|
|
264
|
-
# Sort by relevance (exact matches first)
|
|
265
|
-
matching_pools.sort(
|
|
266
|
-
key=lambda x: (
|
|
267
|
-
query_lower not in x.get("name", "").lower(),
|
|
268
|
-
query_lower not in x.get("symbol", "").lower(),
|
|
269
|
-
)
|
|
270
|
-
)
|
|
271
|
-
|
|
272
|
-
return (
|
|
273
|
-
True,
|
|
274
|
-
{
|
|
275
|
-
"pools": matching_pools[:limit],
|
|
276
|
-
"total_found": len(matching_pools),
|
|
277
|
-
"query": query,
|
|
278
|
-
},
|
|
279
|
-
)
|
|
280
|
-
except Exception as e:
|
|
281
|
-
self.logger.error(f"Error searching pools: {e}")
|
|
282
|
-
return (False, str(e))
|
|
@@ -21,27 +21,6 @@
|
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
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
24
|
"get_llama_matches": {
|
|
46
25
|
"description": "Get Llama protocol matches for pools",
|
|
47
26
|
"input": {},
|
|
@@ -60,84 +39,5 @@
|
|
|
60
39
|
]
|
|
61
40
|
}
|
|
62
41
|
}
|
|
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
42
|
}
|
|
143
43
|
}
|
|
@@ -47,25 +47,6 @@ class TestPoolAdapter:
|
|
|
47
47
|
pool_ids="pool-123,pool-456", merge_external=True
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
-
@pytest.mark.asyncio
|
|
51
|
-
async def test_get_all_pools_success(self, adapter, mock_pool_client):
|
|
52
|
-
"""Test successful retrieval of all pools"""
|
|
53
|
-
# Mock response
|
|
54
|
-
mock_response = {
|
|
55
|
-
"pools": [
|
|
56
|
-
{"id": "pool-123", "name": "Pool 1"},
|
|
57
|
-
{"id": "pool-456", "name": "Pool 2"},
|
|
58
|
-
],
|
|
59
|
-
"total": 2,
|
|
60
|
-
}
|
|
61
|
-
mock_pool_client.get_all_pools = AsyncMock(return_value=mock_response)
|
|
62
|
-
|
|
63
|
-
success, data = await adapter.get_all_pools(merge_external=False)
|
|
64
|
-
|
|
65
|
-
assert success is True
|
|
66
|
-
assert data == mock_response
|
|
67
|
-
mock_pool_client.get_all_pools.assert_called_once_with(merge_external=False)
|
|
68
|
-
|
|
69
50
|
@pytest.mark.asyncio
|
|
70
51
|
async def test_get_llama_matches_success(self, adapter, mock_pool_client):
|
|
71
52
|
"""Test successful Llama matches retrieval"""
|
|
@@ -88,106 +69,6 @@ class TestPoolAdapter:
|
|
|
88
69
|
assert success is True
|
|
89
70
|
assert data == mock_response
|
|
90
71
|
|
|
91
|
-
@pytest.mark.asyncio
|
|
92
|
-
async def test_find_high_yield_pools_success(self, adapter, mock_pool_client):
|
|
93
|
-
"""Test successful high yield pool discovery"""
|
|
94
|
-
mock_llama_response = {
|
|
95
|
-
"matches": [
|
|
96
|
-
{
|
|
97
|
-
"pool_id": "pool-123",
|
|
98
|
-
"llama_apy_pct": 5.2,
|
|
99
|
-
"llama_tvl_usd": 1000000,
|
|
100
|
-
"llama_stablecoin": True,
|
|
101
|
-
"network": "base",
|
|
102
|
-
},
|
|
103
|
-
{
|
|
104
|
-
"pool_id": "pool-456",
|
|
105
|
-
"llama_apy_pct": 2.0,
|
|
106
|
-
"llama_tvl_usd": 500000,
|
|
107
|
-
"llama_stablecoin": True,
|
|
108
|
-
"network": "ethereum",
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
"pool_id": "pool-789",
|
|
112
|
-
"llama_apy_pct": 6.0,
|
|
113
|
-
"llama_tvl_usd": 2000000,
|
|
114
|
-
"llama_stablecoin": False,
|
|
115
|
-
"network": "base",
|
|
116
|
-
},
|
|
117
|
-
]
|
|
118
|
-
}
|
|
119
|
-
mock_pool_client.get_llama_matches = AsyncMock(return_value=mock_llama_response)
|
|
120
|
-
|
|
121
|
-
success, data = await adapter.find_high_yield_pools(
|
|
122
|
-
min_apy=0.03, min_tvl=500000, stablecoin_only=True, network_codes=["base"]
|
|
123
|
-
)
|
|
124
|
-
|
|
125
|
-
assert success is True
|
|
126
|
-
assert len(data["pools"]) == 1 # Only pool-123 meets criteria
|
|
127
|
-
assert (
|
|
128
|
-
data["pools"][0].get("pool_id") == "pool-123"
|
|
129
|
-
or data["pools"][0].get("id") == "pool-123"
|
|
130
|
-
)
|
|
131
|
-
assert data["total_found"] == 1
|
|
132
|
-
assert data["filters_applied"]["min_apy"] == 0.03
|
|
133
|
-
assert data["filters_applied"]["stablecoin_only"] is True
|
|
134
|
-
|
|
135
|
-
@pytest.mark.asyncio
|
|
136
|
-
async def test_get_pool_analytics_success(self, adapter, mock_pool_client):
|
|
137
|
-
"""Test successful pool analytics generation"""
|
|
138
|
-
mock_pool_data = {
|
|
139
|
-
"pools": [
|
|
140
|
-
{"id": "pool-123", "name": "USDC/USDT Pool", "symbol": "USDC-USDT"}
|
|
141
|
-
]
|
|
142
|
-
}
|
|
143
|
-
mock_pool_client.get_pools_by_ids = AsyncMock(return_value=mock_pool_data)
|
|
144
|
-
|
|
145
|
-
mock_llama_data = {
|
|
146
|
-
"pool-123": {
|
|
147
|
-
"llama_apy_pct": 5.2,
|
|
148
|
-
"llama_combined_apy_pct": 5.2,
|
|
149
|
-
"llama_tvl_usd": 1000000,
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
mock_pool_client.get_llama_reports = AsyncMock(return_value=mock_llama_data)
|
|
153
|
-
|
|
154
|
-
success, data = await adapter.get_pool_analytics(["pool-123"])
|
|
155
|
-
|
|
156
|
-
assert success is True
|
|
157
|
-
assert len(data["analytics"]) == 1
|
|
158
|
-
assert data["analytics"][0]["pool"]["id"] == "pool-123"
|
|
159
|
-
assert round(data["analytics"][0]["combined_apy"], 6) == round(0.052, 6)
|
|
160
|
-
assert data["analytics"][0]["tvl_usd"] == 1000000
|
|
161
|
-
|
|
162
|
-
@pytest.mark.asyncio
|
|
163
|
-
async def test_search_pools_success(self, adapter, mock_pool_client):
|
|
164
|
-
"""Test successful pool search"""
|
|
165
|
-
mock_all_pools = {
|
|
166
|
-
"pools": [
|
|
167
|
-
{
|
|
168
|
-
"id": "pool-123",
|
|
169
|
-
"name": "USDC/USDT Pool",
|
|
170
|
-
"symbol": "USDC-USDT",
|
|
171
|
-
"description": "Stablecoin pool on Base",
|
|
172
|
-
},
|
|
173
|
-
{
|
|
174
|
-
"id": "pool-456",
|
|
175
|
-
"name": "ETH/WETH Pool",
|
|
176
|
-
"symbol": "ETH-WETH",
|
|
177
|
-
"description": "Ethereum pool",
|
|
178
|
-
},
|
|
179
|
-
]
|
|
180
|
-
}
|
|
181
|
-
mock_pool_client.get_all_pools = AsyncMock(return_value=mock_all_pools)
|
|
182
|
-
|
|
183
|
-
success, data = await adapter.search_pools("USDC", limit=5)
|
|
184
|
-
|
|
185
|
-
assert success is True
|
|
186
|
-
assert len(data["pools"]) == 1
|
|
187
|
-
assert data["pools"][0]["id"] == "pool-123"
|
|
188
|
-
assert data["total_found"] == 1
|
|
189
|
-
assert data["query"] == "USDC"
|
|
190
|
-
|
|
191
72
|
@pytest.mark.asyncio
|
|
192
73
|
async def test_get_pools_by_ids_failure(self, adapter, mock_pool_client):
|
|
193
74
|
"""Test pool retrieval failure"""
|
|
@@ -200,21 +81,6 @@ class TestPoolAdapter:
|
|
|
200
81
|
assert success is False
|
|
201
82
|
assert "API Error" in data
|
|
202
83
|
|
|
203
|
-
@pytest.mark.asyncio
|
|
204
|
-
async def test_find_high_yield_pools_no_matches(self, adapter, mock_pool_client):
|
|
205
|
-
"""Test high yield pool discovery with no matches"""
|
|
206
|
-
mock_llama_response = {"matches": []}
|
|
207
|
-
mock_pool_client.get_llama_matches.return_value = mock_llama_response
|
|
208
|
-
|
|
209
|
-
success, data = await adapter.find_high_yield_pools(
|
|
210
|
-
min_apy=0.10,
|
|
211
|
-
min_tvl=10000000,
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
assert success is True
|
|
215
|
-
assert len(data["pools"]) == 0
|
|
216
|
-
assert data["total_found"] == 0
|
|
217
|
-
|
|
218
84
|
def test_adapter_type(self, adapter):
|
|
219
85
|
"""Test adapter has adapter_type"""
|
|
220
86
|
assert adapter.adapter_type == "POOL"
|
|
@@ -12,7 +12,7 @@ The adapter uses the TokenClient which automatically handles authentication and
|
|
|
12
12
|
|
|
13
13
|
The TokenClient will automatically:
|
|
14
14
|
- Use the WAYFINDER_API_URL from settings
|
|
15
|
-
- Handle authentication via
|
|
15
|
+
- Handle authentication via config.json
|
|
16
16
|
- Manage token refresh and retry logic
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
1
|
from typing import Any
|
|
3
2
|
|
|
4
3
|
from loguru import logger
|
|
@@ -30,8 +29,6 @@ class AuthClient(WayfinderClient):
|
|
|
30
29
|
creds = self._load_config_credentials()
|
|
31
30
|
if creds.get("api_key"):
|
|
32
31
|
return True
|
|
33
|
-
if os.getenv("WAYFINDER_API_KEY"):
|
|
34
|
-
return True
|
|
35
32
|
except Exception:
|
|
36
33
|
pass
|
|
37
34
|
|
|
@@ -27,6 +27,7 @@ class BRAPQuote(TypedDict):
|
|
|
27
27
|
amount1: Required[str]
|
|
28
28
|
amount2: NotRequired[str]
|
|
29
29
|
routes: NotRequired[list[dict[str, Any]]]
|
|
30
|
+
best_route: NotRequired[dict[str, Any]]
|
|
30
31
|
fees: NotRequired[dict[str, Any] | None]
|
|
31
32
|
slippage: NotRequired[float | None]
|
|
32
33
|
wayfinder_fee: NotRequired[float | None]
|
|
@@ -15,14 +15,10 @@ from wayfinder_paths.core.clients.protocols import (
|
|
|
15
15
|
HyperlendClientProtocol,
|
|
16
16
|
LedgerClientProtocol,
|
|
17
17
|
PoolClientProtocol,
|
|
18
|
-
SimulationClientProtocol,
|
|
19
18
|
TokenClientProtocol,
|
|
20
|
-
TransactionClientProtocol,
|
|
21
19
|
WalletClientProtocol,
|
|
22
20
|
)
|
|
23
|
-
from wayfinder_paths.core.clients.SimulationClient import SimulationClient
|
|
24
21
|
from wayfinder_paths.core.clients.TokenClient import TokenClient
|
|
25
|
-
from wayfinder_paths.core.clients.TransactionClient import TransactionClient
|
|
26
22
|
from wayfinder_paths.core.clients.WalletClient import WalletClient
|
|
27
23
|
|
|
28
24
|
|
|
@@ -32,7 +28,7 @@ class ClientManager:
|
|
|
32
28
|
|
|
33
29
|
Args:
|
|
34
30
|
clients: Optional dict of pre-instantiated clients to inject directly.
|
|
35
|
-
Keys: 'token', 'hyperlend', 'ledger', 'wallet', 'transaction', 'pool', 'brap'
|
|
31
|
+
Keys: 'token', 'hyperlend', 'ledger', 'wallet', 'transaction', 'pool', 'brap'.
|
|
36
32
|
If not provided, defaults to HTTP-based clients.
|
|
37
33
|
skip_auth: If True, skips authentication (for SDK usage).
|
|
38
34
|
"""
|
|
@@ -59,12 +55,10 @@ class ClientManager:
|
|
|
59
55
|
self._auth_client: AuthClient | None = None
|
|
60
56
|
self._token_client: TokenClientProtocol | None = None
|
|
61
57
|
self._wallet_client: WalletClientProtocol | None = None
|
|
62
|
-
self._transaction_client: TransactionClientProtocol | None = None
|
|
63
58
|
self._ledger_client: LedgerClientProtocol | None = None
|
|
64
59
|
self._pool_client: PoolClientProtocol | None = None
|
|
65
60
|
self._hyperlend_client: HyperlendClientProtocol | None = None
|
|
66
61
|
self._brap_client: BRAPClientProtocol | None = None
|
|
67
|
-
self._simulation_client: SimulationClientProtocol | None = None
|
|
68
62
|
|
|
69
63
|
def _get_or_create_client(
|
|
70
64
|
self,
|
|
@@ -109,13 +103,6 @@ class ClientManager:
|
|
|
109
103
|
"""Get or create token client"""
|
|
110
104
|
return self._get_or_create_client("_token_client", "token", TokenClient)
|
|
111
105
|
|
|
112
|
-
@property
|
|
113
|
-
def transaction(self) -> TransactionClientProtocol:
|
|
114
|
-
"""Get or create transaction client"""
|
|
115
|
-
return self._get_or_create_client(
|
|
116
|
-
"_transaction_client", "transaction", TransactionClient
|
|
117
|
-
)
|
|
118
|
-
|
|
119
106
|
@property
|
|
120
107
|
def ledger(self) -> LedgerClientProtocol:
|
|
121
108
|
"""Get or create ledger client"""
|
|
@@ -143,13 +130,6 @@ class ClientManager:
|
|
|
143
130
|
"""Get or create BRAP client"""
|
|
144
131
|
return self._get_or_create_client("_brap_client", "brap", BRAPClient)
|
|
145
132
|
|
|
146
|
-
@property
|
|
147
|
-
def simulation(self) -> SimulationClientProtocol:
|
|
148
|
-
"""Get or create simulation client"""
|
|
149
|
-
return self._get_or_create_client(
|
|
150
|
-
"_simulation_client", "simulation", SimulationClient
|
|
151
|
-
)
|
|
152
|
-
|
|
153
133
|
async def authenticate(
|
|
154
134
|
self,
|
|
155
135
|
username: str | None = None,
|
|
@@ -206,5 +186,4 @@ class ClientManager:
|
|
|
206
186
|
"wallet": self._wallet_client,
|
|
207
187
|
"hyperlend": self._hyperlend_client,
|
|
208
188
|
"brap": self._brap_client,
|
|
209
|
-
"simulation": self._simulation_client,
|
|
210
189
|
}
|
|
@@ -85,22 +85,6 @@ class PoolClient(WayfinderClient):
|
|
|
85
85
|
data = response.json()
|
|
86
86
|
return data.get("data", data)
|
|
87
87
|
|
|
88
|
-
async def get_all_pools(self, *, merge_external: bool | None = None) -> PoolList:
|
|
89
|
-
"""
|
|
90
|
-
Fetch all pools.
|
|
91
|
-
|
|
92
|
-
Example:
|
|
93
|
-
GET /api/v1/public/pools/?merge_external=false
|
|
94
|
-
"""
|
|
95
|
-
url = f"{self.api_base_url}/public/pools/"
|
|
96
|
-
params: dict[str, Any] = {}
|
|
97
|
-
if merge_external is not None:
|
|
98
|
-
params["merge_external"] = "true" if merge_external else "false"
|
|
99
|
-
response = await self._request("GET", url, params=params, headers={})
|
|
100
|
-
response.raise_for_status()
|
|
101
|
-
data = response.json()
|
|
102
|
-
return data.get("data", data)
|
|
103
|
-
|
|
104
88
|
async def get_llama_matches(self) -> dict[str, LlamaMatch]:
|
|
105
89
|
"""
|
|
106
90
|
Fetch Llama matches for pools.
|
|
@@ -33,14 +33,6 @@ class PoolBalance(TypedDict):
|
|
|
33
33
|
usd_value: NotRequired[float | None]
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class EnrichedBalances(TypedDict):
|
|
37
|
-
"""Enriched token balances response structure"""
|
|
38
|
-
|
|
39
|
-
wallet_address: Required[str]
|
|
40
|
-
balances: Required[list[TokenBalance]]
|
|
41
|
-
total_usd_value: NotRequired[float | None]
|
|
42
|
-
|
|
43
|
-
|
|
44
36
|
class WalletClient(WayfinderClient):
|
|
45
37
|
def __init__(self, api_key: str | None = None):
|
|
46
38
|
super().__init__(api_key=api_key)
|
|
@@ -121,17 +121,17 @@ class WayfinderClient:
|
|
|
121
121
|
|
|
122
122
|
async def _ensure_bearer_token(self) -> bool:
|
|
123
123
|
"""
|
|
124
|
-
Ensure Authorization header is set. Priority: existing header > constructor api_key > config.json api_key >
|
|
124
|
+
Ensure Authorization header is set. Priority: existing header > constructor api_key > config.json api_key > config.json tokens > username/password.
|
|
125
125
|
Raises PermissionError if no credentials found.
|
|
126
126
|
"""
|
|
127
127
|
if self.headers.get("Authorization"):
|
|
128
128
|
return True
|
|
129
129
|
|
|
130
|
-
# Check for API key: constructor > config.json
|
|
130
|
+
# Check for API key: constructor > config.json
|
|
131
131
|
api_key = self._api_key
|
|
132
132
|
if not api_key:
|
|
133
133
|
creds = self._load_config_credentials()
|
|
134
|
-
api_key = creds.get("api_key")
|
|
134
|
+
api_key = creds.get("api_key")
|
|
135
135
|
|
|
136
136
|
if api_key:
|
|
137
137
|
api_key = api_key.strip() if isinstance(api_key, str) else api_key
|
|
@@ -142,12 +142,7 @@ class WayfinderClient:
|
|
|
142
142
|
|
|
143
143
|
# Fall back to OAuth token-based auth
|
|
144
144
|
creds = self._load_config_credentials()
|
|
145
|
-
|
|
146
|
-
refresh = creds.get("refresh_token") or os.getenv("WAYFINDER_REFRESH_TOKEN")
|
|
147
|
-
|
|
148
|
-
if access:
|
|
149
|
-
self.set_tokens(access, refresh)
|
|
150
|
-
return True
|
|
145
|
+
refresh = creds.get("refresh_token")
|
|
151
146
|
|
|
152
147
|
if refresh:
|
|
153
148
|
self._refresh_token = refresh
|
|
@@ -155,8 +150,8 @@ class WayfinderClient:
|
|
|
155
150
|
if refreshed:
|
|
156
151
|
return True
|
|
157
152
|
|
|
158
|
-
username = creds.get("username")
|
|
159
|
-
password = creds.get("password")
|
|
153
|
+
username = creds.get("username")
|
|
154
|
+
password = creds.get("password")
|
|
160
155
|
|
|
161
156
|
if username and password:
|
|
162
157
|
try:
|
|
@@ -178,7 +173,7 @@ class WayfinderClient:
|
|
|
178
173
|
pass
|
|
179
174
|
|
|
180
175
|
raise PermissionError(
|
|
181
|
-
"Not authenticated: provide api_key (via constructor
|
|
176
|
+
"Not authenticated: provide api_key (via constructor or config.json) for service account auth, "
|
|
182
177
|
"or username+password/refresh_token in config.json for personal access"
|
|
183
178
|
)
|
|
184
179
|
|
|
@@ -201,11 +196,11 @@ class WayfinderClient:
|
|
|
201
196
|
# Ensure API key or bearer token is set in headers if available and not already set
|
|
202
197
|
# This ensures API keys are passed to all endpoints (including public ones) for rate limiting
|
|
203
198
|
if not self.headers.get("Authorization"):
|
|
204
|
-
# Try to get API key from constructor
|
|
199
|
+
# Try to get API key from constructor or config
|
|
205
200
|
api_key = self._api_key
|
|
206
201
|
if not api_key:
|
|
207
202
|
creds = self._load_config_credentials()
|
|
208
|
-
api_key = creds.get("api_key")
|
|
203
|
+
api_key = creds.get("api_key")
|
|
209
204
|
|
|
210
205
|
if api_key:
|
|
211
206
|
api_key = api_key.strip() if isinstance(api_key, str) else api_key
|