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
@@ -1,11 +1,3 @@
1
- """
2
- Moonwell wstETH Loop Strategy
3
-
4
- A leveraged liquid-staking carry on Base that loops USDC → borrow WETH → swap to wstETH → lend wstETH.
5
- The loop repeats while keeping debt as a fraction F of borrow capacity, chosen conservatively
6
- so the position remains safe under a stETH/ETH depeg.
7
- """
8
-
9
1
  import asyncio
10
2
  import time
11
3
  from collections.abc import Awaitable, Callable
@@ -67,16 +59,10 @@ COLLATERAL_SAFETY_FACTOR = 0.98
67
59
 
68
60
 
69
61
  class SwapOutcomeUnknownError(RuntimeError):
70
- """Raised when a swap transaction's outcome is unknown (e.g., receipt timeout).
71
-
72
- In this case we must not retry (risk duplicate fills) and should halt the strategy
73
- so the caller can inspect on-chain state.
74
- """
62
+ "Raised when the outcome of a swap operation is unknown."
75
63
 
76
64
 
77
65
  class MoonwellWstethLoopStrategy(Strategy):
78
- """Leveraged wstETH yield strategy using Moonwell lending protocol on Base."""
79
-
80
66
  name = "Moonwell wstETH Loop Strategy"
81
67
  description = "Leveraged wstETH yield strategy using Moonwell lending protocol."
82
68
  summary = "Loop wstETH on Moonwell for amplified staking yields."
@@ -88,15 +74,15 @@ class MoonwellWstethLoopStrategy(Strategy):
88
74
  # When wrapping ETH to WETH for swaps/repayment, avoid draining gas below this floor.
89
75
  # We can dip below MIN_GAS temporarily, but should not wipe the wallet.
90
76
  WRAP_GAS_RESERVE = 0.0014
91
- MIN_USDC_DEPOSIT = 10.0 # Minimum USDC deposit required as initial collateral
92
- MIN_REWARD_CLAIM_USD = 0.30 # Only claim WELL rewards if >= this value
93
- MAX_DEPEG = 0.01 # Maximum allowed stETH depeg threshold (1%)
77
+ MIN_USDC_DEPOSIT = 10.0
78
+ MIN_REWARD_CLAIM_USD = 0.30
79
+ MAX_DEPEG = 0.01
94
80
  MAX_HEALTH_FACTOR = 1.5
95
81
  MIN_HEALTH_FACTOR = 1.2
96
82
  # Operational target HF (keep some buffer above MIN_HEALTH_FACTOR).
97
83
  TARGET_HEALTH_FACTOR = 1.25
98
84
  # Lever up if HF is more than this amount above TARGET_HEALTH_FACTOR
99
- HF_LEVER_UP_BUFFER = 0.05 # Lever up if HF > TARGET + buffer
85
+ HF_LEVER_UP_BUFFER = 0.05
100
86
  # Deleverage if HF drops below (TARGET - buffer).
101
87
  HF_DELEVERAGE_BUFFER = 0.05
102
88
  # Deleverage if leverage multiplier exceeds target by this much.
@@ -111,20 +97,20 @@ class MoonwellWstethLoopStrategy(Strategy):
111
97
  # Full-exit (withdraw) dust behavior: do fewer, larger actions so we can repay_full in one go.
112
98
  FULL_EXIT_BUFFER_MULT = 1.05
113
99
  FULL_EXIT_MIN_BATCH_USD = 10.0
114
- _MAX_LOOP_LIMIT = 30 # Prevents infinite loops
100
+ _MAX_LOOP_LIMIT = 30
115
101
 
116
102
  # Parameters
117
- leverage_limit = 10 # Limit on leverage multiplier
103
+ leverage_limit = 10
118
104
  min_withdraw_usd = 2
119
105
  sweep_min_usd = 0.20
120
- max_swap_retries = 3 # Maximum number of swap retry attempts
121
- swap_slippage_tolerance = 0.005 # Base slippage of 50 bps
122
- MAX_SLIPPAGE_TOLERANCE = 0.03 # 3% absolute maximum slippage to prevent MEV attacks
123
- PRICE_STALENESS_THRESHOLD = 300 # 5 minutes - max age for cached prices
106
+ max_swap_retries = 3
107
+ swap_slippage_tolerance = 0.005
108
+ MAX_SLIPPAGE_TOLERANCE = 0.03
109
+ PRICE_STALENESS_THRESHOLD = 300
124
110
 
125
111
  # 50 basis points (0.0050) - minimum leverage gain per loop iteration to continue
126
112
  # If marginal gain drops below this, stop looping as gas costs outweigh benefit
127
- _MIN_LEVERAGE_GAIN_BPS = 50e-4 # 50 bps = 0.50%
113
+ _MIN_LEVERAGE_GAIN_BPS = 50e-4
128
114
 
129
115
  INFO = StratDescriptor(
130
116
  description="Leveraged wstETH carry: loops USDC → borrow WETH → swap wstETH → lend. "
@@ -188,7 +174,6 @@ class MoonwellWstethLoopStrategy(Strategy):
188
174
  *,
189
175
  main_wallet: dict | None = None,
190
176
  strategy_wallet: dict | None = None,
191
- simulation: bool = False,
192
177
  api_key: str | None = None,
193
178
  main_wallet_signing_callback: Callable[[dict], Awaitable[str]] | None = None,
194
179
  strategy_wallet_signing_callback: Callable[[dict], Awaitable[str]]
@@ -206,7 +191,6 @@ class MoonwellWstethLoopStrategy(Strategy):
206
191
  merged_config["strategy_wallet"] = strategy_wallet
207
192
 
208
193
  self.config = merged_config
209
- self.simulation = simulation
210
194
 
211
195
  # Adapter references
212
196
  self.balance_adapter: BalanceAdapter | None = None
@@ -235,10 +219,8 @@ class MoonwellWstethLoopStrategy(Strategy):
235
219
  "strategy": self.config,
236
220
  }
237
221
 
238
- # Initialize adapters
239
222
  balance = BalanceAdapter(
240
223
  adapter_config,
241
- simulation=self.simulation,
242
224
  main_wallet_signing_callback=self.main_wallet_signing_callback,
243
225
  strategy_wallet_signing_callback=self.strategy_wallet_signing_callback,
244
226
  )
@@ -246,12 +228,10 @@ class MoonwellWstethLoopStrategy(Strategy):
246
228
  ledger_adapter = LedgerAdapter()
247
229
  brap_adapter = BRAPAdapter(
248
230
  adapter_config,
249
- simulation=self.simulation,
250
231
  strategy_wallet_signing_callback=self.strategy_wallet_signing_callback,
251
232
  )
252
233
  moonwell_adapter = MoonwellAdapter(
253
234
  adapter_config,
254
- simulation=self.simulation,
255
235
  strategy_wallet_signing_callback=self.strategy_wallet_signing_callback,
256
236
  )
257
237
 
@@ -276,16 +256,6 @@ class MoonwellWstethLoopStrategy(Strategy):
276
256
  raise
277
257
 
278
258
  def _max_safe_F(self, cf_w: float) -> float:
279
- """Max safe debt fraction vs borrow capacity under a depeg.
280
-
281
- Let a = 1 - MAX_DEPEG. If the position is sized at par (a=1) with debt
282
- fraction F = Debt / BorrowCapacity, then after an instantaneous depeg to a
283
- the borrow capacity shrinks by cf_w * (1-a) * Debt. Requiring Debt to
284
- remain <= new capacity yields:
285
- F_max = 1 / (1 + cf_w * (1 - a))
286
-
287
- Returns F_max clipped to [0, 1].
288
- """
289
259
  a = 1 - self.MAX_DEPEG
290
260
  if not (0 < a):
291
261
  return 0.0
@@ -298,20 +268,14 @@ class MoonwellWstethLoopStrategy(Strategy):
298
268
  return max(0.0, min(1.0, min(f_bound, f_feasible, 1.0)))
299
269
 
300
270
  def _get_strategy_wallet_address(self) -> str:
301
- """Get the strategy wallet address."""
302
271
  wallet = self.config.get("strategy_wallet", {})
303
272
  return wallet.get("address", "")
304
273
 
305
274
  def _get_main_wallet_address(self) -> str:
306
- """Get the main wallet address."""
307
275
  wallet = self.config.get("main_wallet", {})
308
276
  return wallet.get("address", "")
309
277
 
310
278
  def _gas_keep_wei(self) -> int:
311
- """
312
- Hard ETH reserve in the strategy wallet.
313
- Never spend below this when wrapping/swapping ETH.
314
- """
315
279
  # Extra buffer for a couple txs (wrap + swap/repay).
316
280
  tx_buffer = int(0.0003 * 10**18)
317
281
  return max(
@@ -327,9 +291,9 @@ class MoonwellWstethLoopStrategy(Strategy):
327
291
  wallet_wsteth: int
328
292
  wallet_usdc: int
329
293
 
330
- usdc_supplied: int # underlying raw
331
- wsteth_supplied: int # underlying raw
332
- weth_debt: int # borrow raw
294
+ usdc_supplied: int
295
+ wsteth_supplied: int
296
+ weth_debt: int
333
297
 
334
298
  # prices/decimals
335
299
  eth_price: float
@@ -363,10 +327,6 @@ class MoonwellWstethLoopStrategy(Strategy):
363
327
  async def _accounting_snapshot(
364
328
  self, collateral_factors: tuple[float, float] | None = None
365
329
  ) -> tuple["MoonwellWstethLoopStrategy.AccountingSnapshot", tuple[float, float]]:
366
- """
367
- One snapshot for decisions.
368
- Keep it deterministic and re-usable across rebalance/unwind paths.
369
- """
370
330
  if collateral_factors is None:
371
331
  collateral_factors = await self._get_collateral_factors()
372
332
  cf_u, cf_w = collateral_factors
@@ -484,7 +444,6 @@ class MoonwellWstethLoopStrategy(Strategy):
484
444
  async def _ensure_markets_for_state(
485
445
  self, snap: "MoonwellWstethLoopStrategy.AccountingSnapshot"
486
446
  ) -> tuple[bool, str]:
487
- """Idempotently ensure Moonwell markets are entered based on current positions."""
488
447
  errors: list[str] = []
489
448
 
490
449
  if snap.usdc_supplied > 0:
@@ -509,9 +468,6 @@ class MoonwellWstethLoopStrategy(Strategy):
509
468
  def _debt_gap_report(
510
469
  self, snap: "MoonwellWstethLoopStrategy.AccountingSnapshot"
511
470
  ) -> dict[str, float]:
512
- """
513
- Mode-agnostic accounting: how much USDC needs to be raised to repay debt.
514
- """
515
471
  eth_usable_usd = (
516
472
  (snap.eth_usable_wei / (10**snap.eth_dec)) * snap.eth_price
517
473
  if snap.eth_price
@@ -544,10 +500,6 @@ class MoonwellWstethLoopStrategy(Strategy):
544
500
  def _delta_mismatch_usd(
545
501
  self, snap: "MoonwellWstethLoopStrategy.AccountingSnapshot"
546
502
  ) -> float:
547
- """
548
- Positive => net SHORT (debt > wstETH collateral) in USD terms.
549
- Negative => net LONG (wstETH collateral > debt).
550
- """
551
503
  wsteth_coll_usd = float(snap.totals_usd.get(f"Base_{M_WSTETH}", 0.0))
552
504
  return float(snap.debt_usd) - wsteth_coll_usd
553
505
 
@@ -560,17 +512,13 @@ class MoonwellWstethLoopStrategy(Strategy):
560
512
  hf_floor: float,
561
513
  precision_usd: float = 0.50,
562
514
  ) -> float:
563
- """
564
- Maximum USD amount of a given collateral you can remove while keeping HF >= hf_floor,
565
- considering only the withdrawal step (before the subsequent swap+repay improves HF).
566
- """
567
515
  current_val = float(totals_usd.get(withdraw_key, 0.0))
568
516
  if current_val <= 0:
569
517
  return 0.0
570
518
 
571
519
  debt_usd = abs(float(totals_usd.get(f"Base_{WETH}", 0.0)))
572
520
  if debt_usd <= 0:
573
- return current_val # if no debt, you can withdraw all
521
+ return current_val
574
522
 
575
523
  cf_u, cf_w = collateral_factors
576
524
  usdc_key = f"Base_{M_USDC}"
@@ -608,15 +556,6 @@ class MoonwellWstethLoopStrategy(Strategy):
608
556
  mode: str = "operate",
609
557
  prior_error: Exception | None = None,
610
558
  ) -> tuple[bool, str]:
611
- """
612
- Always-run finalizer. Attempts to restore:
613
- - HF >= MIN_HEALTH_FACTOR
614
- - Not net-short ETH (wstETH collateral >= WETH debt, within tolerance)
615
-
616
- mode:
617
- - "operate": keep strategy running normally after guard
618
- - "exit": conservative (don't buy/lend; only reconcile wallet collateral + delever)
619
- """
620
559
  logger.info("-" * 40)
621
560
  logger.info(f"POST-RUN GUARD: mode={mode}")
622
561
  if prior_error:
@@ -695,7 +634,7 @@ class MoonwellWstethLoopStrategy(Strategy):
695
634
  tol = max(float(self.DELTA_TOL_USD), float(self.min_withdraw_usd))
696
635
 
697
636
  for _ in range(int(self.POST_RUN_MAX_PASSES)):
698
- mismatch = self._delta_mismatch_usd(snap) # + => net short
637
+ mismatch = self._delta_mismatch_usd(snap)
699
638
  short_usd = max(0.0, float(mismatch))
700
639
  long_usd = max(0.0, float(-mismatch))
701
640
 
@@ -820,10 +759,6 @@ class MoonwellWstethLoopStrategy(Strategy):
820
759
  collateral_factors: tuple[float, float],
821
760
  target_hf: float | None = None,
822
761
  ) -> float:
823
- """Compute the target leverage multiplier implied by HF + collateral factors.
824
-
825
- In this strategy, leverage is defined as (wstETH_supply_usd / usdc_supply_usd) + 1.
826
- """
827
762
  cf_u, cf_w = collateral_factors
828
763
  hf = (
829
764
  float(target_hf)
@@ -849,7 +784,6 @@ class MoonwellWstethLoopStrategy(Strategy):
849
784
  max_batch_usd: float = 4000.0,
850
785
  max_steps: int = 10,
851
786
  ) -> tuple[bool, str]:
852
- """Reduce leverage by withdrawing wstETH collateral and repaying WETH debt."""
853
787
  band = (
854
788
  float(max_over_leverage)
855
789
  if max_over_leverage is not None
@@ -989,12 +923,6 @@ class MoonwellWstethLoopStrategy(Strategy):
989
923
  collateral_factors: tuple[float, float],
990
924
  max_batch_usd: float = 5000.0,
991
925
  ) -> tuple[bool, str]:
992
- """
993
- Operate-mode reconciliation:
994
- - Always lend loose wallet wstETH (no swaps).
995
- - If there is WETH debt and wstETH collateral is short, use wallet WETH then wallet ETH
996
- (above gas reserve) to buy wstETH and lend it.
997
- """
998
926
  snap, _ = await self._accounting_snapshot(collateral_factors=collateral_factors)
999
927
  addr = self._get_strategy_wallet_address()
1000
928
 
@@ -1129,7 +1057,6 @@ class MoonwellWstethLoopStrategy(Strategy):
1129
1057
  hf_floor: float,
1130
1058
  max_batch_usd: float = 8000.0,
1131
1059
  ) -> tuple[bool, str]:
1132
- """Reduce net long wstETH exposure by converting mwstETH collateral into mUSDC."""
1133
1060
  if unwind_usd <= 0:
1134
1061
  return (True, "No wstETH long to unwind")
1135
1062
 
@@ -1245,17 +1172,10 @@ class MoonwellWstethLoopStrategy(Strategy):
1245
1172
  target_debt_usd: float,
1246
1173
  target_hf: float | None = None,
1247
1174
  collateral_factors: tuple[float, float],
1248
- mode: str, # "operate" or "exit"
1175
+ mode: str,
1249
1176
  max_batch_usd: float = 4000.0,
1250
1177
  max_steps: int = 20,
1251
1178
  ) -> tuple[bool, str]:
1252
- """
1253
- Reduce debt until the specified target is met.
1254
- Uses wallet assets first; if insufficient, redeems collateral in HF-safe batches.
1255
-
1256
- mode="operate": be conservative about collateral withdrawals (HF floor ~ MIN_HEALTH_FACTOR)
1257
- mode="exit": allow lower HF floor during the withdraw step (still > 1.05), since repay comes right after.
1258
- """
1259
1179
  logger.info(
1260
1180
  f"SETTLE DEBT: target_debt=${target_debt_usd:.2f}, target_hf={target_hf}, mode={mode}"
1261
1181
  )
@@ -1431,7 +1351,6 @@ class MoonwellWstethLoopStrategy(Strategy):
1431
1351
 
1432
1352
  # Choose the collateral source that can actually be withdrawn (HF-safe + cash bound)
1433
1353
  # in the largest size. This avoids getting stuck redeeming ever-smaller amounts when
1434
- # a market (often wstETH) is cash-limited.
1435
1354
  snap, _ = await self._accounting_snapshot(
1436
1355
  collateral_factors=collateral_factors
1437
1356
  )
@@ -1632,7 +1551,6 @@ class MoonwellWstethLoopStrategy(Strategy):
1632
1551
  return (False, f"Exceeded max_steps={max_steps} while settling debt")
1633
1552
 
1634
1553
  async def setup(self):
1635
- """Initialize token info and validate configuration."""
1636
1554
  if self.token_adapter is None:
1637
1555
  raise RuntimeError("Token adapter not initialized.")
1638
1556
 
@@ -1646,7 +1564,6 @@ class MoonwellWstethLoopStrategy(Strategy):
1646
1564
  logger.warning(f"Failed to fetch token info for {token_id}: {e}")
1647
1565
 
1648
1566
  async def _get_token_info(self, token_id: str) -> dict:
1649
- """Get token info from cache or fetch it."""
1650
1567
  if token_id in self._token_info_cache:
1651
1568
  return self._token_info_cache[token_id]
1652
1569
 
@@ -1657,10 +1574,8 @@ class MoonwellWstethLoopStrategy(Strategy):
1657
1574
  return {}
1658
1575
 
1659
1576
  async def _get_token_price(self, token_id: str) -> float:
1660
- """Get token price with staleness check."""
1661
1577
  now = time.time()
1662
1578
 
1663
- # Check cache with staleness
1664
1579
  if token_id in self._token_price_cache:
1665
1580
  timestamp = self._token_price_timestamps.get(token_id, 0)
1666
1581
  if now - timestamp < self.PRICE_STALENESS_THRESHOLD:
@@ -1682,12 +1597,10 @@ class MoonwellWstethLoopStrategy(Strategy):
1682
1597
  return 0.0
1683
1598
 
1684
1599
  def _clear_price_cache(self):
1685
- """Clear the price cache to force refresh."""
1686
1600
  self._token_price_cache.clear()
1687
1601
  self._token_price_timestamps.clear()
1688
1602
 
1689
1603
  async def _get_token_data(self, token_id: str) -> tuple[float, int]:
1690
- """Get price and decimals for a token in one call."""
1691
1604
  price = await self._get_token_price(token_id)
1692
1605
  info = await self._get_token_info(token_id)
1693
1606
  return price, info.get("decimals", 18)
@@ -1701,13 +1614,11 @@ class MoonwellWstethLoopStrategy(Strategy):
1701
1614
  base_slippage: float | None = None,
1702
1615
  preferred_providers: list[str] | None = None,
1703
1616
  ) -> dict | None:
1704
- """Swap with retries, progressive slippage, and exponential backoff."""
1705
1617
  if max_retries is None:
1706
1618
  max_retries = self.max_swap_retries
1707
1619
  if base_slippage is None:
1708
1620
  base_slippage = self.swap_slippage_tolerance
1709
1621
 
1710
- # Get token info for logging
1711
1622
  from_decimals = (
1712
1623
  18 if from_token_id in (ETH_TOKEN_ID, WETH_TOKEN_ID, WSTETH_TOKEN_ID) else 6
1713
1624
  )
@@ -1842,7 +1753,6 @@ class MoonwellWstethLoopStrategy(Strategy):
1842
1753
  return None
1843
1754
 
1844
1755
  def _parse_balance(self, raw: Any) -> int:
1845
- """Parse balance value to integer, handling various formats."""
1846
1756
  if raw is None:
1847
1757
  return 0
1848
1758
  if isinstance(raw, dict):
@@ -1873,29 +1783,9 @@ class MoonwellWstethLoopStrategy(Strategy):
1873
1783
  wallet_address: str,
1874
1784
  block_identifier: int | str | None = None,
1875
1785
  ) -> int:
1876
- """Read a wallet balance directly from chain (falls back to adapter in simulation).
1877
-
1878
- Args:
1879
- token_id: Token identifier (e.g., WETH_TOKEN_ID)
1880
- wallet_address: Address to query balance for
1881
- block_identifier: Block to query at. Can be:
1882
- - int: specific block number (for pinning to tx block)
1883
- - "safe": OP Stack safe block (data posted to L1)
1884
- - None/"latest": current head (default)
1885
- """
1886
1786
  if not token_id or not wallet_address:
1887
1787
  return 0
1888
1788
 
1889
- # Tests/simulations patch adapters; avoid RPC calls there.
1890
- if self.simulation:
1891
- if self.balance_adapter is None:
1892
- return 0
1893
- success, raw = await self.balance_adapter.get_balance(
1894
- token_id=token_id,
1895
- wallet_address=wallet_address,
1896
- )
1897
- return self._parse_balance(raw) if success else 0
1898
-
1899
1789
  token_address = self._token_address_for_id(token_id)
1900
1790
  if token_id != ETH_TOKEN_ID and not token_address:
1901
1791
  # Try to resolve address via token metadata (not a balance read).
@@ -1959,11 +1849,6 @@ class MoonwellWstethLoopStrategy(Strategy):
1959
1849
  return 0
1960
1850
 
1961
1851
  def _normalize_usd_value(self, raw: Any) -> float:
1962
- """Normalize a USD value that may be 18-decimal scaled (Compound/Moonwell style).
1963
-
1964
- Moonwell's Comptroller `getAccountLiquidity` returns USD with 18 decimals as an int.
1965
- Some mocks may return a float already in USD.
1966
- """
1967
1852
  if raw is None:
1968
1853
  return 0.0
1969
1854
 
@@ -1983,7 +1868,6 @@ class MoonwellWstethLoopStrategy(Strategy):
1983
1868
  def _mtoken_amount_for_underlying(
1984
1869
  self, withdraw_info: dict[str, Any], underlying_raw: int
1985
1870
  ) -> int:
1986
- """Convert desired underlying (raw) to mToken amount (raw), capped by max withdrawable."""
1987
1871
  if underlying_raw <= 0:
1988
1872
  return 0
1989
1873
 
@@ -2011,7 +1895,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2011
1895
  return min(int(ctokens_needed), max_ctokens)
2012
1896
 
2013
1897
  def _pinned_block(self, tx_result: Any) -> int | None:
2014
- """Extract a deterministic pinned block number from an adapter tx result (best-effort)."""
2015
1898
  if not isinstance(tx_result, dict):
2016
1899
  return None
2017
1900
 
@@ -2035,7 +1918,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2035
1918
  min_expected: int = 1,
2036
1919
  attempts: int = 5,
2037
1920
  ) -> int:
2038
- """Read a balance at a pinned block, retrying briefly to avoid RPC indexing lag."""
2039
1921
  bal = 0
2040
1922
  for i in range(int(attempts)):
2041
1923
  bal = await self._get_balance_raw(
@@ -2049,19 +1931,16 @@ class MoonwellWstethLoopStrategy(Strategy):
2049
1931
  return int(bal)
2050
1932
 
2051
1933
  async def _get_gas_balance(self) -> int:
2052
- """Get ETH balance in strategy wallet (raw wei)."""
2053
1934
  return await self._get_balance_raw(
2054
1935
  token_id=ETH_TOKEN_ID, wallet_address=self._get_strategy_wallet_address()
2055
1936
  )
2056
1937
 
2057
1938
  async def _get_usdc_balance(self) -> int:
2058
- """Get USDC balance in strategy wallet (raw wei)."""
2059
1939
  return await self._get_balance_raw(
2060
1940
  token_id=USDC_TOKEN_ID, wallet_address=self._get_strategy_wallet_address()
2061
1941
  )
2062
1942
 
2063
1943
  async def _validate_gas_balance(self) -> tuple[bool, str]:
2064
- """Validate gas balance meets minimum requirements."""
2065
1944
  gas_balance = await self._get_gas_balance()
2066
1945
  main_gas = await self._get_balance_raw(
2067
1946
  token_id=ETH_TOKEN_ID, wallet_address=self._get_main_wallet_address()
@@ -2080,7 +1959,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2080
1959
  async def _validate_usdc_deposit(
2081
1960
  self, usdc_amount: float
2082
1961
  ) -> tuple[bool, str, float]:
2083
- """Validate USDC deposit amount."""
2084
1962
  actual_balance = await self._get_balance_raw(
2085
1963
  token_id=USDC_TOKEN_ID, wallet_address=self._get_main_wallet_address()
2086
1964
  )
@@ -2100,7 +1978,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2100
1978
  return (True, "USDC deposit amount validated", usdc_amount)
2101
1979
 
2102
1980
  async def _check_quote_profitability(self) -> tuple[bool, str]:
2103
- """Check if the quote APY is profitable."""
2104
1981
  quote = await self.quote()
2105
1982
  if quote.get("apy", 0) < 0:
2106
1983
  return (
@@ -2110,7 +1987,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2110
1987
  return (True, "Quote is profitable")
2111
1988
 
2112
1989
  async def _transfer_usdc_to_vault(self, usdc_amount: float) -> tuple[bool, str]:
2113
- """Transfer USDC from main wallet to vault wallet."""
2114
1990
  (
2115
1991
  success,
2116
1992
  msg,
@@ -2122,7 +1998,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2122
1998
  return (True, "USDC transferred to vault")
2123
1999
 
2124
2000
  async def _transfer_gas_to_vault(self) -> tuple[bool, str]:
2125
- """Transfer gas from main wallet to vault if needed."""
2126
2001
  vault_gas = await self._get_gas_balance()
2127
2002
  min_gas_wei = int(self._gas_keep_wei())
2128
2003
  if vault_gas < min_gas_wei:
@@ -2145,7 +2020,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2145
2020
  *,
2146
2021
  strict: bool = False,
2147
2022
  ) -> tuple[bool, str]:
2148
- """Sweep miscellaneous tokens above min_usd_value to target token."""
2149
2023
  if exclude is None:
2150
2024
  exclude = set()
2151
2025
 
@@ -2204,11 +2078,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2204
2078
  return (True, f"Swept {swept_count} tokens totaling ${total_swept_usd:.2f}")
2205
2079
 
2206
2080
  async def _claim_and_reinvest_rewards(self) -> tuple[bool, str]:
2207
- """Claim WELL rewards, swap to USDC, and lend directly to mUSDC (no leverage).
2208
-
2209
- This deposits rewards as unleveraged USDC collateral rather than running
2210
- them through the leverage loop, preserving the strategy's debt ratio.
2211
- """
2212
2081
  # Claim rewards if above threshold
2213
2082
  claimed_ok, claimed = await self.moonwell_adapter.claim_rewards(
2214
2083
  min_rewards_usd=self.MIN_REWARD_CLAIM_USD
@@ -2217,11 +2086,9 @@ class MoonwellWstethLoopStrategy(Strategy):
2217
2086
  logger.warning(f"Failed to claim rewards: {claimed}")
2218
2087
  return (True, "Reward claim failed, skipping reinvestment")
2219
2088
 
2220
- # Check if we actually got rewards (claimed is dict of token -> amount)
2221
2089
  if not claimed or not isinstance(claimed, dict):
2222
2090
  return (True, "No rewards to reinvest")
2223
2091
 
2224
- # Check WELL balance in wallet
2225
2092
  well_balance = await self._get_balance_raw(
2226
2093
  token_id=WELL_TOKEN_ID,
2227
2094
  wallet_address=self._get_strategy_wallet_address(),
@@ -2229,7 +2096,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2229
2096
  if well_balance <= 0:
2230
2097
  return (True, "No WELL balance to reinvest")
2231
2098
 
2232
- # Get WELL price and check value
2233
2099
  well_price, well_decimals = await self._get_token_data(WELL_TOKEN_ID)
2234
2100
  well_value_usd = (well_balance / 10**well_decimals) * well_price
2235
2101
 
@@ -2257,7 +2123,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2257
2123
  logger.warning(f"WELL swap error: {e}")
2258
2124
  return (True, f"WELL swap error: {e}")
2259
2125
 
2260
- # Get resulting USDC balance to lend
2261
2126
  usdc_balance = await self._get_balance_raw(
2262
2127
  token_id=USDC_TOKEN_ID,
2263
2128
  wallet_address=self._get_strategy_wallet_address(),
@@ -2283,19 +2148,15 @@ class MoonwellWstethLoopStrategy(Strategy):
2283
2148
  async def deposit(
2284
2149
  self, main_token_amount: float = 0.0, gas_token_amount: float = 0.0
2285
2150
  ) -> StatusTuple:
2286
- """Deposit USDC and execute leverage loop."""
2287
2151
  self._clear_price_cache()
2288
2152
 
2289
- # Validate deposit amount is positive
2290
2153
  if main_token_amount <= 0:
2291
2154
  return (False, "Deposit amount must be positive")
2292
2155
 
2293
- # Check quote profitability
2294
2156
  success, message = await self._check_quote_profitability()
2295
2157
  if not success:
2296
2158
  return (False, message)
2297
2159
 
2298
- # Validate USDC deposit amount
2299
2160
  success, message, validated_amount = await self._validate_usdc_deposit(
2300
2161
  main_token_amount
2301
2162
  )
@@ -2303,7 +2164,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2303
2164
  return (False, message)
2304
2165
  usdc_amount = validated_amount
2305
2166
 
2306
- # Validate gas balance
2307
2167
  success, message = await self._validate_gas_balance()
2308
2168
  if not success:
2309
2169
  return (False, message)
@@ -2324,10 +2184,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2324
2184
  )
2325
2185
 
2326
2186
  async def _get_collateral_factors(self) -> tuple[float, float]:
2327
- """Fetch both collateral factors (USDC and wstETH), using adapter cache.
2328
-
2329
- Returns (cf_usdc, cf_wsteth).
2330
- """
2331
2187
  cf_u_result, cf_w_result = await asyncio.gather(
2332
2188
  self.moonwell_adapter.get_collateral_factor(mtoken=M_USDC),
2333
2189
  self.moonwell_adapter.get_collateral_factor(mtoken=M_WSTETH),
@@ -2341,12 +2197,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2341
2197
  snap: Optional["MoonwellWstethLoopStrategy.AccountingSnapshot"] = None,
2342
2198
  collateral_factors: tuple[float, float] | None = None,
2343
2199
  ) -> tuple[float, float, float]:
2344
- """Returns (usdc_lend_value, wsteth_lend_value, current_leverage).
2345
-
2346
- Args:
2347
- snap: Optional accounting snapshot. If provided, skips snapshot fetches.
2348
- collateral_factors: Optional (cf_usdc, cf_wsteth) tuple; only used if `snap` is None.
2349
- """
2350
2200
  if snap is None:
2351
2201
  snap, _ = await self._accounting_snapshot(
2352
2202
  collateral_factors=collateral_factors
@@ -2364,7 +2214,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2364
2214
  return (usdc_lend_value, wsteth_lend_value, initial_leverage)
2365
2215
 
2366
2216
  async def _get_steth_apy(self) -> float | None:
2367
- """Fetch wstETH APY from Lido API."""
2368
2217
  url = "https://eth-api.lido.fi/v1/protocol/steth/apr/sma"
2369
2218
  try:
2370
2219
  async with httpx.AsyncClient(timeout=60) as client:
@@ -2380,8 +2229,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2380
2229
  return None
2381
2230
 
2382
2231
  async def quote(self) -> dict:
2383
- """Calculate projected APY for the strategy."""
2384
- # Get APYs and collateral factors in parallel
2385
2232
  (
2386
2233
  usdc_apy_result,
2387
2234
  weth_apy_result,
@@ -2413,7 +2260,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2413
2260
  if not cf_u or cf_u <= 0:
2414
2261
  return {"apy": 0, "information": "Invalid collateral factor", "data": {}}
2415
2262
 
2416
- # Calculate target borrow and leverage
2417
2263
  denominator = self.TARGET_HEALTH_FACTOR - cf_w
2418
2264
  if denominator <= 0:
2419
2265
  return {"apy": 0, "information": "Invalid health factor params", "data": {}}
@@ -2436,7 +2282,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2436
2282
  }
2437
2283
 
2438
2284
  async def _atomic_deposit_iteration(self, borrow_amt_wei: int) -> int:
2439
- """One atomic iteration: borrow WETH → swap wstETH → lend. Returns wstETH lent."""
2440
2285
  safe_borrow_amt = int(borrow_amt_wei * COLLATERAL_SAFETY_FACTOR)
2441
2286
  strategy_address = self._get_strategy_wallet_address()
2442
2287
 
@@ -2458,7 +2303,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2458
2303
  if not success:
2459
2304
  raise Exception(f"Borrow failed: {borrow_result}")
2460
2305
 
2461
- # Extract a deterministic pinned block number from the transaction result.
2462
2306
  # On Base we wait +2 blocks by default; `confirmed_block_number` is safe to pin reads to.
2463
2307
  pinned_block = self._pinned_block(borrow_result)
2464
2308
 
@@ -2614,7 +2458,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2614
2458
  ) from repay_exc
2615
2459
  raise Exception("Atomic deposit failed at swap step after all retries")
2616
2460
 
2617
- # Parse to_amount from swap result (may be int or string)
2618
2461
  raw_to_amount = (
2619
2462
  swap_result.get("to_amount", 0) if isinstance(swap_result, dict) else 0
2620
2463
  )
@@ -2623,7 +2466,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2623
2466
  except (ValueError, TypeError):
2624
2467
  to_amount_wei = 0
2625
2468
 
2626
- # Get actual wstETH balance
2627
2469
  wsteth_success, wsteth_bal_raw = await self.balance_adapter.get_balance(
2628
2470
  token_id=WSTETH_TOKEN_ID, wallet_address=self._get_strategy_wallet_address()
2629
2471
  )
@@ -2640,7 +2482,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2640
2482
  if lend_amt_wei <= 0:
2641
2483
  logger.warning("Swap resulted in 0 wstETH. Rolling back borrow...")
2642
2484
  try:
2643
- # Get WETH balance to repay (swap may have returned WETH or nothing)
2644
2485
  weth_bal = await self._get_balance_raw(
2645
2486
  token_id=WETH_TOKEN_ID, wallet_address=strategy_address
2646
2487
  )
@@ -2766,7 +2607,6 @@ class MoonwellWstethLoopStrategy(Strategy):
2766
2607
  return lend_amt_wei
2767
2608
 
2768
2609
  async def partial_liquidate(self, usd_value: float) -> StatusTuple:
2769
- """Create USDC liquidity in the strategy wallet by safely redeeming collateral."""
2770
2610
  if usd_value <= 0:
2771
2611
  raise ValueError(f"usd_value must be positive, got {usd_value}")
2772
2612
 
@@ -2961,19 +2801,16 @@ class MoonwellWstethLoopStrategy(Strategy):
2961
2801
  )
2962
2802
 
2963
2803
  async def _execute_deposit_loop(self, usdc_amount: float) -> tuple[bool, Any, int]:
2964
- """Execute the recursive leverage loop."""
2965
2804
  token_info = await self._get_token_info(USDC_TOKEN_ID)
2966
2805
  decimals = token_info.get("decimals", 6)
2967
2806
  initial_deposit = int(usdc_amount * 10**decimals)
2968
2807
 
2969
- # Fetch prices and collateral factors in parallel (use cache, minimal RPC)
2970
2808
  wsteth_price, weth_price, collateral_factors = await asyncio.gather(
2971
2809
  self._get_token_price(WSTETH_TOKEN_ID),
2972
2810
  self._get_token_price(WETH_TOKEN_ID),
2973
2811
  self._get_collateral_factors(),
2974
2812
  )
2975
2813
 
2976
- # Fetch position separately to avoid overwhelming public RPC
2977
2814
  weth_pos = await self.moonwell_adapter.get_pos(mtoken=M_WETH)
2978
2815
 
2979
2816
  current_borrowed_value = 0.0
@@ -3020,12 +2857,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3020
2857
  wsteth_lend_value: float,
3021
2858
  collateral_factors: tuple[float, float] | None = None,
3022
2859
  ) -> tuple[bool, Any, int]:
3023
- """Execute leverage loop until target health factor reached.
3024
-
3025
- Args:
3026
- collateral_factors: Optional (cf_usdc, cf_wsteth) tuple.
3027
- If provided, skips collateral factor fetches.
3028
- """
3029
2860
  # Ensure USDC and wstETH markets are entered as collateral before borrowing
3030
2861
  # This is idempotent - if already entered, Moonwell just returns success
3031
2862
  if usdc_lend_value > 0:
@@ -3069,7 +2900,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3069
2900
  else:
3070
2901
  cf_u, cf_w = await self._get_collateral_factors()
3071
2902
 
3072
- # Calculate depeg-aware max safe leverage fraction
3073
2903
  max_safe_f = self._max_safe_F(cf_w)
3074
2904
 
3075
2905
  # Guard against division by zero/negative denominator
@@ -3080,7 +2910,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3080
2910
  )
3081
2911
  return (False, initial_leverage, -1)
3082
2912
 
3083
- # Calculate target borrow value
3084
2913
  target_borrow_value = (
3085
2914
  usdc_lend_value * cf_u / denominator - current_borrowed_value
3086
2915
  )
@@ -3104,7 +2933,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3104
2933
  leverage_tracker: list[float] = [initial_leverage]
3105
2934
 
3106
2935
  for i in range(self._MAX_LOOP_LIMIT):
3107
- # Get borrowable amount (returns USD with 18 decimals)
3108
2936
  borrowable_result = await self.moonwell_adapter.get_borrowable_amount()
3109
2937
  if not borrowable_result[0]:
3110
2938
  logger.warning("Failed to get borrowable amount")
@@ -3121,7 +2949,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3121
2949
 
3122
2950
  weth_info = await self._get_token_info(WETH_TOKEN_ID)
3123
2951
  weth_decimals = weth_info.get("decimals", 18)
3124
- # Convert USD to WETH wei: (USD / price) * 10^decimals
3125
2952
  max_borrow_wei = int(borrowable_usd / weth_price * 10**weth_decimals)
3126
2953
 
3127
2954
  # remaining_value is how much more we need to borrow/lend THIS session
@@ -3175,7 +3002,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3175
3002
  return (True, leverage_tracker[-1], len(leverage_tracker) - 1)
3176
3003
 
3177
3004
  async def update(self) -> StatusTuple:
3178
- """Rebalance positions, then run a post-run safety guard."""
3179
3005
  logger.info("")
3180
3006
  logger.info("*" * 60)
3181
3007
  logger.info("* MOONWELL STRATEGY UPDATE CALLED")
@@ -3208,12 +3034,10 @@ class MoonwellWstethLoopStrategy(Strategy):
3208
3034
  )
3209
3035
 
3210
3036
  async def _update_impl(self) -> StatusTuple:
3211
- """Update implementation (called by update() wrapper)."""
3212
3037
  logger.info("=" * 60)
3213
3038
  logger.info("UPDATE START")
3214
3039
  logger.info("=" * 60)
3215
3040
 
3216
- # Check gas balance - deposit() should have provided sufficient gas
3217
3041
  gas_amt = await self._get_gas_balance()
3218
3042
  logger.info(
3219
3043
  f"Gas balance: {gas_amt / 10**18:.6f} ETH (min: {self.MAINTENANCE_GAS} ETH)"
@@ -3392,18 +3216,15 @@ class MoonwellWstethLoopStrategy(Strategy):
3392
3216
  reward_ok, reward_msg = await self._claim_and_reinvest_rewards()
3393
3217
  logger.info(f" Rewards: {reward_msg}")
3394
3218
 
3395
- # Check profitability
3396
3219
  success, msg = await self._check_quote_profitability()
3397
3220
  if not success:
3398
3221
  return (False, msg)
3399
3222
 
3400
- # Get USDC balance in wallet
3401
3223
  usdc_balance_wei = await self._get_usdc_balance()
3402
3224
  token_info = await self._get_token_info(USDC_TOKEN_ID)
3403
3225
  decimals = token_info.get("decimals", 6)
3404
3226
  usdc_balance = usdc_balance_wei / 10**decimals
3405
3227
 
3406
- # Get lend values from the snapshot
3407
3228
  usdc_key = f"Base_{M_USDC}"
3408
3229
  wsteth_key = f"Base_{M_WSTETH}"
3409
3230
  usdc_lend_value = totals_usd.get(usdc_key, 0)
@@ -3506,7 +3327,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3506
3327
  )
3507
3328
 
3508
3329
  async def _repay_weth(self, amount: int, remaining_debt: int) -> int:
3509
- """Repay WETH debt. Returns amount actually repaid."""
3510
3330
  if amount <= 0 or remaining_debt <= 0:
3511
3331
  return 0
3512
3332
 
@@ -3515,9 +3335,7 @@ class MoonwellWstethLoopStrategy(Strategy):
3515
3335
 
3516
3336
  # Only use repay_full when we have a small buffer above the observed debt.
3517
3337
  # This avoids cases where debt accrues between snapshot and execution and leaves dust.
3518
- full_repay_buffer_wei = max(
3519
- 10_000, remaining_debt // 10_000
3520
- ) # 0.01% or 10k wei
3338
+ full_repay_buffer_wei = max(10_000, remaining_debt // 10_000)
3521
3339
  can_repay_full = amount >= (remaining_debt + full_repay_buffer_wei)
3522
3340
 
3523
3341
  if can_repay_full:
@@ -3543,7 +3361,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3543
3361
  async def _swap_to_weth_and_repay(
3544
3362
  self, token_id: str, amount: int, remaining_debt: int
3545
3363
  ) -> int:
3546
- """Swap token to WETH and repay. Returns amount repaid."""
3547
3364
  swap_result = await self._swap_with_retries(
3548
3365
  from_token_id=token_id,
3549
3366
  to_token_id=WETH_TOKEN_ID,
@@ -3580,7 +3397,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3580
3397
  return await self._repay_weth(weth_bal, remaining_debt)
3581
3398
 
3582
3399
  async def withdraw(self, amount: float | None = None) -> StatusTuple:
3583
- """Withdraw funds; on failure, run a post-run safety guard."""
3584
3400
  logger.info("")
3585
3401
  logger.info("*" * 60)
3586
3402
  logger.info("* MOONWELL STRATEGY WITHDRAW CALLED")
@@ -3613,12 +3429,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3613
3429
  return (False, f"{status[1]} | {suffix}")
3614
3430
 
3615
3431
  async def _withdraw_impl(self, amount: float | None = None) -> StatusTuple:
3616
- """Withdraw implementation (called by withdraw() wrapper).
3617
-
3618
- Logic:
3619
- 1. Liquidate any Moonwell positions to USDC (if any exist)
3620
- 2. Transfer any USDC > 0 to main wallet
3621
- """
3622
3432
  # NOTE: amount is currently unused; withdraw() is all-or-nothing in this strategy.
3623
3433
  logger.info("=" * 60)
3624
3434
  logger.info("WITHDRAW START - Full position unwind")
@@ -3626,7 +3436,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3626
3436
 
3627
3437
  collateral_factors = await self._get_collateral_factors()
3628
3438
 
3629
- # Get initial state for logging
3630
3439
  snap, _ = await self._accounting_snapshot(collateral_factors=collateral_factors)
3631
3440
  logger.info("INITIAL STATE:")
3632
3441
  logger.info(f" USDC supplied: ${snap.usdc_supplied / 10**6:.2f}")
@@ -3670,9 +3479,7 @@ class MoonwellWstethLoopStrategy(Strategy):
3670
3479
  (
3671
3480
  ok,
3672
3481
  msg,
3673
- ) = (
3674
- await self._unlend_remaining_positions()
3675
- ) # swaps wstETH->USDC internally
3482
+ ) = await self._unlend_remaining_positions()
3676
3483
  except SwapOutcomeUnknownError as exc:
3677
3484
  return (False, f"Swap outcome unknown while unlending positions: {exc}")
3678
3485
  except Exception as exc: # noqa: BLE001
@@ -3723,7 +3530,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3723
3530
  )
3724
3531
 
3725
3532
  async def exit(self, **kwargs) -> StatusTuple:
3726
- """Transfer funds from strategy wallet to main wallet."""
3727
3533
  logger.info("")
3728
3534
  logger.info("*" * 60)
3729
3535
  logger.info("* MOONWELL STRATEGY EXIT CALLED")
@@ -3757,7 +3563,7 @@ class MoonwellWstethLoopStrategy(Strategy):
3757
3563
  gas_balance = await self._get_balance_raw(
3758
3564
  token_id=ETH_TOKEN_ID, wallet_address=self._get_strategy_wallet_address()
3759
3565
  )
3760
- tx_fee_reserve = int(0.0002 * 10**18) # Reserve 0.0002 ETH for tx fee
3566
+ tx_fee_reserve = int(0.0002 * 10**18)
3761
3567
  transferable_gas = gas_balance - tx_fee_reserve
3762
3568
  if transferable_gas > 0:
3763
3569
  gas_amount = transferable_gas / 10**18
@@ -3780,7 +3586,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3780
3586
  return (True, f"Transferred to main wallet: {', '.join(transferred_items)}")
3781
3587
 
3782
3588
  async def _unlend_remaining_positions(self) -> tuple[bool, str]:
3783
- """Unlend remaining collateral and convert to USDC."""
3784
3589
  logger.info("UNLEND: Redeeming remaining Moonwell positions...")
3785
3590
 
3786
3591
  # Unlend remaining wstETH
@@ -3828,7 +3633,7 @@ class MoonwellWstethLoopStrategy(Strategy):
3828
3633
  # Sweep any remaining tokens to USDC
3829
3634
  ok, msg = await self._sweep_token_balances(
3830
3635
  target_token_id=USDC_TOKEN_ID,
3831
- exclude={ETH_TOKEN_ID, WELL_TOKEN_ID}, # Keep gas + reward token
3636
+ exclude={ETH_TOKEN_ID, WELL_TOKEN_ID},
3832
3637
  min_usd_value=float(self.sweep_min_usd),
3833
3638
  strict=True,
3834
3639
  )
@@ -3837,7 +3642,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3837
3642
  return (True, "Unlent remaining positions")
3838
3643
 
3839
3644
  async def get_peg_diff(self) -> float | dict:
3840
- """Get stETH/ETH peg difference."""
3841
3645
  steth_price = await self._get_token_price(STETH_TOKEN_ID)
3842
3646
  weth_price = await self._get_token_price(WETH_TOKEN_ID)
3843
3647
 
@@ -3853,22 +3657,18 @@ class MoonwellWstethLoopStrategy(Strategy):
3853
3657
  return peg_diff
3854
3658
 
3855
3659
  async def _status(self) -> StatusDict:
3856
- """Report strategy status."""
3857
3660
  snap, _ = await self._accounting_snapshot()
3858
3661
  totals_usd = dict(snap.totals_usd)
3859
3662
 
3860
3663
  ltv = float(snap.ltv)
3861
3664
  hf = (1 / ltv) if ltv and ltv > 0 and not (ltv != ltv) else None
3862
3665
 
3863
- # Get gas balance
3864
3666
  gas_balance = await self._get_gas_balance()
3865
3667
 
3866
- # Get borrowable amount
3867
3668
  borrowable_result = await self.moonwell_adapter.get_borrowable_amount()
3868
3669
  borrowable_amt_raw = borrowable_result[1] if borrowable_result[0] else 0
3869
3670
  borrowable_amt = self._normalize_usd_value(borrowable_amt_raw)
3870
3671
 
3871
- # Calculate credit remaining
3872
3672
  total_borrowed = float(snap.debt_usd)
3873
3673
  credit_remaining = 1.0
3874
3674
  if (borrowable_amt + total_borrowed) > 0:
@@ -3876,13 +3676,10 @@ class MoonwellWstethLoopStrategy(Strategy):
3876
3676
  borrowable_amt / (borrowable_amt + total_borrowed), 4
3877
3677
  )
3878
3678
 
3879
- # Get peg diff
3880
3679
  peg_diff = await self.get_peg_diff()
3881
3680
 
3882
- # Calculate portfolio value
3883
3681
  portfolio_value = float(snap.net_equity_usd)
3884
3682
 
3885
- # Get projected earnings
3886
3683
  quote = await self.quote()
3887
3684
 
3888
3685
  strategy_status = {
@@ -3896,7 +3693,7 @@ class MoonwellWstethLoopStrategy(Strategy):
3896
3693
 
3897
3694
  return StatusDict(
3898
3695
  portfolio_value=portfolio_value,
3899
- net_deposit=0.0, # Would need ledger integration
3696
+ net_deposit=0.0,
3900
3697
  strategy_status=strategy_status,
3901
3698
  gas_available=gas_balance / 10**18,
3902
3699
  gassed_up=gas_balance >= int(self.MAINTENANCE_GAS * 10**18),
@@ -3904,7 +3701,6 @@ class MoonwellWstethLoopStrategy(Strategy):
3904
3701
 
3905
3702
  @staticmethod
3906
3703
  async def policies() -> list[str]:
3907
- """Return policy strings used to scope on-chain permissions."""
3908
3704
  return [
3909
3705
  # Moonwell operations
3910
3706
  await musdc_mint_or_approve_or_redeem(),