wayfinder-paths 0.1.19__py3-none-any.whl → 0.1.21__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 (98) hide show
  1. wayfinder_paths/__init__.py +0 -2
  2. wayfinder_paths/adapters/balance_adapter/README.md +59 -45
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +1 -22
  4. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -14
  5. wayfinder_paths/adapters/brap_adapter/README.md +61 -184
  6. wayfinder_paths/adapters/brap_adapter/__init__.py +0 -4
  7. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -148
  8. wayfinder_paths/adapters/brap_adapter/test_adapter.py +0 -15
  9. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +0 -4
  10. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +1 -10
  11. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +0 -17
  12. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +3 -312
  13. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +1 -71
  14. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +0 -57
  15. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +0 -17
  16. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +2 -42
  17. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +1 -9
  18. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +15 -47
  19. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +0 -7
  20. wayfinder_paths/adapters/ledger_adapter/README.md +54 -74
  21. wayfinder_paths/adapters/ledger_adapter/__init__.py +0 -4
  22. wayfinder_paths/adapters/ledger_adapter/adapter.py +0 -106
  23. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +0 -12
  24. wayfinder_paths/adapters/moonwell_adapter/README.md +67 -106
  25. wayfinder_paths/adapters/moonwell_adapter/__init__.py +0 -4
  26. wayfinder_paths/adapters/moonwell_adapter/adapter.py +10 -122
  27. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +84 -83
  28. wayfinder_paths/adapters/pool_adapter/README.md +30 -51
  29. wayfinder_paths/adapters/pool_adapter/__init__.py +0 -4
  30. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -19
  31. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -8
  32. wayfinder_paths/adapters/token_adapter/README.md +41 -49
  33. wayfinder_paths/adapters/token_adapter/adapter.py +0 -32
  34. wayfinder_paths/adapters/token_adapter/test_adapter.py +1 -12
  35. wayfinder_paths/conftest.py +0 -8
  36. wayfinder_paths/core/__init__.py +0 -2
  37. wayfinder_paths/core/adapters/BaseAdapter.py +0 -22
  38. wayfinder_paths/core/adapters/__init__.py +0 -5
  39. wayfinder_paths/core/adapters/models.py +0 -5
  40. wayfinder_paths/core/analytics/__init__.py +0 -2
  41. wayfinder_paths/core/analytics/bootstrap.py +0 -16
  42. wayfinder_paths/core/analytics/stats.py +0 -7
  43. wayfinder_paths/core/analytics/test_analytics.py +5 -34
  44. wayfinder_paths/core/clients/BRAPClient.py +0 -35
  45. wayfinder_paths/core/clients/ClientManager.py +0 -51
  46. wayfinder_paths/core/clients/HyperlendClient.py +0 -77
  47. wayfinder_paths/core/clients/LedgerClient.py +2 -122
  48. wayfinder_paths/core/clients/PoolClient.py +0 -2
  49. wayfinder_paths/core/clients/TokenClient.py +0 -39
  50. wayfinder_paths/core/clients/WalletClient.py +0 -15
  51. wayfinder_paths/core/clients/WayfinderClient.py +0 -24
  52. wayfinder_paths/core/clients/__init__.py +0 -4
  53. wayfinder_paths/core/clients/protocols.py +25 -98
  54. wayfinder_paths/core/config.py +0 -24
  55. wayfinder_paths/core/constants/__init__.py +0 -7
  56. wayfinder_paths/core/constants/base.py +2 -9
  57. wayfinder_paths/core/constants/erc20_abi.py +0 -5
  58. wayfinder_paths/core/constants/hyperlend_abi.py +0 -7
  59. wayfinder_paths/core/constants/moonwell_abi.py +0 -35
  60. wayfinder_paths/core/engine/StrategyJob.py +0 -32
  61. wayfinder_paths/core/strategies/Strategy.py +0 -99
  62. wayfinder_paths/core/strategies/__init__.py +0 -2
  63. wayfinder_paths/core/utils/__init__.py +0 -1
  64. wayfinder_paths/core/utils/evm_helpers.py +0 -50
  65. wayfinder_paths/core/utils/{erc20_service.py → tokens.py} +25 -21
  66. wayfinder_paths/core/utils/transaction.py +0 -1
  67. wayfinder_paths/run_strategy.py +0 -46
  68. wayfinder_paths/scripts/create_strategy.py +0 -17
  69. wayfinder_paths/scripts/make_wallets.py +1 -4
  70. wayfinder_paths/strategies/basis_trading_strategy/README.md +71 -163
  71. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +0 -24
  72. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +36 -400
  73. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +15 -64
  74. wayfinder_paths/strategies/basis_trading_strategy/types.py +0 -4
  75. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +65 -56
  76. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +4 -27
  77. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -10
  78. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +71 -72
  79. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +23 -227
  80. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +120 -113
  81. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +64 -59
  82. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +4 -44
  83. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +2 -35
  84. wayfinder_paths/templates/adapter/README.md +107 -46
  85. wayfinder_paths/templates/adapter/adapter.py +0 -9
  86. wayfinder_paths/templates/adapter/test_adapter.py +0 -19
  87. wayfinder_paths/templates/strategy/README.md +113 -59
  88. wayfinder_paths/templates/strategy/strategy.py +0 -22
  89. wayfinder_paths/templates/strategy/test_strategy.py +0 -28
  90. wayfinder_paths/tests/test_test_coverage.py +2 -12
  91. wayfinder_paths/tests/test_utils.py +1 -31
  92. wayfinder_paths-0.1.21.dist-info/METADATA +355 -0
  93. wayfinder_paths-0.1.21.dist-info/RECORD +129 -0
  94. {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.21.dist-info}/WHEEL +1 -1
  95. wayfinder_paths/core/adapters/base.py +0 -5
  96. wayfinder_paths-0.1.19.dist-info/METADATA +0 -592
  97. wayfinder_paths-0.1.19.dist-info/RECORD +0 -130
  98. {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.21.dist-info}/LICENSE +0 -0
@@ -12,22 +12,17 @@ from wayfinder_paths.adapters.moonwell_adapter.adapter import (
12
12
 
13
13
 
14
14
  class TestMoonwellAdapter:
15
- """Test cases for MoonwellAdapter"""
16
-
17
15
  @pytest.fixture
18
16
  def adapter(self):
19
- """Create a MoonwellAdapter instance with mocked services"""
20
17
  config = {
21
18
  "strategy_wallet": {"address": "0x1234567890123456789012345678901234567890"}
22
19
  }
23
- return MoonwellAdapter(config=config, simulation=True)
20
+ return MoonwellAdapter(config=config)
24
21
 
25
22
  def test_adapter_type(self, adapter):
26
- """Test adapter has correct adapter_type"""
27
23
  assert adapter.adapter_type == "MOONWELL"
28
24
 
29
25
  def test_default_addresses(self, adapter):
30
- """Test default Moonwell addresses are set correctly"""
31
26
  assert (
32
27
  adapter.comptroller_address.lower()
33
28
  == MOONWELL_DEFAULTS["comptroller"].lower()
@@ -42,16 +37,13 @@ class TestMoonwellAdapter:
42
37
  assert adapter.well_token.lower() == MOONWELL_DEFAULTS["well_token"].lower()
43
38
 
44
39
  def test_chain_id(self, adapter):
45
- """Test default chain ID is Base"""
46
40
  assert adapter.chain_id == BASE_CHAIN_ID
47
41
 
48
42
  def test_chain_name(self, adapter):
49
- """Test chain name is base"""
50
43
  assert adapter.chain_name == "base"
51
44
 
52
45
  @pytest.mark.asyncio
53
46
  async def test_health_check(self, adapter):
54
- """Test adapter health check"""
55
47
  health = await adapter.health_check()
56
48
  assert isinstance(health, dict)
57
49
  assert health.get("status") in {"healthy", "unhealthy", "error"}
@@ -59,21 +51,28 @@ class TestMoonwellAdapter:
59
51
 
60
52
  @pytest.mark.asyncio
61
53
  async def test_connect(self, adapter):
62
- """Test adapter connection"""
63
54
  ok = await adapter.connect()
64
55
  assert isinstance(ok, bool)
65
56
  assert ok is True
66
57
 
67
58
  @pytest.mark.asyncio
68
- async def test_lend_simulation(self, adapter):
69
- """Test lend operation in simulation mode"""
70
- with patch.object(
71
- adapter, "_encode_call", new_callable=AsyncMock
72
- ) as mock_encode:
59
+ async def test_lend(self, adapter):
60
+ mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
61
+ with (
62
+ patch.object(
63
+ adapter, "_ensure_allowance", new_callable=AsyncMock
64
+ ) as mock_allowance,
65
+ patch.object(
66
+ adapter, "_encode_call", new_callable=AsyncMock
67
+ ) as mock_encode,
68
+ patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
69
+ ):
70
+ mock_allowance.return_value = (True, {})
73
71
  mock_encode.return_value = {
74
72
  "data": "0x1234",
75
73
  "to": MOONWELL_DEFAULTS["m_usdc"],
76
74
  }
75
+ mock_send.return_value = (True, mock_tx_hash)
77
76
 
78
77
  success, result = await adapter.lend(
79
78
  mtoken=MOONWELL_DEFAULTS["m_usdc"],
@@ -82,11 +81,10 @@ class TestMoonwellAdapter:
82
81
  )
83
82
 
84
83
  assert success
85
- assert "simulation" in result
84
+ assert result == mock_tx_hash
86
85
 
87
86
  @pytest.mark.asyncio
88
87
  async def test_lend_invalid_amount(self, adapter):
89
- """Test lend with invalid amount"""
90
88
  success, result = await adapter.lend(
91
89
  mtoken=MOONWELL_DEFAULTS["m_usdc"],
92
90
  underlying_token=MOONWELL_DEFAULTS["usdc"],
@@ -97,8 +95,7 @@ class TestMoonwellAdapter:
97
95
  assert "positive" in result.lower()
98
96
 
99
97
  @pytest.mark.asyncio
100
- async def test_unlend_simulation(self, adapter):
101
- """Test unlend operation in simulation mode"""
98
+ async def test_unlend(self, adapter):
102
99
  # Mock contract encoding
103
100
  mock_contract = MagicMock()
104
101
  mock_contract.functions.redeem = MagicMock(
@@ -113,21 +110,26 @@ class TestMoonwellAdapter:
113
110
  async def mock_web3_ctx(_chain_id):
114
111
  yield mock_web3
115
112
 
116
- with patch(
117
- "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
118
- mock_web3_ctx,
113
+ mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
114
+
115
+ with (
116
+ patch(
117
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
118
+ mock_web3_ctx,
119
+ ),
120
+ patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
119
121
  ):
122
+ mock_send.return_value = (True, mock_tx_hash)
120
123
  success, result = await adapter.unlend(
121
124
  mtoken=MOONWELL_DEFAULTS["m_usdc"],
122
125
  amount=10**8,
123
126
  )
124
127
 
125
128
  assert success
126
- assert "simulation" in result
129
+ assert result == mock_tx_hash
127
130
 
128
131
  @pytest.mark.asyncio
129
132
  async def test_unlend_invalid_amount(self, adapter):
130
- """Test unlend with invalid amount"""
131
133
  success, result = await adapter.unlend(
132
134
  mtoken=MOONWELL_DEFAULTS["m_usdc"],
133
135
  amount=-1,
@@ -137,12 +139,11 @@ class TestMoonwellAdapter:
137
139
  assert "positive" in result.lower()
138
140
 
139
141
  @pytest.mark.asyncio
140
- async def test_borrow_simulation(self, adapter):
141
- """Test borrow operation in simulation mode"""
142
+ async def test_borrow(self, adapter):
142
143
  # Track calls to return different values (0 before, 10**6 after)
143
144
  borrow_balance_calls = [0]
144
145
 
145
- async def mock_borrow_balance_call():
146
+ async def mock_borrow_balance_call(**kwargs):
146
147
  result = borrow_balance_calls[0]
147
148
  # Next call returns increased balance
148
149
  borrow_balance_calls[0] = 10**6
@@ -155,7 +156,7 @@ class TestMoonwellAdapter:
155
156
  )
156
157
  mock_mtoken.functions.borrow = MagicMock(
157
158
  return_value=MagicMock(
158
- call=AsyncMock(return_value=0), # 0 = success
159
+ call=AsyncMock(return_value=0),
159
160
  _encode_transaction_data=MagicMock(return_value="0x1234"),
160
161
  build_transaction=AsyncMock(return_value={"data": "0x1234"}),
161
162
  )
@@ -168,28 +169,42 @@ class TestMoonwellAdapter:
168
169
  async def mock_web3_ctx(_chain_id):
169
170
  yield mock_web3
170
171
 
171
- with patch(
172
- "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
173
- mock_web3_ctx,
172
+ mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
173
+
174
+ with (
175
+ patch(
176
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
177
+ mock_web3_ctx,
178
+ ),
179
+ patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
174
180
  ):
181
+ mock_send.return_value = (True, mock_tx_hash)
175
182
  success, result = await adapter.borrow(
176
183
  mtoken=MOONWELL_DEFAULTS["m_usdc"],
177
184
  amount=10**6,
178
185
  )
179
186
 
180
187
  assert success
181
- assert "simulation" in result
188
+ assert result == mock_tx_hash
182
189
 
183
190
  @pytest.mark.asyncio
184
- async def test_repay_simulation(self, adapter):
185
- """Test repay operation in simulation mode"""
186
- with patch.object(
187
- adapter, "_encode_call", new_callable=AsyncMock
188
- ) as mock_encode:
191
+ async def test_repay(self, adapter):
192
+ mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
193
+ with (
194
+ patch.object(
195
+ adapter, "_ensure_allowance", new_callable=AsyncMock
196
+ ) as mock_allowance,
197
+ patch.object(
198
+ adapter, "_encode_call", new_callable=AsyncMock
199
+ ) as mock_encode,
200
+ patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
201
+ ):
202
+ mock_allowance.return_value = (True, {})
189
203
  mock_encode.return_value = {
190
204
  "data": "0x1234",
191
205
  "to": MOONWELL_DEFAULTS["m_usdc"],
192
206
  }
207
+ mock_send.return_value = (True, mock_tx_hash)
193
208
 
194
209
  success, result = await adapter.repay(
195
210
  mtoken=MOONWELL_DEFAULTS["m_usdc"],
@@ -198,11 +213,10 @@ class TestMoonwellAdapter:
198
213
  )
199
214
 
200
215
  assert success
201
- assert "simulation" in result
216
+ assert result == mock_tx_hash
202
217
 
203
218
  @pytest.mark.asyncio
204
- async def test_set_collateral_simulation(self, adapter):
205
- """Test set collateral operation in simulation mode"""
219
+ async def test_set_collateral(self, adapter):
206
220
  # Mock comptroller contract for verification
207
221
  mock_comptroller = MagicMock()
208
222
  mock_comptroller.functions.checkMembership = MagicMock(
@@ -222,20 +236,25 @@ class TestMoonwellAdapter:
222
236
  async def mock_web3_ctx(_chain_id):
223
237
  yield mock_web3
224
238
 
225
- with patch(
226
- "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
227
- mock_web3_ctx,
239
+ mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
240
+
241
+ with (
242
+ patch(
243
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
244
+ mock_web3_ctx,
245
+ ),
246
+ patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
228
247
  ):
248
+ mock_send.return_value = (True, mock_tx_hash)
229
249
  success, result = await adapter.set_collateral(
230
250
  mtoken=MOONWELL_DEFAULTS["m_wsteth"],
231
251
  )
232
252
 
233
253
  assert success is True
234
- assert "simulation" in result
254
+ assert result == mock_tx_hash
235
255
 
236
256
  @pytest.mark.asyncio
237
- async def test_claim_rewards_simulation(self, adapter):
238
- """Test claim rewards operation in simulation mode"""
257
+ async def test_claim_rewards(self, adapter):
239
258
  # Mock contract for getting outstanding rewards
240
259
  mock_reward_contract = MagicMock()
241
260
  mock_reward_contract.functions.getOutstandingRewardsForUser = MagicMock(
@@ -265,7 +284,6 @@ class TestMoonwellAdapter:
265
284
 
266
285
  @pytest.mark.asyncio
267
286
  async def test_get_pos_success(self, adapter):
268
- """Test get position data"""
269
287
  underlying_addr = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
270
288
 
271
289
  # Mock mtoken contract calls
@@ -317,7 +335,6 @@ class TestMoonwellAdapter:
317
335
 
318
336
  @pytest.mark.asyncio
319
337
  async def test_get_collateral_factor_success(self, adapter):
320
- """Test get collateral factor"""
321
338
  # Clear cache to ensure fresh test
322
339
  adapter._cf_cache.clear()
323
340
 
@@ -348,7 +365,6 @@ class TestMoonwellAdapter:
348
365
 
349
366
  @pytest.mark.asyncio
350
367
  async def test_get_collateral_factor_not_listed(self, adapter):
351
- """Test get collateral factor for unlisted market"""
352
368
  mock_contract = MagicMock()
353
369
  mock_contract.functions.markets = MagicMock(
354
370
  return_value=MagicMock(call=AsyncMock(return_value=(False, 0)))
@@ -373,7 +389,6 @@ class TestMoonwellAdapter:
373
389
 
374
390
  @pytest.mark.asyncio
375
391
  async def test_get_collateral_factor_caching(self, adapter):
376
- """Test that collateral factor is cached and subsequent calls don't hit RPC"""
377
392
  # Clear cache to ensure fresh test
378
393
  adapter._cf_cache.clear()
379
394
 
@@ -411,24 +426,22 @@ class TestMoonwellAdapter:
411
426
  success2, result2 = await adapter.get_collateral_factor(mtoken=mtoken)
412
427
  assert success2 is True
413
428
  assert result2 == 0.80
414
- assert call_count == 1 # Still 1, cache was used
429
+ assert call_count == 1
415
430
 
416
431
  # Third call for same mtoken should still use cache
417
432
  success3, result3 = await adapter.get_collateral_factor(mtoken=mtoken)
418
433
  assert success3 is True
419
434
  assert result3 == 0.80
420
- assert call_count == 1 # Still 1
435
+ assert call_count == 1
421
436
 
422
- # Call for different mtoken should hit RPC
423
437
  success4, result4 = await adapter.get_collateral_factor(
424
438
  mtoken=MOONWELL_DEFAULTS["m_usdc"]
425
439
  )
426
440
  assert success4 is True
427
- assert call_count == 2 # Incremented for new mtoken
441
+ assert call_count == 2
428
442
 
429
443
  @pytest.mark.asyncio
430
444
  async def test_get_collateral_factor_cache_expiry(self, adapter):
431
- """Test that collateral factor cache expires after TTL"""
432
445
  import time
433
446
 
434
447
  from wayfinder_paths.adapters.moonwell_adapter import adapter as adapter_module
@@ -436,12 +449,10 @@ class TestMoonwellAdapter:
436
449
  # Clear cache to ensure fresh test
437
450
  adapter._cf_cache.clear()
438
451
 
439
- # Save original TTL
440
452
  original_ttl = adapter_module.CF_CACHE_TTL
441
453
 
442
454
  try:
443
- # Set a very short TTL for testing
444
- adapter_module.CF_CACHE_TTL = 0.1 # 100ms
455
+ adapter_module.CF_CACHE_TTL = 0.1
445
456
 
446
457
  call_count = 0
447
458
 
@@ -478,7 +489,6 @@ class TestMoonwellAdapter:
478
489
  # Wait for cache to expire
479
490
  time.sleep(0.15)
480
491
 
481
- # Call after expiry should hit RPC again
482
492
  await adapter.get_collateral_factor(mtoken=mtoken)
483
493
  assert call_count == 2
484
494
 
@@ -488,7 +498,6 @@ class TestMoonwellAdapter:
488
498
 
489
499
  @pytest.mark.asyncio
490
500
  async def test_get_apy_supply(self, adapter):
491
- """Test get supply APY"""
492
501
  rate_per_second = int(1.5e9)
493
502
 
494
503
  # Mock mtoken contract
@@ -534,7 +543,6 @@ class TestMoonwellAdapter:
534
543
 
535
544
  @pytest.mark.asyncio
536
545
  async def test_get_apy_borrow(self, adapter):
537
- """Test get borrow APY"""
538
546
  rate_per_second = int(2e9)
539
547
 
540
548
  # Mock mtoken contract
@@ -580,14 +588,9 @@ class TestMoonwellAdapter:
580
588
 
581
589
  @pytest.mark.asyncio
582
590
  async def test_get_borrowable_amount_success(self, adapter):
583
- """Test get borrowable amount"""
584
591
  mock_contract = MagicMock()
585
592
  mock_contract.functions.getAccountLiquidity = MagicMock(
586
- return_value=MagicMock(
587
- call=AsyncMock(
588
- return_value=(0, 10**18, 0)
589
- ) # error, liquidity, shortfall
590
- )
593
+ return_value=MagicMock(call=AsyncMock(return_value=(0, 10**18, 0)))
591
594
  )
592
595
  mock_web3 = MagicMock()
593
596
  mock_web3.eth.contract = MagicMock(return_value=mock_contract)
@@ -607,12 +610,9 @@ class TestMoonwellAdapter:
607
610
 
608
611
  @pytest.mark.asyncio
609
612
  async def test_get_borrowable_amount_shortfall(self, adapter):
610
- """Test get borrowable amount when account has shortfall"""
611
613
  mock_contract = MagicMock()
612
614
  mock_contract.functions.getAccountLiquidity = MagicMock(
613
- return_value=MagicMock(
614
- call=AsyncMock(return_value=(0, 0, 10**16)) # has shortfall
615
- )
615
+ return_value=MagicMock(call=AsyncMock(return_value=(0, 0, 10**16)))
616
616
  )
617
617
  mock_web3 = MagicMock()
618
618
  mock_web3.eth.contract = MagicMock(return_value=mock_contract)
@@ -631,8 +631,7 @@ class TestMoonwellAdapter:
631
631
  assert "shortfall" in result.lower()
632
632
 
633
633
  @pytest.mark.asyncio
634
- async def test_wrap_eth_simulation(self, adapter):
635
- """Test wrap ETH operation in simulation mode"""
634
+ async def test_wrap_eth(self, adapter):
636
635
  mock_contract = MagicMock()
637
636
  mock_contract.functions.deposit = MagicMock(
638
637
  return_value=MagicMock(
@@ -646,29 +645,32 @@ class TestMoonwellAdapter:
646
645
  async def mock_web3_ctx(_chain_id):
647
646
  yield mock_web3
648
647
 
649
- with patch(
650
- "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
651
- mock_web3_ctx,
648
+ mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
649
+
650
+ with (
651
+ patch(
652
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
653
+ mock_web3_ctx,
654
+ ),
655
+ patch.object(adapter, "_send_tx", new_callable=AsyncMock) as mock_send,
652
656
  ):
657
+ mock_send.return_value = (True, mock_tx_hash)
653
658
  success, result = await adapter.wrap_eth(amount=10**18)
654
659
 
655
660
  assert success
656
- assert "simulation" in result
661
+ assert result == mock_tx_hash
657
662
 
658
663
  def test_strategy_address_missing(self):
659
- """Test error when strategy wallet is missing"""
660
- adapter = MoonwellAdapter(config={}, simulation=True)
664
+ adapter = MoonwellAdapter(config={})
661
665
 
662
666
  with pytest.raises(ValueError, match="strategy_wallet"):
663
667
  adapter._strategy_address()
664
668
 
665
669
  def test_checksum_missing_address(self, adapter):
666
- """Test error when address is missing"""
667
670
  with pytest.raises(ValueError, match="Missing required"):
668
671
  adapter._checksum(None)
669
672
 
670
673
  def test_config_override(self):
671
- """Test config can override default addresses"""
672
674
  custom_comptroller = "0x1111111111111111111111111111111111111111"
673
675
  custom_well = "0x2222222222222222222222222222222222222222"
674
676
  config = {
@@ -682,7 +684,7 @@ class TestMoonwellAdapter:
682
684
  },
683
685
  }
684
686
 
685
- adapter = MoonwellAdapter(config=config, simulation=True)
687
+ adapter = MoonwellAdapter(config=config)
686
688
 
687
689
  assert adapter.comptroller_address.lower() == custom_comptroller.lower()
688
690
  assert adapter.well_token.lower() == custom_well.lower()
@@ -690,7 +692,6 @@ class TestMoonwellAdapter:
690
692
 
691
693
  @pytest.mark.asyncio
692
694
  async def test_max_withdrawable_mtoken_zero_balance(self, adapter):
693
- """Test max withdrawable when balance is zero"""
694
695
  # Mock contracts
695
696
  mock_mtoken = MagicMock()
696
697
  mock_mtoken.functions.balanceOf = MagicMock(
@@ -1,37 +1,30 @@
1
1
  # Pool Adapter
2
2
 
3
- A Wayfinder adapter that provides high-level operations for DeFi pool data and analytics. This adapter wraps the `PoolClient` to offer strategy-friendly methods for discovering, analyzing, and filtering yield opportunities.
3
+ Adapter for DeFi pool data and yield analytics.
4
4
 
5
- ## Capabilities
5
+ - **Type**: `POOL`
6
+ - **Module**: `wayfinder_paths.adapters.pool_adapter.adapter.PoolAdapter`
6
7
 
7
- - `pool.read`: Read pool information and metadata
8
- - `pool.analytics`: Get comprehensive pool analytics
9
- - `pool.discovery`: Find and search pools
10
- - `llama.data`: Access Llama protocol data
11
- - `pool.reports`: Get pool reports and analytics
8
+ ## Overview
12
9
 
13
- ## Configuration
14
-
15
- The adapter uses the PoolClient which automatically handles authentication and API configuration through the Wayfinder settings. No additional configuration is required.
16
-
17
- The PoolClient will automatically:
18
-
19
- - Use the WAYFINDER_API_URL from settings
20
- - Handle authentication via config.json
21
- - Manage token refresh and retry logic
10
+ The PoolAdapter provides:
11
+ - Pool information and metadata
12
+ - Yield analytics via DeFi Llama integration
13
+ - Pool discovery and filtering
22
14
 
23
15
  ## Usage
24
16
 
25
- ### Initialize the Adapter
26
-
27
17
  ```python
28
18
  from wayfinder_paths.adapters.pool_adapter.adapter import PoolAdapter
29
19
 
30
- # No configuration needed - uses PoolClient with automatic settings
31
20
  adapter = PoolAdapter()
32
21
  ```
33
22
 
34
- ### Get Pools by IDs
23
+ ## Methods
24
+
25
+ ### get_pools_by_ids
26
+
27
+ Fetch pool information by pool IDs.
35
28
 
36
29
  ```python
37
30
  success, data = await adapter.get_pools_by_ids(
@@ -39,51 +32,37 @@ success, data = await adapter.get_pools_by_ids(
39
32
  )
40
33
  if success:
41
34
  pools = data.get("pools", [])
42
- print(f"Found {len(pools)} pools")
43
- else:
44
- print(f"Error: {data}")
45
35
  ```
46
36
 
47
- ### Get pools (all or filtered)
37
+ ### get_pools
48
38
 
49
- Pass at least `chain_id` when fetching all pools (e.g. `chain_id=8453` for Base):
39
+ Fetch pools with optional filtering.
50
40
 
51
41
  ```python
52
- success, data = await adapter.get_pools(chain_id=8453)
42
+ success, data = await adapter.get_pools(
43
+ chain_id=8453, # Filter by chain (e.g., Base)
44
+ project="lido", # Optional: filter by project
45
+ )
53
46
  if success:
54
47
  matches = data.get("matches", [])
55
- for match in matches:
56
- if match.get("stablecoin"):
57
- print(f"Pool {match.get('id')} - APY: {match.get('apy')}%")
58
- else:
59
- print(f"Error: {data}")
60
48
  ```
61
49
 
62
- Optional: `project="lido"` to filter by project.
63
-
64
- ## API Endpoints
50
+ ## Response Format
65
51
 
66
- The adapter uses the Wayfinder API:
52
+ Pool data includes:
53
+ - `id` - Pool identifier
54
+ - `apy` - Current APY
55
+ - `tvl` - Total value locked
56
+ - `chain` - Chain information
57
+ - `project` - Protocol name
58
+ - `stablecoin` - Whether pool is stablecoin-based
67
59
 
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"]}`)
70
-
71
- ## Error Handling
72
-
73
- All methods return a tuple of `(success: bool, data: Any)` where:
60
+ ## Dependencies
74
61
 
75
- - `success` is `True` if the operation succeeded
76
- - `data` contains the response data on success or error message on failure
62
+ - `PoolClient` - Low-level API client
77
63
 
78
64
  ## Testing
79
65
 
80
- Run the adapter tests:
81
-
82
66
  ```bash
83
- pytest wayfinder_paths/adapters/pool_adapter/test_adapter.py -v
67
+ poetry run pytest wayfinder_paths/adapters/pool_adapter/ -v
84
68
  ```
85
-
86
- ## Dependencies
87
-
88
- - `PoolClient` - Low-level API client for pool operations
89
- - `BaseAdapter` - Base adapter class with common functionality
@@ -1,7 +1,3 @@
1
- """
2
- Pool Adapter Package
3
- """
4
-
5
1
  from .adapter import PoolAdapter
6
2
 
7
3
  __all__ = ["PoolAdapter"]
@@ -9,16 +9,6 @@ from wayfinder_paths.core.clients.PoolClient import (
9
9
 
10
10
 
11
11
  class PoolAdapter(BaseAdapter):
12
- """
13
- Pool adapter for DeFi pool data and analytics operations.
14
-
15
- Provides high-level operations for:
16
- - Fetching pool information and metadata
17
- - Getting pool analytics and reports
18
- - Accessing Llama protocol data
19
- - Pool discovery and filtering
20
- """
21
-
22
12
  adapter_type: str = "POOL"
23
13
 
24
14
  def __init__(
@@ -32,15 +22,6 @@ class PoolAdapter(BaseAdapter):
32
22
  async def get_pools_by_ids(
33
23
  self, pool_ids: list[str]
34
24
  ) -> tuple[bool, PoolList | str]:
35
- """
36
- Get pool information by pool IDs.
37
-
38
- Args:
39
- pool_ids: List of pool identifiers
40
-
41
- Returns:
42
- Tuple of (success, data) where data is pool information or error message
43
- """
44
25
  try:
45
26
  data = await self.pool_client.get_pools_by_ids(pool_ids=pool_ids)
46
27
  return (True, data)
@@ -6,24 +6,19 @@ from wayfinder_paths.adapters.pool_adapter.adapter import PoolAdapter
6
6
 
7
7
 
8
8
  class TestPoolAdapter:
9
- """Test cases for PoolAdapter"""
10
-
11
9
  @pytest.fixture
12
10
  def mock_pool_client(self):
13
- """Mock PoolClient for testing"""
14
11
  mock_client = AsyncMock()
15
12
  return mock_client
16
13
 
17
14
  @pytest.fixture
18
15
  def adapter(self, mock_pool_client):
19
- """Create a PoolAdapter instance with mocked client for testing"""
20
16
  adapter = PoolAdapter()
21
17
  adapter.pool_client = mock_pool_client
22
18
  return adapter
23
19
 
24
20
  @pytest.mark.asyncio
25
21
  async def test_get_pools_by_ids_success(self, adapter, mock_pool_client):
26
- """Test successful pool retrieval by IDs"""
27
22
  mock_response = {
28
23
  "pools": [
29
24
  {
@@ -49,7 +44,6 @@ class TestPoolAdapter:
49
44
 
50
45
  @pytest.mark.asyncio
51
46
  async def test_get_pools_success(self, adapter, mock_pool_client):
52
- """Test successful Llama matches retrieval"""
53
47
  # Mock response
54
48
  mock_response = {
55
49
  "matches": [
@@ -71,7 +65,6 @@ class TestPoolAdapter:
71
65
 
72
66
  @pytest.mark.asyncio
73
67
  async def test_get_pools_by_ids_failure(self, adapter, mock_pool_client):
74
- """Test pool retrieval failure"""
75
68
  mock_pool_client.get_pools_by_ids = AsyncMock(
76
69
  side_effect=Exception("API Error")
77
70
  )
@@ -82,5 +75,4 @@ class TestPoolAdapter:
82
75
  assert "API Error" in data
83
76
 
84
77
  def test_adapter_type(self, adapter):
85
- """Test adapter has adapter_type"""
86
78
  assert adapter.adapter_type == "POOL"