wayfinder-paths 0.1.9__py3-none-any.whl → 0.1.11__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 (54) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +1 -2
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +4 -4
  3. wayfinder_paths/adapters/brap_adapter/adapter.py +139 -23
  4. wayfinder_paths/adapters/moonwell_adapter/README.md +174 -0
  5. wayfinder_paths/adapters/moonwell_adapter/__init__.py +7 -0
  6. wayfinder_paths/adapters/moonwell_adapter/adapter.py +1226 -0
  7. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +635 -0
  8. wayfinder_paths/core/clients/AuthClient.py +3 -0
  9. wayfinder_paths/core/clients/WayfinderClient.py +2 -2
  10. wayfinder_paths/core/constants/__init__.py +0 -2
  11. wayfinder_paths/core/constants/base.py +6 -2
  12. wayfinder_paths/core/constants/moonwell_abi.py +411 -0
  13. wayfinder_paths/core/engine/StrategyJob.py +3 -0
  14. wayfinder_paths/core/services/local_evm_txn.py +182 -217
  15. wayfinder_paths/core/services/local_token_txn.py +46 -26
  16. wayfinder_paths/core/strategies/descriptors.py +1 -1
  17. wayfinder_paths/core/utils/evm_helpers.py +0 -27
  18. wayfinder_paths/run_strategy.py +34 -74
  19. wayfinder_paths/scripts/create_strategy.py +2 -27
  20. wayfinder_paths/scripts/run_strategy.py +37 -7
  21. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +1 -1
  22. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +0 -15
  23. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +1 -1
  24. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +108 -0
  25. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/examples.json +11 -0
  26. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2975 -0
  27. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +886 -0
  28. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +0 -7
  29. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1 -1
  30. wayfinder_paths/templates/adapter/README.md +5 -21
  31. wayfinder_paths/templates/adapter/adapter.py +1 -2
  32. wayfinder_paths/templates/adapter/test_adapter.py +1 -1
  33. wayfinder_paths/templates/strategy/README.md +4 -21
  34. wayfinder_paths/tests/test_smoke_manifest.py +17 -2
  35. {wayfinder_paths-0.1.9.dist-info → wayfinder_paths-0.1.11.dist-info}/METADATA +60 -187
  36. {wayfinder_paths-0.1.9.dist-info → wayfinder_paths-0.1.11.dist-info}/RECORD +38 -45
  37. wayfinder_paths/CONFIG_GUIDE.md +0 -390
  38. wayfinder_paths/adapters/balance_adapter/manifest.yaml +0 -8
  39. wayfinder_paths/adapters/brap_adapter/manifest.yaml +0 -11
  40. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +0 -10
  41. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +0 -8
  42. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +0 -11
  43. wayfinder_paths/adapters/pool_adapter/manifest.yaml +0 -10
  44. wayfinder_paths/adapters/token_adapter/manifest.yaml +0 -6
  45. wayfinder_paths/config.example.json +0 -22
  46. wayfinder_paths/core/engine/manifest.py +0 -97
  47. wayfinder_paths/scripts/validate_manifests.py +0 -213
  48. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +0 -23
  49. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +0 -7
  50. wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +0 -17
  51. wayfinder_paths/templates/adapter/manifest.yaml +0 -6
  52. wayfinder_paths/templates/strategy/manifest.yaml +0 -8
  53. {wayfinder_paths-0.1.9.dist-info → wayfinder_paths-0.1.11.dist-info}/LICENSE +0 -0
  54. {wayfinder_paths-0.1.9.dist-info → wayfinder_paths-0.1.11.dist-info}/WHEEL +0 -0
@@ -3,12 +3,11 @@
3
3
  Adapter that exposes wallet, token, and pool balances backed by `WalletClient`/`TokenClient` and now orchestrates transfers between the configured main/strategy wallets (with ledger bookkeeping).
4
4
 
5
5
  - Entrypoint: `adapters.balance_adapter.adapter.BalanceAdapter`
6
- - Manifest: `manifest.yaml`
7
6
  - Tests: `test_adapter.py`
8
7
 
9
8
  ## Capabilities
10
9
 
11
- The adapter declares both `wallet_read` and `wallet_transfer` capabilities in its manifest. Transfers are executed by leveraging the shared `DefaultWeb3Service.token_transactions` helper, but ledger recording + wallet selection now live inside the adapter.
10
+ The adapter provides both wallet read and wallet transfer capabilities. Transfers are executed by leveraging the shared `DefaultWeb3Service.token_transactions` helper, but ledger recording + wallet selection now live inside the adapter.
12
11
 
13
12
  ## Construction
14
13
 
@@ -170,7 +170,7 @@ class BalanceAdapter(BaseAdapter):
170
170
  token_amount=str(amount),
171
171
  usd_value=usd_value,
172
172
  data={
173
- "token_id": token_info.get("id"),
173
+ "token_id": token_info.get("token_id"),
174
174
  "amount": str(amount),
175
175
  "usd_value": usd_value,
176
176
  },
@@ -180,7 +180,7 @@ class BalanceAdapter(BaseAdapter):
180
180
  self.logger.warning(
181
181
  "Ledger entry failed",
182
182
  wallet=wallet_address,
183
- token_id=token_info.get("id"),
183
+ token_id=token_info.get("token_id"),
184
184
  amount=amount,
185
185
  error=response,
186
186
  )
@@ -188,13 +188,13 @@ class BalanceAdapter(BaseAdapter):
188
188
  self.logger.warning(
189
189
  f"Ledger entry raised: {exc}",
190
190
  wallet=wallet_address,
191
- token_id=token_info.get("id"),
191
+ token_id=token_info.get("token_id"),
192
192
  )
193
193
 
194
194
  async def _token_amount_usd(
195
195
  self, token_info: dict[str, Any], amount: float
196
196
  ) -> float:
197
- token_id = token_info.get("id")
197
+ token_id = token_info.get("token_id")
198
198
  if not token_id:
199
199
  return 0.0
200
200
  success, price_data = await self.token_adapter.get_token_price(token_id)
@@ -11,7 +11,7 @@ from wayfinder_paths.core.adapters.models import SWAP
11
11
  from wayfinder_paths.core.clients.BRAPClient import BRAPClient, BRAPQuote
12
12
  from wayfinder_paths.core.clients.LedgerClient import TransactionRecord
13
13
  from wayfinder_paths.core.clients.TokenClient import TokenClient
14
- from wayfinder_paths.core.constants import DEFAULT_SLIPPAGE
14
+ from wayfinder_paths.core.constants import DEFAULT_SLIPPAGE, ZERO_ADDRESS
15
15
  from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
16
16
  from wayfinder_paths.core.services.base import Web3Service
17
17
  from wayfinder_paths.core.settings import settings
@@ -108,6 +108,7 @@ class BRAPAdapter(BaseAdapter):
108
108
  amount: str,
109
109
  slippage: float | None = None,
110
110
  wayfinder_fee: float | None = None,
111
+ preferred_providers: list[str] | None = None,
111
112
  ) -> tuple[bool, dict[str, Any] | str]:
112
113
  """
113
114
  Get the best available quote for a swap operation.
@@ -122,6 +123,8 @@ class BRAPAdapter(BaseAdapter):
122
123
  amount: Amount to swap (in smallest units)
123
124
  slippage: Maximum slippage tolerance (optional)
124
125
  wayfinder_fee: Wayfinder fee (optional)
126
+ preferred_providers: List of provider names in order of preference (optional).
127
+ If provided, selects first matching provider instead of "best".
125
128
 
126
129
  Returns:
127
130
  Tuple of (success, data) where data is best quote or error message
@@ -139,8 +142,27 @@ class BRAPAdapter(BaseAdapter):
139
142
  wayfinder_fee=wayfinder_fee,
140
143
  )
141
144
 
142
- # Extract best quote from response
143
- best_quote = data["best_route"]
145
+ quotes_container = data.get("quotes", {})
146
+ # BRAP returns quotes in "all_quotes" not "quotes"
147
+ all_quotes = quotes_container.get("all_quotes", []) or quotes_container.get(
148
+ "quotes", []
149
+ )
150
+
151
+ # If preferred providers specified, select by provider preference
152
+ if preferred_providers and all_quotes:
153
+ selected_quote = self._select_quote_by_provider(
154
+ all_quotes, preferred_providers
155
+ )
156
+ if selected_quote:
157
+ return (True, selected_quote)
158
+ # Fall through to best_quote if no preferred provider found
159
+
160
+ # Extract best quote from response - check nested quotes first, then top level
161
+ best_quote = (
162
+ quotes_container.get("best_quote")
163
+ or data.get("best_quote")
164
+ or data.get("best_route")
165
+ )
144
166
 
145
167
  if not best_quote:
146
168
  return (False, "No quotes available")
@@ -150,6 +172,58 @@ class BRAPAdapter(BaseAdapter):
150
172
  self.logger.error(f"Error getting best quote: {e}")
151
173
  return (False, str(e))
152
174
 
175
+ def _select_quote_by_provider(
176
+ self,
177
+ quotes: list[dict[str, Any]],
178
+ preferred_providers: list[str],
179
+ ) -> dict[str, Any] | None:
180
+ """
181
+ Select a quote from the list based on provider preference order.
182
+
183
+ Args:
184
+ quotes: List of quote objects from BRAP API
185
+ preferred_providers: List of provider names in order of preference (case-insensitive)
186
+
187
+ Returns:
188
+ The first matching quote, or None if no preferred provider found
189
+ """
190
+ # Normalize preferred providers to lowercase for case-insensitive matching
191
+ preferred_lower = [p.lower() for p in preferred_providers]
192
+
193
+ # Build a map of provider name -> quotes
194
+ provider_quotes: dict[str, list[dict[str, Any]]] = {}
195
+ for quote in quotes:
196
+ # Provider name might be in different fields depending on BRAP response structure
197
+ provider = (
198
+ quote.get("provider")
199
+ or quote.get("provider_name")
200
+ or quote.get("source")
201
+ or quote.get("protocol")
202
+ or ""
203
+ ).lower()
204
+ if provider:
205
+ if provider not in provider_quotes:
206
+ provider_quotes[provider] = []
207
+ provider_quotes[provider].append(quote)
208
+
209
+ # Select first matching provider in preference order
210
+ for pref in preferred_lower:
211
+ if pref in provider_quotes:
212
+ # Return the quote with highest output for this provider
213
+ provider_list = provider_quotes[pref]
214
+ best_for_provider = max(
215
+ provider_list, key=lambda q: int(q.get("output_amount", 0) or 0)
216
+ )
217
+ self.logger.info(f"Selected quote from preferred provider: {pref}")
218
+ return best_for_provider
219
+
220
+ # Log available providers for debugging
221
+ available = list(provider_quotes.keys())
222
+ self.logger.warning(
223
+ f"No preferred provider found. Wanted: {preferred_providers}, Available: {available}"
224
+ )
225
+ return None
226
+
153
227
  async def calculate_swap_fees(
154
228
  self,
155
229
  from_token_address: str,
@@ -248,7 +322,7 @@ class BRAPAdapter(BaseAdapter):
248
322
  )
249
323
 
250
324
  quotes = data.get("quotes", {})
251
- all_quotes = quotes.get("quotes", [])
325
+ all_quotes = quotes.get("all_quotes", []) or quotes.get("quotes", [])
252
326
  best_quote = quotes.get("best_quote")
253
327
 
254
328
  if not all_quotes:
@@ -291,9 +365,20 @@ class BRAPAdapter(BaseAdapter):
291
365
  amount: str,
292
366
  slippage: float = DEFAULT_SLIPPAGE,
293
367
  strategy_name: str | None = None,
368
+ preferred_providers: list[str] | None = None,
294
369
  ) -> tuple[bool, Any]:
295
370
  """
296
371
  Execute a swap by looking up token metadata via token IDs.
372
+
373
+ Args:
374
+ from_token_id: Source token ID
375
+ to_token_id: Destination token ID
376
+ from_address: Wallet address
377
+ amount: Amount to swap (in smallest units)
378
+ slippage: Maximum slippage tolerance
379
+ strategy_name: Strategy name for ledger recording
380
+ preferred_providers: List of provider names in order of preference (e.g., ["enso", "aerodrome"]).
381
+ If provided, selects first matching provider instead of "best".
297
382
  """
298
383
  from_token = await self.token_client.get_token_details(from_token_id)
299
384
  if not from_token:
@@ -311,6 +396,7 @@ class BRAPAdapter(BaseAdapter):
311
396
  to_address=from_address,
312
397
  amount=amount,
313
398
  slippage=slippage,
399
+ preferred_providers=preferred_providers,
314
400
  )
315
401
  if not success:
316
402
  return (False, best_quote)
@@ -349,7 +435,14 @@ class BRAPAdapter(BaseAdapter):
349
435
  or quote.get("inputAmount")
350
436
  or transaction.get("value")
351
437
  )
352
- if from_token.get("address") and spender and approve_amount:
438
+ token_address = from_token.get("address")
439
+ token_address_l = str(token_address or "").lower()
440
+ is_native = token_address_l in {
441
+ "",
442
+ ZERO_ADDRESS.lower(),
443
+ "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
444
+ }
445
+ if token_address and (not is_native) and spender and approve_amount:
353
446
  approve_success, approve_response = await self._handle_token_approval(
354
447
  chain=chain,
355
448
  token_address=from_token.get("address"),
@@ -363,17 +456,36 @@ class BRAPAdapter(BaseAdapter):
363
456
  broadcast_success, broadcast_response = await self._broadcast_transaction(
364
457
  transaction
365
458
  )
459
+ self.logger.info(
460
+ f"Swap broadcast result: success={broadcast_success}, "
461
+ f"response={broadcast_response}"
462
+ )
366
463
  if not broadcast_success:
367
464
  return (False, broadcast_response)
368
465
 
369
- ledger_record = await self._record_swap_operation(
370
- from_token=from_token,
371
- to_token=to_token,
372
- wallet_address=from_address,
373
- quote=quote,
374
- broadcast_response=broadcast_response,
375
- strategy_name=strategy_name,
376
- )
466
+ # Record the swap operation in ledger - but don't let ledger errors fail the swap
467
+ # since the on-chain transaction already succeeded
468
+ try:
469
+ ledger_record = await self._record_swap_operation(
470
+ from_token=from_token,
471
+ to_token=to_token,
472
+ wallet_address=from_address,
473
+ quote=quote,
474
+ broadcast_response=broadcast_response,
475
+ strategy_name=strategy_name,
476
+ )
477
+ except Exception as e:
478
+ self.logger.warning(
479
+ f"Ledger recording failed (swap succeeded on-chain): {e}"
480
+ )
481
+ # Return the quote with output amount so caller can proceed
482
+ ledger_record = {
483
+ "to_amount": quote.get("output_amount"),
484
+ "from_amount": quote.get("input_amount"),
485
+ "tx_hash": broadcast_response.get("tx_hash")
486
+ if isinstance(broadcast_response, dict)
487
+ else None,
488
+ }
377
489
  return (True, ledger_record)
378
490
 
379
491
  async def get_bridge_quote(
@@ -581,10 +693,10 @@ class BRAPAdapter(BaseAdapter):
581
693
  )
582
694
  if not build_success:
583
695
  return False, approve_tx
584
- return await self._broadcast_transaction(approve_tx)
696
+ return await self._broadcast_transaction(approve_tx, confirmations=2)
585
697
 
586
698
  async def _broadcast_transaction(
587
- self, transaction: dict[str, Any]
699
+ self, transaction: dict[str, Any], confirmations: int = 0
588
700
  ) -> tuple[bool, Any]:
589
701
  if getattr(settings, "DRY_RUN", False):
590
702
  return True, {"dry_run": True, "transaction": transaction}
@@ -592,6 +704,7 @@ class BRAPAdapter(BaseAdapter):
592
704
  transaction,
593
705
  wait_for_receipt=True,
594
706
  timeout=DEFAULT_TRANSACTION_TIMEOUT,
707
+ confirmations=confirmations,
595
708
  )
596
709
 
597
710
  async def _record_swap_operation(
@@ -618,16 +731,19 @@ class BRAPAdapter(BaseAdapter):
618
731
  response = broadcast_response if isinstance(broadcast_response, dict) else {}
619
732
  operation_data = SWAP(
620
733
  adapter=self.adapter_type,
621
- from_token_id=from_token.get("id"),
622
- to_token_id=to_token.get("id"),
623
- from_amount=quote.get("input_amount"),
624
- to_amount=quote.get("output_amount"),
734
+ from_token_id=str(from_token.get("id")),
735
+ to_token_id=str(to_token.get("id")),
736
+ from_amount=str(quote.get("input_amount")),
737
+ to_amount=str(quote.get("output_amount")),
625
738
  from_amount_usd=from_amount_usd or 0,
626
739
  to_amount_usd=to_amount_usd or 0,
627
- transaction_hash=response.get("transaction_hash"),
628
- transaction_chain_id=from_token.get("chain_id"),
740
+ transaction_hash=response.get("tx_hash")
741
+ or response.get("transaction_hash"),
742
+ transaction_chain_id=from_token.get("chain_id")
743
+ or (from_token.get("chain") or {}).get("id"),
629
744
  transaction_status=response.get("transaction_status"),
630
- transaction_receipt=response.get("transaction_receipt"),
745
+ # Don't pass raw receipt - it contains HexBytes that can't be JSON serialized
746
+ transaction_receipt=None,
631
747
  )
632
748
 
633
749
  try:
@@ -653,7 +769,7 @@ class BRAPAdapter(BaseAdapter):
653
769
  if raw_amount is None:
654
770
  return None
655
771
  success, price_data = await self.token_adapter.get_token_price(
656
- token_info.get("id")
772
+ token_info.get("token_id")
657
773
  )
658
774
  if not success or not price_data:
659
775
  return None
@@ -0,0 +1,174 @@
1
+ # Moonwell Adapter
2
+
3
+ Adapter for interacting with the [Moonwell](https://moonwell.fi/) lending protocol on Base chain.
4
+
5
+ - Entrypoint: `adapters.moonwell_adapter.adapter.MoonwellAdapter`
6
+ - Tests: `test_adapter.py`
7
+
8
+ ## Capabilities
9
+
10
+ The adapter provides the following capabilities:
11
+ - Lending: Supply and withdraw tokens
12
+ - Borrowing: Borrow and repay tokens
13
+ - Collateral management: Enable/disable markets as collateral
14
+ - Rewards: Claim WELL token rewards
15
+ - Position & market queries: Get balances, APYs, and liquidity info
16
+
17
+ ## Overview
18
+
19
+ The MoonwellAdapter provides functionality for:
20
+ - **Lending**: Supply tokens to earn yield
21
+ - **Borrowing**: Borrow against collateral
22
+ - **Collateral Management**: Enable/disable markets as collateral
23
+ - **Rewards**: Claim WELL token rewards
24
+ - **Position Queries**: Get balances, APYs, and liquidity info
25
+
26
+ ## Supported Markets (Base Chain)
27
+
28
+ | Token | mToken Address | Underlying Address |
29
+ |-------|----------------|-------------------|
30
+ | USDC | `0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
31
+ | WETH | `0x628ff693426583D9a7FB391E54366292F509D457` | `0x4200000000000000000000000000000000000006` |
32
+ | wstETH | `0x627Fe393Bc6EdDA28e99AE648fD6fF362514304b` | `0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452` |
33
+
34
+ ## Protocol Addresses (Base Chain)
35
+
36
+ - **Comptroller**: `0xfbb21d0380bee3312b33c4353c8936a0f13ef26c`
37
+ - **Reward Distributor**: `0xe9005b078701e2a0948d2eac43010d35870ad9d2`
38
+ - **WELL Token**: `0xA88594D404727625A9437C3f886C7643872296AE`
39
+
40
+ ## Construction
41
+
42
+ ```python
43
+ from wayfinder_paths.core.services.web3_service import DefaultWeb3Service
44
+ from wayfinder_paths.adapters.moonwell_adapter import MoonwellAdapter
45
+
46
+ config = {
47
+ "strategy_wallet": {"address": "0x...your_wallet..."},
48
+ "moonwell_adapter": {
49
+ "chain_id": 8453, # Base chain (default)
50
+ }
51
+ }
52
+ web3_service = DefaultWeb3Service(config)
53
+ adapter = MoonwellAdapter(config=config, web3_service=web3_service)
54
+ ```
55
+
56
+ `web3_service` is required so the adapter can share the same wallet provider as the rest of the strategy.
57
+
58
+ ## Usage
59
+
60
+ ```python
61
+ # Supply tokens
62
+ await adapter.lend(
63
+ mtoken="0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22", # mUSDC
64
+ underlying_token="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC
65
+ amount=1000 * 10**6, # 1000 USDC
66
+ )
67
+
68
+ # Enable as collateral
69
+ await adapter.set_collateral(
70
+ mtoken="0x627Fe393Bc6EdDA28e99AE648fD6fF362514304b", # mwstETH
71
+ )
72
+
73
+ # Borrow
74
+ await adapter.borrow(
75
+ mtoken="0x628ff693426583D9a7FB391E54366292F509D457", # mWETH
76
+ amount=10**17, # 0.1 WETH
77
+ )
78
+
79
+ # Get position info
80
+ success, position = await adapter.get_pos(
81
+ mtoken="0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22",
82
+ )
83
+ # Returns: {mtoken_balance, underlying_balance, borrow_balance, exchange_rate, balances}
84
+
85
+ # Get collateral factor
86
+ success, cf = await adapter.get_collateral_factor(
87
+ mtoken="0x627Fe393Bc6EdDA28e99AE648fD6fF362514304b",
88
+ )
89
+ # Returns: 0.75 (75% LTV)
90
+
91
+ # Get APY
92
+ success, apy = await adapter.get_apy(
93
+ mtoken="0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22",
94
+ apy_type="supply", # or "borrow"
95
+ )
96
+
97
+ # Claim rewards
98
+ await adapter.claim_rewards()
99
+ ```
100
+
101
+ ## API Reference
102
+
103
+ ### Lending Operations
104
+
105
+ | Method | Description |
106
+ |--------|-------------|
107
+ | `lend(mtoken, underlying_token, amount)` | Supply tokens to earn yield |
108
+ | `unlend(mtoken, amount)` | Withdraw by redeeming mTokens |
109
+
110
+ ### Borrowing Operations
111
+
112
+ | Method | Description |
113
+ |--------|-------------|
114
+ | `borrow(mtoken, amount)` | Borrow underlying tokens |
115
+ | `repay(mtoken, underlying_token, amount)` | Repay borrowed tokens |
116
+
117
+ ### Collateral Management
118
+
119
+ | Method | Description |
120
+ |--------|-------------|
121
+ | `set_collateral(mtoken)` | Enable market as collateral (enterMarkets) |
122
+ | `remove_collateral(mtoken)` | Disable market as collateral (exitMarket) |
123
+
124
+ ### Position & Market Data
125
+
126
+ | Method | Description |
127
+ |--------|-------------|
128
+ | `get_pos(mtoken, account)` | Get position data (balances, debt, rewards) |
129
+ | `get_collateral_factor(mtoken)` | Get collateral factor (LTV) |
130
+ | `get_apy(mtoken, apy_type)` | Get supply or borrow APY |
131
+ | `get_borrowable_amount(account)` | Get max borrowable in USD |
132
+ | `max_withdrawable_mtoken(mtoken, account)` | Calculate safe withdrawal amount via binary search |
133
+
134
+ ### Rewards
135
+
136
+ | Method | Description |
137
+ |--------|-------------|
138
+ | `claim_rewards(min_rewards_usd)` | Claim WELL rewards (skips if below threshold) |
139
+
140
+ ### Utilities
141
+
142
+ | Method | Description |
143
+ |--------|-------------|
144
+ | `wrap_eth(amount)` | Wrap ETH to WETH |
145
+
146
+ All methods return `(success: bool, payload: Any)` tuples. On failure the payload is an error string.
147
+
148
+ ## Testing
149
+
150
+ ```bash
151
+ poetry run pytest wayfinder_paths/adapters/moonwell_adapter/ -v
152
+ ```
153
+
154
+ ## Configuration
155
+
156
+ The adapter can be configured via the `moonwell_adapter` config key:
157
+
158
+ ```python
159
+ config = {
160
+ "strategy_wallet": {"address": "0x..."},
161
+ "moonwell_adapter": {
162
+ "chain_id": 8453, # Chain ID (default: Base)
163
+ "comptroller": "0x...", # Override comptroller address
164
+ "reward_distributor": "0x...", # Override reward distributor
165
+ "m_usdc": "0x...", # Override mUSDC address
166
+ "m_weth": "0x...", # Override mWETH address
167
+ "m_wsteth": "0x...", # Override mwstETH address
168
+ }
169
+ }
170
+ ```
171
+
172
+ ## Error handling
173
+
174
+ Any exception raised by the underlying web3 calls is caught and returned as a `(False, "message")` tuple. The inherited `health_check()` method reports adapter status, making it safe to call from `Strategy.health_check`.
@@ -0,0 +1,7 @@
1
+ """
2
+ Moonwell Adapter
3
+ """
4
+
5
+ from wayfinder_paths.adapters.moonwell_adapter.adapter import MoonwellAdapter
6
+
7
+ __all__ = ["MoonwellAdapter"]