wayfinder-paths 0.1.8__py3-none-any.whl → 0.1.10__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 (80) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +6 -15
  2. wayfinder_paths/adapters/balance_adapter/README.md +1 -2
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +4 -4
  4. wayfinder_paths/adapters/brap_adapter/README.md +1 -1
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +139 -74
  6. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
  7. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
  8. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
  9. wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
  10. wayfinder_paths/adapters/moonwell_adapter/README.md +174 -0
  11. wayfinder_paths/adapters/moonwell_adapter/__init__.py +7 -0
  12. wayfinder_paths/adapters/moonwell_adapter/adapter.py +1226 -0
  13. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +635 -0
  14. wayfinder_paths/adapters/pool_adapter/README.md +1 -77
  15. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -122
  16. wayfinder_paths/adapters/pool_adapter/examples.json +0 -57
  17. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -86
  18. wayfinder_paths/adapters/token_adapter/README.md +1 -1
  19. wayfinder_paths/core/clients/ClientManager.py +1 -22
  20. wayfinder_paths/core/clients/WalletClient.py +0 -8
  21. wayfinder_paths/core/clients/WayfinderClient.py +7 -12
  22. wayfinder_paths/core/clients/__init__.py +0 -8
  23. wayfinder_paths/core/clients/protocols.py +0 -60
  24. wayfinder_paths/core/config.py +5 -45
  25. wayfinder_paths/core/constants/__init__.py +0 -2
  26. wayfinder_paths/core/constants/base.py +6 -2
  27. wayfinder_paths/core/constants/moonwell_abi.py +411 -0
  28. wayfinder_paths/core/services/base.py +7 -1
  29. wayfinder_paths/core/services/local_evm_txn.py +223 -222
  30. wayfinder_paths/core/services/local_token_txn.py +103 -92
  31. wayfinder_paths/core/services/web3_service.py +0 -2
  32. wayfinder_paths/core/settings.py +8 -8
  33. wayfinder_paths/core/strategies/Strategy.py +1 -5
  34. wayfinder_paths/core/strategies/descriptors.py +1 -1
  35. wayfinder_paths/core/utils/evm_helpers.py +7 -12
  36. wayfinder_paths/core/wallets/README.md +3 -6
  37. wayfinder_paths/run_strategy.py +62 -105
  38. wayfinder_paths/scripts/create_strategy.py +2 -27
  39. wayfinder_paths/scripts/make_wallets.py +1 -25
  40. wayfinder_paths/scripts/run_strategy.py +37 -9
  41. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
  42. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +87 -138
  43. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
  44. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -17
  45. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
  46. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +107 -29
  47. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
  48. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +108 -0
  49. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/examples.json +11 -0
  50. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2975 -0
  51. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +886 -0
  52. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +0 -7
  53. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +2 -7
  54. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -4
  55. wayfinder_paths/templates/adapter/README.md +5 -21
  56. wayfinder_paths/templates/adapter/adapter.py +1 -2
  57. wayfinder_paths/templates/adapter/test_adapter.py +1 -1
  58. wayfinder_paths/templates/strategy/README.md +4 -21
  59. wayfinder_paths/templates/strategy/test_strategy.py +0 -4
  60. wayfinder_paths/tests/test_smoke_manifest.py +17 -2
  61. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/METADATA +64 -201
  62. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/RECORD +64 -71
  63. wayfinder_paths/adapters/balance_adapter/manifest.yaml +0 -8
  64. wayfinder_paths/adapters/brap_adapter/manifest.yaml +0 -11
  65. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +0 -10
  66. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +0 -8
  67. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +0 -11
  68. wayfinder_paths/adapters/pool_adapter/manifest.yaml +0 -10
  69. wayfinder_paths/adapters/token_adapter/manifest.yaml +0 -6
  70. wayfinder_paths/core/clients/SimulationClient.py +0 -192
  71. wayfinder_paths/core/clients/TransactionClient.py +0 -63
  72. wayfinder_paths/core/engine/manifest.py +0 -97
  73. wayfinder_paths/scripts/validate_manifests.py +0 -213
  74. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +0 -23
  75. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +0 -7
  76. wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +0 -17
  77. wayfinder_paths/templates/adapter/manifest.yaml +0 -6
  78. wayfinder_paths/templates/strategy/manifest.yaml +0 -8
  79. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/LICENSE +0 -0
  80. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/WHEEL +0 -0
@@ -112,7 +112,7 @@ class HyperlendStableYieldStrategy(Strategy):
112
112
  gas_threshold=MAX_GAS / 3,
113
113
  # risk indicators
114
114
  volatility=Volatility.LOW,
115
- volatility_description_short=(
115
+ volatility_description=(
116
116
  "Pure HyperLend stablecoin lending keeps NAV steady aside from rate drift."
117
117
  ),
118
118
  directionality=Directionality.MARKET_NEUTRAL,
@@ -195,7 +195,6 @@ class HyperlendStableYieldStrategy(Strategy):
195
195
  *,
196
196
  main_wallet: dict[str, Any] | None = None,
197
197
  strategy_wallet: dict[str, Any] | None = None,
198
- simulation: bool = False,
199
198
  web3_service: Web3Service = None,
200
199
  api_key: str | None = None,
201
200
  ):
@@ -207,7 +206,6 @@ class HyperlendStableYieldStrategy(Strategy):
207
206
  merged_config["strategy_wallet"] = strategy_wallet
208
207
 
209
208
  self.config = merged_config
210
- self.simulation = simulation
211
209
  self.balance_adapter = None
212
210
  self.tx_adapter = None
213
211
  self.token_adapter = None
@@ -238,7 +236,6 @@ class HyperlendStableYieldStrategy(Strategy):
238
236
  token_transaction_service = LocalTokenTxnService(
239
237
  adapter_config,
240
238
  wallet_provider=wallet_provider,
241
- simulation=self.simulation,
242
239
  )
243
240
  web3_service = DefaultWeb3Service(
244
241
  wallet_provider=wallet_provider,
@@ -250,12 +247,9 @@ class HyperlendStableYieldStrategy(Strategy):
250
247
  balance = BalanceAdapter(adapter_config, web3_service=web3_service)
251
248
  token_adapter = TokenAdapter()
252
249
  ledger_adapter = LedgerAdapter() # here
253
- brap_adapter = BRAPAdapter(
254
- web3_service=web3_service, simulation=self.simulation
255
- )
250
+ brap_adapter = BRAPAdapter(web3_service=web3_service)
256
251
  hyperlend_adapter = HyperlendAdapter(
257
252
  adapter_config,
258
- simulation=self.simulation,
259
253
  web3_service=web3_service,
260
254
  )
261
255
 
@@ -323,11 +317,109 @@ class HyperlendStableYieldStrategy(Strategy):
323
317
  self.hys_z: float = self.HYSTERESIS_Z
324
318
  self.rotation_tx_cost: float = self.ROTATION_TX_COST
325
319
 
326
- async def deposit(self) -> StatusTuple:
320
+ async def deposit(
321
+ self, main_token_amount: float = 0.0, gas_token_amount: float = 0.0
322
+ ) -> StatusTuple:
323
+ if main_token_amount == 0.0 and gas_token_amount == 0.0:
324
+ return (
325
+ False,
326
+ "Either main_token_amount or gas_token_amount must be provided",
327
+ )
328
+
329
+ if main_token_amount > 0:
330
+ if main_token_amount < self.MIN_USDT0_DEPOSIT_AMOUNT:
331
+ return (
332
+ False,
333
+ f"Main token amount {main_token_amount} is below minimum {self.MIN_USDT0_DEPOSIT_AMOUNT}",
334
+ )
335
+
336
+ if gas_token_amount and gas_token_amount > self.GAS_MAXIMUM:
337
+ return (
338
+ False,
339
+ f"Gas token amount exceeds maximum configured gas buffer: {self.GAS_MAXIMUM}",
340
+ )
341
+
342
+ if self.balance_adapter is None:
343
+ return (
344
+ False,
345
+ "Balance adapter not initialized. Strategy initialization may have failed.",
346
+ )
347
+
348
+ (
349
+ success,
350
+ main_usdt0_balance,
351
+ ) = await self.balance_adapter.get_balance(
352
+ token_id=self.usdt_token_info.get("token_id"),
353
+ wallet_address=self._get_main_wallet_address(),
354
+ )
355
+ if not success:
356
+ return (
357
+ False,
358
+ f"Failed to get main wallet USDT0 balance: {main_usdt0_balance}",
359
+ )
360
+
361
+ (
362
+ success,
363
+ main_hype_balance,
364
+ ) = await self.balance_adapter.get_balance(
365
+ token_id=self.hype_token_info.get("token_id"),
366
+ wallet_address=self._get_main_wallet_address(),
367
+ )
368
+ if not success:
369
+ return (
370
+ False,
371
+ f"Failed to get main wallet HYPE balance: {main_hype_balance}",
372
+ )
373
+
374
+ main_usdt0_native = main_usdt0_balance / (
375
+ 10 ** self.usdt_token_info.get("decimals")
376
+ )
377
+ main_hype_native = main_hype_balance / (
378
+ 10 ** self.hype_token_info.get("decimals")
379
+ )
380
+
381
+ if main_token_amount > 0:
382
+ if main_usdt0_native < main_token_amount:
383
+ return (
384
+ False,
385
+ f"Main wallet USDT0 balance is less than the deposit amount: {main_usdt0_native} < {main_token_amount}",
386
+ )
387
+
388
+ if gas_token_amount > 0:
389
+ if main_hype_native < gas_token_amount:
390
+ return (
391
+ False,
392
+ f"Main wallet HYPE balance is less than the deposit amount: {main_hype_native} < {gas_token_amount}",
393
+ )
394
+
395
+ if gas_token_amount > 0:
396
+ (
397
+ success,
398
+ msg,
399
+ ) = await self.balance_adapter.move_from_main_wallet_to_strategy_wallet(
400
+ self.hype_token_info.get("token_id"),
401
+ gas_token_amount,
402
+ strategy_name=self.name,
403
+ )
404
+ if not success:
405
+ return (False, f"HYPE transfer to strategy failed: {msg}")
406
+
407
+ if main_token_amount > 0:
408
+ (
409
+ success,
410
+ msg,
411
+ ) = await self.balance_adapter.move_from_main_wallet_to_strategy_wallet(
412
+ self.usdt_token_info.get("token_id"),
413
+ main_token_amount,
414
+ strategy_name=self.name,
415
+ )
416
+ if not success:
417
+ return (False, f"USDT0 transfer to strategy failed: {msg}")
418
+
327
419
  self._invalidate_assets_snapshot()
328
420
  await self._hydrate_position_from_chain()
329
421
 
330
- return (True, "hydrated positions")
422
+ return (success, msg)
331
423
 
332
424
  async def _estimate_redeploy_tokens(self) -> float:
333
425
  positions = await self._get_lent_positions()
@@ -1344,26 +1436,12 @@ class HyperlendStableYieldStrategy(Strategy):
1344
1436
  continue
1345
1437
 
1346
1438
  balance_tokens = float(entry.get("tokens") or 0)
1347
- if balance_tokens <= 0:
1348
- continue
1349
-
1350
- try:
1351
- (
1352
- transfer_success,
1353
- _,
1354
- ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
1355
- token_id=token.get("token_id"),
1356
- amount=balance_tokens,
1357
- strategy_name=self.name,
1358
- )
1359
- except Exception:
1360
- continue
1361
-
1362
- if transfer_success:
1363
- actions.append(
1364
- f"Transferred {balance_tokens:.4f} {token.get('symbol')} to main wallet"
1439
+ if balance_tokens > 0:
1440
+ logger.warning(
1441
+ f"Failed to swap {balance_tokens:.4f} {token.get('symbol', 'unknown')} "
1442
+ f"to {target_token.get('symbol', 'target')} during alignment. "
1443
+ f"Leaving in strategy wallet for next update cycle."
1365
1444
  )
1366
- self._invalidate_assets_snapshot()
1367
1445
 
1368
1446
  kept_tokens = float(balances.get("native", {}).get("tokens") or 0)
1369
1447
 
@@ -46,20 +46,20 @@ def strategy():
46
46
  config=mock_config,
47
47
  main_wallet=mock_config["main_wallet"],
48
48
  strategy_wallet=mock_config["strategy_wallet"],
49
- simulation=True,
50
49
  )
51
50
 
52
51
  if hasattr(s, "balance_adapter") and s.balance_adapter:
53
52
  # Mock balances: 1000 USDT0 (with 6 decimals) and 2 HYPE (with 18 decimals)
54
- usdt0_balance_mock = AsyncMock(return_value=(True, 1000000000))
55
- hype_balance_mock = AsyncMock(return_value=(True, 2000000000000000000))
56
-
57
53
  def get_balance_side_effect(token_id, wallet_address, **kwargs):
58
- if token_id == "usdt0-hyperevm" or token_id == "usdt0":
59
- return usdt0_balance_mock.return_value
60
- elif token_id == "hype-hyperevm" or token_id == "hype":
61
- return hype_balance_mock.return_value
62
- return (True, 1000000000)
54
+ token_id_str = str(token_id).lower() if token_id else ""
55
+ if "usdt0" in token_id_str or token_id_str == "usdt0":
56
+ # 1000 USDT0 with 6 decimals = 1000 * 10^6 = 1000000000
57
+ return (True, 1000000000)
58
+ elif "hype" in token_id_str or token_id_str == "hype":
59
+ # 2 HYPE with 18 decimals = 2 * 10^18 = 2000000000000000000
60
+ return (True, 2000000000000000000)
61
+ # Default: return high balance for any other token
62
+ return (True, 2000000000000000000)
63
63
 
64
64
  s.balance_adapter.get_balance = AsyncMock(side_effect=get_balance_side_effect)
65
65
 
@@ -101,15 +101,54 @@ def strategy():
101
101
  )
102
102
 
103
103
  if hasattr(s, "balance_adapter") and s.balance_adapter:
104
+ # Mock the main methods first
104
105
  s.balance_adapter.move_from_main_wallet_to_strategy_wallet = AsyncMock(
105
106
  return_value=(True, "Transfer successful (simulated)")
106
107
  )
107
108
  s.balance_adapter.move_from_strategy_wallet_to_main_wallet = AsyncMock(
108
109
  return_value=(True, "Transfer successful (simulated)")
109
110
  )
110
- if hasattr(s.balance_adapter, "wallet_provider"):
111
- s.balance_adapter.wallet_provider.broadcast_transaction = AsyncMock(
112
- return_value=(True, {"transaction_hash": "0xCAFEBABE"})
111
+ # Mock internal dependencies unconditionally to prevent MagicMock await errors
112
+ # These are needed if the real method somehow gets called
113
+ s.balance_adapter.token_client = AsyncMock()
114
+ s.balance_adapter.token_client.get_token_details = AsyncMock(
115
+ return_value={
116
+ "id": "usdt0-hyperevm",
117
+ "address": "0x1234",
118
+ "decimals": 6,
119
+ }
120
+ )
121
+ s.balance_adapter.token_transactions = AsyncMock()
122
+ s.balance_adapter.token_transactions.build_send = AsyncMock(
123
+ return_value=(True, {"transaction": "0xMOCK"})
124
+ )
125
+ s.balance_adapter.wallet_provider = AsyncMock()
126
+ s.balance_adapter.wallet_provider.broadcast_transaction = AsyncMock(
127
+ return_value=(True, {"transaction_hash": "0xCAFEBABE"})
128
+ )
129
+ # token_adapter might already be set, so check before overriding
130
+ if (
131
+ not hasattr(s.balance_adapter, "token_adapter")
132
+ or s.balance_adapter.token_adapter is None
133
+ ):
134
+ s.balance_adapter.token_adapter = AsyncMock()
135
+ if hasattr(s.balance_adapter.token_adapter, "get_token_price"):
136
+ s.balance_adapter.token_adapter.get_token_price = AsyncMock(
137
+ return_value=(True, {"current_price": 1.0})
138
+ )
139
+ # ledger_adapter might already be set, so check before overriding
140
+ if (
141
+ not hasattr(s.balance_adapter, "ledger_adapter")
142
+ or s.balance_adapter.ledger_adapter is None
143
+ ):
144
+ s.balance_adapter.ledger_adapter = AsyncMock()
145
+ if hasattr(s.balance_adapter.ledger_adapter, "record_deposit"):
146
+ s.balance_adapter.ledger_adapter.record_deposit = AsyncMock(
147
+ return_value=(True, {})
148
+ )
149
+ if hasattr(s.balance_adapter.ledger_adapter, "record_withdrawal"):
150
+ s.balance_adapter.ledger_adapter.record_withdrawal = AsyncMock(
151
+ return_value=(True, {})
113
152
  )
114
153
 
115
154
  if hasattr(s, "ledger_adapter") and s.ledger_adapter:
@@ -304,8 +343,8 @@ async def test_canonical_usage(strategy):
304
343
  for example_name, example_data in canonical.items():
305
344
  if "deposit" in example_data:
306
345
  deposit_params = example_data.get("deposit", {})
307
- ok, _ = await strategy.deposit(**deposit_params)
308
- assert ok, f"Canonical example '{example_name}' deposit failed"
346
+ ok, msg = await strategy.deposit(**deposit_params)
347
+ assert ok, f"Canonical example '{example_name}' deposit failed: {msg}"
309
348
 
310
349
  if "update" in example_data:
311
350
  result = await strategy.update()
@@ -0,0 +1,108 @@
1
+ # Moonwell wstETH Loop Strategy
2
+
3
+ - Entrypoint: `strategies.moonwell_wsteth_loop_strategy.strategy.MoonwellWstethLoopStrategy`
4
+ - Examples: `examples.json`
5
+ - Tests: `test_strategy.py`
6
+
7
+ ## What it does
8
+
9
+ A leveraged liquid-staking carry trade on Base that loops USDC collateral into wstETH exposure. The strategy:
10
+
11
+ 1. Deposits USDC as initial collateral on Moonwell lending protocol.
12
+ 2. Borrows WETH against the USDC collateral.
13
+ 3. Swaps WETH to wstETH via Aerodrome/BRAP routing.
14
+ 4. Lends wstETH back to Moonwell as additional collateral.
15
+ 5. Repeats the loop until target leverage is reached or marginal gains fall below threshold.
16
+
17
+ The position is **delta-neutral**: WETH debt offsets wstETH collateral, so PnL is driven by the spread between wstETH staking yield and WETH borrow cost.
18
+
19
+ ## Key parameters
20
+
21
+ - `MIN_GAS = 0.002` ETH (minimum Base ETH for gas)
22
+ - `MIN_USDC_DEPOSIT = 20` USDC (minimum initial collateral)
23
+ - `MAX_DEPEG = 0.01` (1% max stETH/ETH depeg threshold)
24
+ - `MIN_HEALTH_FACTOR = 1.2` (triggers deleveraging if below)
25
+ - `MAX_HEALTH_FACTOR = 1.5` (triggers leverage loop if above)
26
+ - `leverage_limit = 10` (maximum leverage multiplier)
27
+ - `COLLATERAL_SAFETY_FACTOR = 0.98` (2% safety buffer on borrows)
28
+ - `MAX_SLIPPAGE_TOLERANCE = 0.03` (3% max slippage to prevent MEV)
29
+ - `_MIN_LEVERAGE_GAIN_BPS = 50e-4` (stop looping if marginal gain < 50 bps)
30
+
31
+ ## Safety features
32
+
33
+ - **Depeg guard**: `_max_safe_F()` calculates leverage ceiling based on wstETH collateral factor and max depeg tolerance.
34
+ - **Delta-neutrality**: `_balance_weth_debt()` rebalances when WETH debt exceeds wstETH collateral value.
35
+ - **Swap retries**: `_swap_with_retries()` uses progressive slippage (0.5% → 1% → 1.5%) with exponential backoff.
36
+ - **Health monitoring**: Automatic deleveraging when health factor drops below `MIN_HEALTH_FACTOR`.
37
+ - **Rollback protection**: Checks actual balances before rollback swaps to prevent failed transactions.
38
+
39
+ ## Adapters used
40
+
41
+ - `BalanceAdapter` for token balances and wallet transfers with ledger tracking.
42
+ - `TokenAdapter` for token metadata and price feeds.
43
+ - `LedgerAdapter` for net deposit tracking.
44
+ - `BRAPAdapter` for swap quotes and execution via Aerodrome/routing.
45
+ - `MoonwellAdapter` for lending, borrowing, collateral management, and position queries.
46
+ - `LocalTokenTxnService` via `DefaultWeb3Service` for low-level sends/approvals.
47
+
48
+ ## Actions
49
+
50
+ ### Deposit
51
+
52
+ - Validates USDC and ETH balances in the main wallet.
53
+ - Transfers ETH (gas) into the strategy wallet if needed.
54
+ - Moves USDC from main wallet to strategy wallet.
55
+ - Lends USDC on Moonwell and enables as collateral.
56
+ - Executes leverage loop: borrow WETH → swap to wstETH → lend wstETH → repeat.
57
+
58
+ ### Update
59
+
60
+ - Checks gas balance meets maintenance threshold.
61
+ - Balances WETH debt against wstETH collateral for delta-neutrality.
62
+ - Computes health factor from aggregated positions.
63
+ - If HF < MIN: triggers deleveraging via `_repay_debt_loop()`.
64
+ - If HF > MAX: executes additional leverage loops to optimize yield.
65
+ - Claims WELL rewards if above minimum threshold.
66
+
67
+ ### Status
68
+
69
+ `_status()` returns:
70
+
71
+ - `portfolio_value`: sum of all position values (USDC lent + wstETH lent - WETH debt)
72
+ - `net_deposit`: fetched from LedgerAdapter
73
+ - `strategy_status`: includes current leverage, health factor, LTV, peg diff, credit remaining
74
+
75
+ ### Withdraw
76
+
77
+ - Sweeps miscellaneous token balances to WETH.
78
+ - Repays all WETH debt via `_repay_debt_loop()`.
79
+ - Unlends wstETH, swaps to USDC.
80
+ - Unlends USDC collateral.
81
+ - Returns USDC and remaining ETH to main wallet.
82
+
83
+ ## Running locally
84
+
85
+ ```bash
86
+ # Install dependencies
87
+ poetry install
88
+
89
+ # Generate wallets (writes wallets.json)
90
+ poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
91
+
92
+ # Copy config and edit credentials
93
+ cp wayfinder_paths/config.example.json config.json
94
+
95
+ # Check status / health
96
+ poetry run python wayfinder_paths/run_strategy.py moonwell_wsteth_loop_strategy --action status --config $(pwd)/config.json
97
+
98
+ # Perform a deposit/update/withdraw cycle
99
+ poetry run python wayfinder_paths/run_strategy.py moonwell_wsteth_loop_strategy --action deposit --main-token-amount 100 --gas-token-amount 0.01 --config $(pwd)/config.json
100
+ poetry run python wayfinder_paths/run_strategy.py moonwell_wsteth_loop_strategy --action update --config $(pwd)/config.json
101
+ poetry run python wayfinder_paths/run_strategy.py moonwell_wsteth_loop_strategy --action withdraw --config $(pwd)/config.json
102
+ ```
103
+
104
+ ## Testing
105
+
106
+ ```bash
107
+ poetry run pytest wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/ -v
108
+ ```
@@ -0,0 +1,11 @@
1
+ {
2
+ "smoke": {
3
+ "deposit": {
4
+ "main_token_amount": 0.1,
5
+ "gas_token_amount": 0.001
6
+ },
7
+ "update": {},
8
+ "status": {},
9
+ "withdraw": {}
10
+ }
11
+ }