wayfinder-paths 0.1.11__py3-none-any.whl → 0.1.14__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 (66) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +13 -14
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +36 -39
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +123 -0
  4. wayfinder_paths/adapters/brap_adapter/README.md +11 -16
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +87 -75
  6. wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
  7. wayfinder_paths/adapters/brap_adapter/test_adapter.py +121 -59
  8. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +22 -23
  9. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +114 -60
  10. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1 -1
  11. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +44 -5
  12. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +104 -0
  13. wayfinder_paths/adapters/moonwell_adapter/adapter.py +0 -3
  14. wayfinder_paths/adapters/pool_adapter/README.md +11 -27
  15. wayfinder_paths/adapters/pool_adapter/adapter.py +11 -37
  16. wayfinder_paths/adapters/pool_adapter/examples.json +6 -7
  17. wayfinder_paths/adapters/pool_adapter/test_adapter.py +8 -8
  18. wayfinder_paths/adapters/token_adapter/README.md +2 -14
  19. wayfinder_paths/adapters/token_adapter/adapter.py +16 -10
  20. wayfinder_paths/adapters/token_adapter/examples.json +4 -8
  21. wayfinder_paths/adapters/token_adapter/test_adapter.py +5 -3
  22. wayfinder_paths/core/clients/BRAPClient.py +103 -62
  23. wayfinder_paths/core/clients/ClientManager.py +1 -68
  24. wayfinder_paths/core/clients/HyperlendClient.py +127 -66
  25. wayfinder_paths/core/clients/LedgerClient.py +1 -4
  26. wayfinder_paths/core/clients/PoolClient.py +126 -88
  27. wayfinder_paths/core/clients/TokenClient.py +92 -37
  28. wayfinder_paths/core/clients/WalletClient.py +28 -58
  29. wayfinder_paths/core/clients/WayfinderClient.py +33 -166
  30. wayfinder_paths/core/clients/__init__.py +0 -2
  31. wayfinder_paths/core/clients/protocols.py +35 -52
  32. wayfinder_paths/core/clients/sdk_example.py +37 -22
  33. wayfinder_paths/core/config.py +60 -224
  34. wayfinder_paths/core/engine/StrategyJob.py +7 -55
  35. wayfinder_paths/core/services/local_evm_txn.py +28 -10
  36. wayfinder_paths/core/services/local_token_txn.py +1 -1
  37. wayfinder_paths/core/strategies/Strategy.py +3 -5
  38. wayfinder_paths/core/strategies/descriptors.py +7 -0
  39. wayfinder_paths/core/utils/evm_helpers.py +7 -3
  40. wayfinder_paths/core/utils/wallets.py +12 -19
  41. wayfinder_paths/core/wallets/README.md +1 -1
  42. wayfinder_paths/run_strategy.py +8 -17
  43. wayfinder_paths/scripts/create_strategy.py +5 -5
  44. wayfinder_paths/scripts/make_wallets.py +5 -5
  45. wayfinder_paths/scripts/run_strategy.py +3 -3
  46. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -1
  47. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +206 -526
  48. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +228 -11
  49. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
  50. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +41 -25
  51. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
  52. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +1 -1
  53. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +10 -9
  54. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +12 -6
  55. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +3 -3
  56. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +110 -78
  57. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +44 -21
  58. wayfinder_paths/templates/adapter/README.md +1 -1
  59. wayfinder_paths/templates/strategy/README.md +3 -3
  60. wayfinder_paths/templates/strategy/test_strategy.py +3 -2
  61. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/METADATA +21 -59
  62. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/RECORD +64 -65
  63. wayfinder_paths/core/clients/AuthClient.py +0 -83
  64. wayfinder_paths/core/settings.py +0 -61
  65. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/LICENSE +0 -0
  66. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/WHEEL +0 -0
@@ -34,31 +34,31 @@ class TestHyperlendAdapter:
34
34
  async def test_get_stable_markets_success(self, adapter, mock_hyperlend_client):
35
35
  """Test successful stable markets retrieval"""
36
36
  mock_response = {
37
- "markets": [
38
- {
39
- "chain_id": 999,
40
- "underlying_token": "0x1234...",
37
+ "markets": {
38
+ "0x1234...": {
41
39
  "symbol": "USDT",
42
- "apy": 0.05,
43
- "available_liquidity": 1000000,
44
- "buffer_bps": 100,
45
- "min_buffer_tokens": 100.0,
40
+ "symbol_canonical": "usdt",
41
+ "display_symbol": "USDT",
42
+ "reserve": {},
43
+ "decimals": 6,
44
+ "headroom": 1000000000000,
45
+ "supply_cap": 5000000000000,
46
46
  },
47
- {
48
- "chain_id": 999,
49
- "underlying_token": "0x5678...",
47
+ "0x5678...": {
50
48
  "symbol": "USDC",
51
- "apy": 0.04,
52
- "available_liquidity": 2000000,
53
- "buffer_bps": 100,
54
- "min_buffer_tokens": 100.0,
49
+ "symbol_canonical": "usdc",
50
+ "display_symbol": "USDC",
51
+ "reserve": {},
52
+ "decimals": 6,
53
+ "headroom": 2000000000000,
54
+ "supply_cap": 10000000000000,
55
55
  },
56
- ]
56
+ },
57
+ "notes": [],
57
58
  }
58
59
  mock_hyperlend_client.get_stable_markets = AsyncMock(return_value=mock_response)
59
60
 
60
61
  success, data = await adapter.get_stable_markets(
61
- chain_id=999,
62
62
  required_underlying_tokens=1000.0,
63
63
  buffer_bps=100,
64
64
  min_buffer_tokens=100.0,
@@ -67,11 +67,9 @@ class TestHyperlendAdapter:
67
67
  assert success is True
68
68
  assert data == mock_response
69
69
  mock_hyperlend_client.get_stable_markets.assert_called_once_with(
70
- chain_id=999,
71
70
  required_underlying_tokens=1000.0,
72
71
  buffer_bps=100,
73
72
  min_buffer_tokens=100.0,
74
- is_stable_symbol=None,
75
73
  )
76
74
 
77
75
  @pytest.mark.asyncio
@@ -80,27 +78,29 @@ class TestHyperlendAdapter:
80
78
  ):
81
79
  """Test stable markets retrieval with only required chain_id"""
82
80
  mock_response = {
83
- "markets": [
84
- {
85
- "chain_id": 999,
86
- "underlying_token": "0x1234...",
81
+ "markets": {
82
+ "0x1234...": {
87
83
  "symbol": "USDT",
88
- "apy": 0.05,
84
+ "symbol_canonical": "usdt",
85
+ "display_symbol": "USDT",
86
+ "reserve": {},
87
+ "decimals": 6,
88
+ "headroom": 1000000000000,
89
+ "supply_cap": 5000000000000,
89
90
  }
90
- ]
91
+ },
92
+ "notes": [],
91
93
  }
92
94
  mock_hyperlend_client.get_stable_markets = AsyncMock(return_value=mock_response)
93
95
 
94
- success, data = await adapter.get_stable_markets(chain_id=999)
96
+ success, data = await adapter.get_stable_markets()
95
97
 
96
98
  assert success is True
97
99
  assert data == mock_response
98
100
  mock_hyperlend_client.get_stable_markets.assert_called_once_with(
99
- chain_id=999,
100
101
  required_underlying_tokens=None,
101
102
  buffer_bps=None,
102
103
  min_buffer_tokens=None,
103
- is_stable_symbol=None,
104
104
  )
105
105
 
106
106
  @pytest.mark.asyncio
@@ -108,21 +108,19 @@ class TestHyperlendAdapter:
108
108
  self, adapter, mock_hyperlend_client
109
109
  ):
110
110
  """Test stable markets retrieval with partial optional parameters"""
111
- mock_response = {"markets": []}
111
+ mock_response = {"markets": {}, "notes": []}
112
112
  mock_hyperlend_client.get_stable_markets = AsyncMock(return_value=mock_response)
113
113
 
114
114
  success, data = await adapter.get_stable_markets(
115
- chain_id=999, required_underlying_tokens=500.0
115
+ required_underlying_tokens=500.0
116
116
  )
117
117
 
118
118
  assert success is True
119
119
  assert data == mock_response
120
120
  mock_hyperlend_client.get_stable_markets.assert_called_once_with(
121
- chain_id=999,
122
121
  required_underlying_tokens=500.0,
123
122
  buffer_bps=None,
124
123
  min_buffer_tokens=None,
125
- is_stable_symbol=None,
126
124
  )
127
125
 
128
126
  @pytest.mark.asyncio
@@ -132,7 +130,7 @@ class TestHyperlendAdapter:
132
130
  side_effect=Exception("API Error: Connection timeout")
133
131
  )
134
132
 
135
- success, data = await adapter.get_stable_markets(chain_id=999)
133
+ success, data = await adapter.get_stable_markets()
136
134
 
137
135
  assert success is False
138
136
  assert "API Error: Connection timeout" in data
@@ -144,7 +142,7 @@ class TestHyperlendAdapter:
144
142
  side_effect=Exception("HTTP 404 Not Found")
145
143
  )
146
144
 
147
- success, data = await adapter.get_stable_markets(chain_id=999)
145
+ success, data = await adapter.get_stable_markets()
148
146
 
149
147
  assert success is False
150
148
  assert "404" in data or "Not Found" in data
@@ -154,14 +152,14 @@ class TestHyperlendAdapter:
154
152
  self, adapter, mock_hyperlend_client
155
153
  ):
156
154
  """Test stable markets retrieval with empty response"""
157
- mock_response = {"markets": []}
155
+ mock_response = {"markets": {}, "notes": []}
158
156
  mock_hyperlend_client.get_stable_markets = AsyncMock(return_value=mock_response)
159
157
 
160
- success, data = await adapter.get_stable_markets(chain_id=999)
158
+ success, data = await adapter.get_stable_markets()
161
159
 
162
160
  assert success is True
163
161
  assert data == mock_response
164
- assert len(data.get("markets", [])) == 0
162
+ assert len(data.get("markets", {})) == 0
165
163
 
166
164
  def test_adapter_type(self, adapter):
167
165
  """Test adapter has adapter_type"""
@@ -186,59 +184,95 @@ class TestHyperlendAdapter:
186
184
  async def test_get_stable_markets_with_is_stable_symbol(
187
185
  self, adapter, mock_hyperlend_client
188
186
  ):
189
- """Test stable markets retrieval with is_stable_symbol parameter"""
187
+ """Test stable markets retrieval with is_stable_symbol parameter (ignored by API)"""
190
188
  mock_response = {
191
- "markets": [
192
- {
193
- "chain_id": 999,
194
- "underlying_token": "0x1234...",
189
+ "markets": {
190
+ "0x1234...": {
195
191
  "symbol": "USDT",
196
- "apy": 0.05,
192
+ "symbol_canonical": "usdt",
193
+ "display_symbol": "USDT",
194
+ "reserve": {},
195
+ "decimals": 6,
196
+ "headroom": 1000000000000,
197
+ "supply_cap": 5000000000000,
197
198
  }
198
- ]
199
+ },
200
+ "notes": [],
199
201
  }
200
202
  mock_hyperlend_client.get_stable_markets = AsyncMock(return_value=mock_response)
201
203
 
202
- success, data = await adapter.get_stable_markets(
203
- chain_id=999, is_stable_symbol=True
204
- )
204
+ success, data = await adapter.get_stable_markets()
205
205
 
206
206
  assert success is True
207
207
  assert data == mock_response
208
208
  mock_hyperlend_client.get_stable_markets.assert_called_once_with(
209
- chain_id=999,
210
209
  required_underlying_tokens=None,
211
210
  buffer_bps=None,
212
211
  min_buffer_tokens=None,
213
- is_stable_symbol=True,
214
212
  )
215
213
 
216
214
  @pytest.mark.asyncio
217
215
  async def test_get_assets_view_success(self, adapter, mock_hyperlend_client):
218
216
  """Test successful assets view retrieval"""
219
217
  mock_response = {
218
+ "block_number": 12345,
219
+ "user": "0x0c737cB5934afCb5B01965141F865F795B324080",
220
+ "native_balance_wei": 0,
221
+ "native_balance": 0.0,
220
222
  "assets": [
221
223
  {
222
- "token_address": "0x1234...",
224
+ "underlying": "0x1234...",
223
225
  "symbol": "USDT",
224
- "balance": "1000.0",
225
- "supplied": "500.0",
226
- "borrowed": "0.0",
226
+ "symbol_canonical": "usdt",
227
+ "symbol_display": "USDT",
228
+ "decimals": 6,
229
+ "a_token": "0x...",
230
+ "variable_debt_token": "0x...",
231
+ "usage_as_collateral_enabled": True,
232
+ "borrowing_enabled": True,
233
+ "is_active": True,
234
+ "is_frozen": False,
235
+ "is_paused": False,
236
+ "is_siloed_borrowing": False,
237
+ "is_stablecoin": True,
238
+ "underlying_wallet_balance": 1000.0,
239
+ "underlying_wallet_balance_wei": 1000000000,
240
+ "price_usd": 1.0,
241
+ "supply": 500.0,
242
+ "variable_borrow": 0.0,
243
+ "supply_usd": 500.0,
244
+ "variable_borrow_usd": 0.0,
245
+ "supply_apr": 0.05,
246
+ "supply_apy": 0.05,
247
+ "variable_borrow_apr": 0.07,
248
+ "variable_borrow_apy": 0.07,
227
249
  }
228
250
  ],
229
- "total_value": 1000.0,
251
+ "account_data": {
252
+ "total_collateral_base": 500,
253
+ "total_debt_base": 0,
254
+ "available_borrows_base": 400,
255
+ "current_liquidation_threshold": 8000,
256
+ "ltv": 7500,
257
+ "health_factor_wad": 115792089237316195423570985008687907853269984665640564039457584007913129639935,
258
+ "health_factor": 1.157920892373162e59,
259
+ },
260
+ "base_currency_info": {
261
+ "marketReferenceCurrencyUnit": 100000000,
262
+ "marketReferenceCurrencyPriceInUsd": 100000000,
263
+ "networkBaseTokenPriceInUsd": 0,
264
+ "networkBaseTokenPriceDecimals": 8,
265
+ },
230
266
  }
231
267
  mock_hyperlend_client.get_assets_view = AsyncMock(return_value=mock_response)
232
268
 
233
269
  success, data = await adapter.get_assets_view(
234
- chain_id=999,
235
270
  user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
236
271
  )
237
272
 
238
273
  assert success is True
239
274
  assert data == mock_response
240
275
  mock_hyperlend_client.get_assets_view.assert_called_once_with(
241
- chain_id=999,
242
276
  user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
243
277
  )
244
278
 
@@ -250,7 +284,6 @@ class TestHyperlendAdapter:
250
284
  )
251
285
 
252
286
  success, data = await adapter.get_assets_view(
253
- chain_id=999,
254
287
  user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
255
288
  )
256
289
 
@@ -260,15 +293,36 @@ class TestHyperlendAdapter:
260
293
  @pytest.mark.asyncio
261
294
  async def test_get_assets_view_empty_response(self, adapter, mock_hyperlend_client):
262
295
  """Test assets view retrieval with empty response"""
263
- mock_response = {"assets": [], "total_value": 0.0}
296
+ mock_response = {
297
+ "block_number": 12345,
298
+ "user": "0x0c737cB5934afCb5B01965141F865F795B324080",
299
+ "native_balance_wei": 0,
300
+ "native_balance": 0.0,
301
+ "assets": [],
302
+ "account_data": {
303
+ "total_collateral_base": 0,
304
+ "total_debt_base": 0,
305
+ "available_borrows_base": 0,
306
+ "current_liquidation_threshold": 0,
307
+ "ltv": 0,
308
+ "health_factor_wad": 0,
309
+ "health_factor": 0.0,
310
+ },
311
+ "base_currency_info": {
312
+ "marketReferenceCurrencyUnit": 100000000,
313
+ "marketReferenceCurrencyPriceInUsd": 100000000,
314
+ "networkBaseTokenPriceInUsd": 0,
315
+ "networkBaseTokenPriceDecimals": 8,
316
+ },
317
+ }
264
318
  mock_hyperlend_client.get_assets_view = AsyncMock(return_value=mock_response)
265
319
 
266
320
  success, data = await adapter.get_assets_view(
267
- chain_id=999,
268
321
  user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
269
322
  )
270
323
 
271
324
  assert success is True
272
325
  assert data == mock_response
273
326
  assert len(data.get("assets", [])) == 0
274
- assert data.get("total_value") == 0.0
327
+ # New API uses account_data; total_value may not be present
328
+ assert data.get("account_data", {}).get("total_collateral_base") == 0
@@ -31,7 +31,7 @@ except ImportError:
31
31
 
32
32
 
33
33
  class SimpleCache:
34
- """Simple in-memory cache with TTL to replace Django cache."""
34
+ """Simple in-memory cache with TTL for local caching."""
35
35
 
36
36
  def __init__(self):
37
37
  self._cache: dict[str, Any] = {}
@@ -10,6 +10,7 @@ the protocol (for example, by delegating signing to a hosted signer).
10
10
  from __future__ import annotations
11
11
 
12
12
  import uuid
13
+ from collections.abc import Mapping
13
14
  from typing import Any
14
15
 
15
16
  from loguru import logger
@@ -86,11 +87,49 @@ class LocalHyperliquidExecutor:
86
87
  )
87
88
  self.info = Info(base_url, skip_ws=True)
88
89
  self.exchange = Exchange(self._wallet, base_url)
90
+ self._asset_id_to_coin: dict[int, str] | None = None
89
91
 
90
92
  logger.info(
91
93
  f"LocalHyperliquidExecutor initialized for address: {self._wallet.address}"
92
94
  )
93
95
 
96
+ def _get_perp_coin(self, asset_id: int) -> str | None:
97
+ """
98
+ Resolve a perp coin name from a perp asset_id.
99
+
100
+ Note: newer versions of the hyperliquid SDK expose `coin_to_asset` but not
101
+ `asset_to_coin`, so we build the reverse mapping when needed.
102
+ """
103
+ if self._asset_id_to_coin is None:
104
+ mapping: dict[int, str] = {}
105
+
106
+ asset_to_coin = getattr(self.info, "asset_to_coin", None)
107
+ if isinstance(asset_to_coin, Mapping):
108
+ for k, v in asset_to_coin.items():
109
+ try:
110
+ asset_int = int(k)
111
+ except (TypeError, ValueError):
112
+ continue
113
+ if v:
114
+ mapping[asset_int] = str(v)
115
+
116
+ coin_to_asset = getattr(self.info, "coin_to_asset", None)
117
+ try:
118
+ coin_to_asset_dict = dict(coin_to_asset) if coin_to_asset else {}
119
+ except Exception: # noqa: BLE001
120
+ coin_to_asset_dict = {}
121
+ for coin, aid in coin_to_asset_dict.items():
122
+ try:
123
+ asset_int = int(aid)
124
+ except (TypeError, ValueError):
125
+ continue
126
+ if coin and asset_int not in mapping:
127
+ mapping[asset_int] = str(coin)
128
+
129
+ self._asset_id_to_coin = mapping
130
+
131
+ return self._asset_id_to_coin.get(asset_id) if self._asset_id_to_coin else None
132
+
94
133
  def _resolve_private_key(self, config: dict[str, Any]) -> str | None:
95
134
  """Extract private key from config."""
96
135
  # Try strategy_wallet first
@@ -172,7 +211,7 @@ class LocalHyperliquidExecutor:
172
211
  )
173
212
  else:
174
213
  # Perp market order
175
- coin = self.info.asset_to_coin.get(asset_id)
214
+ coin = self._get_perp_coin(asset_id)
176
215
  if not coin:
177
216
  return {
178
217
  "status": "err",
@@ -231,7 +270,7 @@ class LocalHyperliquidExecutor:
231
270
  spot_index = asset_id - 10000
232
271
  coin = f"@{spot_index}"
233
272
  else:
234
- coin = self.info.asset_to_coin.get(asset_id)
273
+ coin = self._get_perp_coin(asset_id)
235
274
  if not coin:
236
275
  return {
237
276
  "status": "err",
@@ -268,7 +307,7 @@ class LocalHyperliquidExecutor:
268
307
  }
269
308
 
270
309
  try:
271
- coin = self.info.asset_to_coin.get(asset_id)
310
+ coin = self._get_perp_coin(asset_id)
272
311
  if not coin:
273
312
  return {
274
313
  "status": "err",
@@ -366,7 +405,7 @@ class LocalHyperliquidExecutor:
366
405
  }
367
406
 
368
407
  try:
369
- coin = self.info.asset_to_coin.get(asset_id)
408
+ coin = self._get_perp_coin(asset_id)
370
409
  if not coin:
371
410
  return {
372
411
  "status": "err",
@@ -439,7 +478,7 @@ class LocalHyperliquidExecutor:
439
478
  spot_index = asset_id - 10000
440
479
  coin = f"@{spot_index}"
441
480
  else:
442
- coin = self.info.asset_to_coin.get(asset_id)
481
+ coin = self._get_perp_coin(asset_id)
443
482
  if not coin:
444
483
  return {
445
484
  "status": "err",
@@ -0,0 +1,104 @@
1
+ """Tests for LocalHyperliquidExecutor."""
2
+
3
+ from types import SimpleNamespace
4
+ from unittest.mock import MagicMock, patch
5
+
6
+ import pytest
7
+
8
+
9
+ class TestLocalHyperliquidExecutor:
10
+ """Tests for LocalHyperliquidExecutor."""
11
+
12
+ @pytest.fixture
13
+ def mock_info_without_asset_to_coin(self):
14
+ """Mock Info that only exposes coin_to_asset (no asset_to_coin)."""
15
+ return SimpleNamespace(coin_to_asset={"HYPE": 7})
16
+
17
+ @pytest.fixture
18
+ def mock_exchange(self):
19
+ """Mock Exchange client."""
20
+ mock = MagicMock()
21
+ mock.update_leverage.return_value = {"status": "ok"}
22
+ mock.market_open.return_value = {"status": "ok"}
23
+ return mock
24
+
25
+ @pytest.mark.asyncio
26
+ async def test_update_leverage_works_without_asset_to_coin(
27
+ self, mock_info_without_asset_to_coin, mock_exchange
28
+ ):
29
+ """update_leverage should map asset_id->coin via coin_to_asset inversion."""
30
+ dummy_wallet = SimpleNamespace(address="0xabc")
31
+
32
+ with patch(
33
+ "wayfinder_paths.adapters.hyperliquid_adapter.executor.Account"
34
+ ) as mock_account:
35
+ mock_account.from_key.return_value = dummy_wallet
36
+ with patch(
37
+ "wayfinder_paths.adapters.hyperliquid_adapter.executor.Info",
38
+ return_value=mock_info_without_asset_to_coin,
39
+ ):
40
+ with patch(
41
+ "wayfinder_paths.adapters.hyperliquid_adapter.executor.Exchange",
42
+ return_value=mock_exchange,
43
+ ):
44
+ from wayfinder_paths.adapters.hyperliquid_adapter.executor import (
45
+ LocalHyperliquidExecutor,
46
+ )
47
+
48
+ executor = LocalHyperliquidExecutor(
49
+ config={"strategy_wallet": {"private_key": "0x" + "11" * 32}}
50
+ )
51
+ resp = await executor.update_leverage(
52
+ asset_id=7,
53
+ leverage=1,
54
+ is_cross=True,
55
+ address="0xabc",
56
+ )
57
+
58
+ assert resp.get("status") == "ok"
59
+ mock_exchange.update_leverage.assert_called_once_with(
60
+ leverage=1,
61
+ name="HYPE",
62
+ is_cross=True,
63
+ )
64
+
65
+ @pytest.mark.asyncio
66
+ async def test_place_market_order_perp_works_without_asset_to_coin(
67
+ self, mock_info_without_asset_to_coin, mock_exchange
68
+ ):
69
+ """place_market_order should map asset_id->coin via coin_to_asset inversion."""
70
+ dummy_wallet = SimpleNamespace(address="0xabc")
71
+
72
+ with patch(
73
+ "wayfinder_paths.adapters.hyperliquid_adapter.executor.Account"
74
+ ) as mock_account:
75
+ mock_account.from_key.return_value = dummy_wallet
76
+ with patch(
77
+ "wayfinder_paths.adapters.hyperliquid_adapter.executor.Info",
78
+ return_value=mock_info_without_asset_to_coin,
79
+ ):
80
+ with patch(
81
+ "wayfinder_paths.adapters.hyperliquid_adapter.executor.Exchange",
82
+ return_value=mock_exchange,
83
+ ):
84
+ from wayfinder_paths.adapters.hyperliquid_adapter.executor import (
85
+ LocalHyperliquidExecutor,
86
+ )
87
+
88
+ executor = LocalHyperliquidExecutor(
89
+ config={"strategy_wallet": {"private_key": "0x" + "11" * 32}}
90
+ )
91
+ resp = await executor.place_market_order(
92
+ asset_id=7,
93
+ is_buy=True,
94
+ slippage=0.01,
95
+ size=1.0,
96
+ address="0xabc",
97
+ reduce_only=False,
98
+ cloid=MagicMock(), # Avoid Cloid conversion
99
+ )
100
+
101
+ assert resp.get("status") == "ok"
102
+ mock_exchange.market_open.assert_called_once()
103
+ _, kwargs = mock_exchange.market_open.call_args
104
+ assert kwargs["name"] == "HYPE"
@@ -23,7 +23,6 @@ from wayfinder_paths.core.constants.moonwell_abi import (
23
23
  WETH_ABI,
24
24
  )
25
25
  from wayfinder_paths.core.services.base import Web3Service
26
- from wayfinder_paths.core.settings import settings
27
26
 
28
27
  # Moonwell Base chain addresses
29
28
  MOONWELL_DEFAULTS = {
@@ -1149,8 +1148,6 @@ class MoonwellAdapter(BaseAdapter):
1149
1148
 
1150
1149
  Includes retry logic with exponential backoff for rate-limited RPCs.
1151
1150
  """
1152
- if getattr(settings, "DRY_RUN", False):
1153
- return True, {"dry_run": True, "transaction": tx}
1154
1151
  if not self.web3:
1155
1152
  return False, "web3 service not configured"
1156
1153
 
@@ -35,8 +35,7 @@ adapter = PoolAdapter()
35
35
 
36
36
  ```python
37
37
  success, data = await adapter.get_pools_by_ids(
38
- pool_ids=["pool-123", "pool-456"],
39
- merge_external=True
38
+ pool_ids=["pool-123", "pool-456"]
40
39
  )
41
40
  if success:
42
41
  pools = data.get("pools", [])
@@ -45,44 +44,29 @@ else:
45
44
  print(f"Error: {data}")
46
45
  ```
47
46
 
48
- ### Get Llama Matches
47
+ ### Get pools (all or filtered)
48
+
49
+ Pass at least `chain_id` when fetching all pools (e.g. `chain_id=8453` for Base):
49
50
 
50
51
  ```python
51
- success, data = await adapter.get_llama_matches()
52
+ success, data = await adapter.get_pools(chain_id=8453)
52
53
  if success:
53
54
  matches = data.get("matches", [])
54
- print(f"Found {len(matches)} Llama matches")
55
55
  for match in matches:
56
- if match.get("llama_stablecoin"):
57
- print(f"Stablecoin pool: {match.get('id')} - APY: {match.get('llama_apy_pct')}%")
58
- else:
59
- print(f"Error: {data}")
60
- ```
61
-
62
- ### Get Llama Reports
63
-
64
- ```python
65
- success, data = await adapter.get_llama_reports(
66
- identifiers=["pool-123", "usd-coin-base", "base_0x1234..."]
67
- )
68
- if success:
69
- reports = data
70
- for identifier, report in reports.items():
71
- print(f"Report for {identifier}: APY {report.get('llama_apy_pct', 0)}%")
56
+ if match.get("stablecoin"):
57
+ print(f"Pool {match.get('id')} - APY: {match.get('apy')}%")
72
58
  else:
73
59
  print(f"Error: {data}")
74
60
  ```
75
61
 
76
- ## Advanced Usage
62
+ Optional: `project="lido"` to filter by project.
77
63
 
78
64
  ## API Endpoints
79
65
 
80
- The adapter uses the following Wayfinder API endpoints:
66
+ The adapter uses the Wayfinder API:
81
67
 
82
- - `GET /api/v1/public/pools/?pool_ids=X` - Get pools by IDs
83
- - `GET /api/v1/public/pools/` - Get all pools
84
- - `GET /api/v1/public/pools/llama/matches/` - Get Llama matches
85
- - `GET /api/v1/public/pools/llama/reports/` - Get Llama reports
68
+ - `GET /v1/blockchain/pools/?chain_id=1&project=lido` - List pools (filter by chain_id, optional project)
69
+ - `POST /v1/blockchain/pools/` - Get pools by IDs (body: `{"pool_ids": ["id1", "id2"]}`)
86
70
 
87
71
  ## Error Handling
88
72