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
@@ -12,8 +12,7 @@ from pathlib import Path
12
12
 
13
13
  from loguru import logger
14
14
 
15
- from wayfinder_paths.core.config import StrategyJobConfig, load_config_from_env
16
- from wayfinder_paths.core.engine.manifest import load_manifest, validate_manifest
15
+ from wayfinder_paths.core.config import StrategyJobConfig
17
16
  from wayfinder_paths.core.engine.StrategyJob import StrategyJob
18
17
 
19
18
 
@@ -21,82 +20,101 @@ def load_strategy(
21
20
  strategy_name: str,
22
21
  *,
23
22
  strategy_config: dict | None = None,
24
- simulation: bool = False,
25
23
  api_key: str | None = None,
26
24
  ):
27
25
  """
28
- Dynamically load a strategy by name using its manifest
26
+ Dynamically load a strategy by name
29
27
 
30
28
  Args:
31
29
  strategy_name: Name of the strategy to load (directory name in strategies/)
32
30
  strategy_config: Configuration dict for the strategy
33
- simulation: Enable simulation mode for testing
34
31
  api_key: Optional API key for service account authentication
35
32
 
36
33
  Returns:
37
34
  Strategy instance
38
35
  """
39
- # Find strategy manifest by scanning for manifest.yaml in the strategy directory
36
+ # Build the expected module path from strategy name
40
37
  strategies_dir = Path(__file__).parent / "strategies"
41
38
  strategy_dir = strategies_dir / strategy_name
42
- manifest_path = strategy_dir / "manifest.yaml"
43
39
 
44
- if not manifest_path.exists():
40
+ if not strategy_dir.exists():
45
41
  # List available strategies for better error message
46
42
  available = []
47
43
  if strategies_dir.exists():
48
44
  for path in strategies_dir.iterdir():
49
- if path.is_dir() and (path / "manifest.yaml").exists():
45
+ if path.is_dir() and (path / "strategy.py").exists():
50
46
  available.append(path.name)
51
47
  available_str = ", ".join(available) if available else "none"
52
48
  raise ValueError(
53
49
  f"Unknown strategy: {strategy_name}. Available strategies: {available_str}"
54
50
  )
55
51
 
56
- # Load manifest and use its entrypoint
57
- manifest = load_manifest(str(manifest_path))
58
- module_path, class_name = manifest.entrypoint.rsplit(".", 1)
59
- module = __import__(module_path, fromlist=[class_name])
60
- strategy_class = getattr(module, class_name)
52
+ # Import strategy module and find Strategy class
53
+ module_path = f"strategies.{strategy_name}.strategy"
54
+ module = __import__(module_path, fromlist=[""])
61
55
 
62
- return strategy_class(
63
- config=strategy_config, simulation=simulation, api_key=api_key
64
- )
56
+ # Find the Strategy subclass in the module
57
+ from wayfinder_paths.core.strategies.Strategy import Strategy
58
+
59
+ strategy_class = None
60
+ for attr_name in dir(module):
61
+ attr = getattr(module, attr_name)
62
+ if (
63
+ isinstance(attr, type)
64
+ and issubclass(attr, Strategy)
65
+ and attr is not Strategy
66
+ ):
67
+ strategy_class = attr
68
+ break
69
+
70
+ if strategy_class is None:
71
+ raise ValueError(f"No Strategy class found in {module_path}")
72
+
73
+ return strategy_class(config=strategy_config, api_key=api_key)
65
74
 
66
75
 
67
76
  def load_config(
68
77
  config_path: str | None = None, strategy_name: str | None = None
69
78
  ) -> StrategyJobConfig:
70
79
  """
71
- Load configuration from file or environment
80
+ Load configuration from config.json file
72
81
 
73
82
  Args:
74
- config_path: Optional path to config file
83
+ config_path: Path to config file (defaults to "config.json")
75
84
  strategy_name: Optional strategy name for per-strategy wallet lookup
76
85
 
77
86
  Returns:
78
87
  StrategyJobConfig instance
88
+
89
+ Raises:
90
+ FileNotFoundError: If config file does not exist
79
91
  """
80
- if config_path and Path(config_path).exists():
81
- logger.info(f"Loading config from {config_path}")
82
- with open(config_path) as f:
83
- config_data = json.load(f)
84
- return StrategyJobConfig.from_dict(config_data, strategy_name=strategy_name)
85
- else:
86
- logger.info("Loading config from environment variables")
87
- config = load_config_from_env()
88
- if strategy_name:
89
- config.strategy_config["_strategy_name"] = strategy_name
90
- config.__post_init__()
91
- return config
92
+ # Default to config.json if not provided
93
+ if not config_path:
94
+ config_path = "config.json"
95
+
96
+ config_file = Path(config_path)
97
+ if not config_file.exists():
98
+ raise FileNotFoundError(
99
+ f"Config file not found: {config_path}. "
100
+ "Please create config.json (see wayfinder_paths/config.example.json for template)"
101
+ )
102
+
103
+ logger.info(f"Loading config from {config_path}")
104
+ with open(config_file) as f:
105
+ config_data = json.load(f)
106
+
107
+ config = StrategyJobConfig.from_dict(config_data, strategy_name=strategy_name)
108
+ if strategy_name:
109
+ config.strategy_config["_strategy_name"] = strategy_name
110
+ config.__post_init__()
111
+ return config
92
112
 
93
113
 
94
114
  async def run_strategy(
95
115
  strategy_name: str | None = None,
96
116
  config_path: str | None = None,
97
117
  action: str = "run",
98
- manifest_path: str | None = None,
99
- simulation: bool = False,
100
118
  **kwargs,
101
119
  ):
102
120
  """
@@ -109,37 +127,14 @@ async def run_strategy(
109
127
  **kwargs: Additional arguments for the action
110
128
  """
111
129
  try:
112
- # Determine strategy name for wallet lookup BEFORE loading config
113
- # This ensures wallets are properly matched during config initialization
114
- manifest = None
115
- strategy_name_for_wallet = None
116
- if manifest_path:
117
- logger.debug(f"Loading strategy via manifest: {manifest_path}")
118
- manifest = load_manifest(manifest_path)
119
- validate_manifest(manifest)
120
- # Extract directory name from manifest path for wallet lookup
121
- # Use the directory name (strategy identifier) for wallet lookup
122
- manifest_dir = Path(manifest_path).parent
123
- strategies_dir = Path(__file__).parent / "strategies"
124
- try:
125
- # Try to get relative path - if it's under strategies_dir, use directory name
126
- rel_path = manifest_dir.relative_to(strategies_dir)
127
- strategy_name_for_wallet = (
128
- rel_path.parts[0] if rel_path.parts else manifest_dir.name
129
- )
130
- except ValueError:
131
- # Not under strategies_dir, fallback to directory name or manifest name
132
- strategy_name_for_wallet = manifest_dir.name or manifest.name
133
- else:
134
- if not strategy_name:
135
- raise ValueError("Either strategy_name or --manifest must be provided")
136
- logger.debug(f"Loading strategy by name: {strategy_name}")
137
- # Use directory name (strategy_name) directly for wallet lookup
138
- strategy_name_for_wallet = strategy_name
130
+ if not strategy_name:
131
+ raise ValueError("strategy_name is required")
132
+
133
+ logger.debug(f"Loading strategy by name: {strategy_name}")
139
134
 
140
135
  # Load configuration with strategy name for wallet lookup
141
136
  logger.debug(f"Config path provided: {config_path}")
142
- config = load_config(config_path, strategy_name=strategy_name_for_wallet)
137
+ config = load_config(config_path, strategy_name=strategy_name)
143
138
  logger.debug(
144
139
  "Loaded config: creds=%s wallets(main=%s strategy=%s)",
145
140
  "yes"
@@ -150,28 +145,12 @@ async def run_strategy(
150
145
  (config.user.strategy_wallet_address or "none"),
151
146
  )
152
147
 
153
- # Validate required configuration
154
- # No user id required; authentication is via credentials or refresh token
155
-
156
148
  # Load strategy with the enriched config
157
- if manifest_path:
158
- # Load strategy class from manifest
159
- module_path, class_name = manifest.entrypoint.rsplit(".", 1)
160
- module = __import__(module_path, fromlist=[class_name])
161
- strategy_class = getattr(module, class_name)
162
- strategy = strategy_class(
163
- config=config.strategy_config, simulation=simulation
164
- )
165
- logger.info(
166
- f"Loaded strategy from manifest: {strategy_name_for_wallet or 'unnamed'}"
167
- )
168
- else:
169
- strategy = load_strategy(
170
- strategy_name,
171
- strategy_config=config.strategy_config,
172
- simulation=simulation,
173
- )
174
- logger.info(f"Loaded strategy: {strategy.name}")
149
+ strategy = load_strategy(
150
+ strategy_name,
151
+ strategy_config=config.strategy_config,
152
+ )
153
+ logger.info(f"Loaded strategy: {strategy.name}")
175
154
 
176
155
  # Create strategy job
177
156
  strategy_job = StrategyJob(strategy, config)
@@ -248,16 +227,6 @@ async def run_strategy(
248
227
  except Exception:
249
228
  pass
250
229
 
251
- if not policies and manifest and getattr(manifest, "permissions", None):
252
- try:
253
- mpol = manifest.permissions.get("policy")
254
- if isinstance(mpol, str):
255
- policies = [mpol]
256
- elif isinstance(mpol, list):
257
- policies = [p for p in mpol if isinstance(p, str)]
258
- except Exception:
259
- pass
260
-
261
230
  seen = set()
262
231
  deduped: list[str] = []
263
232
  for p in policies:
@@ -318,15 +287,10 @@ def main():
318
287
  parser = argparse.ArgumentParser(description="Run strategy strategies")
319
288
  parser.add_argument(
320
289
  "strategy",
321
- nargs="?",
322
290
  help="Strategy to run (stablecoin_yield_strategy)",
323
291
  )
324
292
  parser.add_argument(
325
- "--manifest",
326
- help="Path to strategy manifest YAML (alternative to strategy name)",
327
- )
328
- parser.add_argument(
329
- "--config", help="Path to config file (defaults to environment variables)"
293
+ "--config", help="Path to config file (defaults to config.json)"
330
294
  )
331
295
  parser.add_argument(
332
296
  "--action",
@@ -372,11 +336,6 @@ def main():
372
336
  "--duration", type=int, help="Duration in seconds for script action"
373
337
  )
374
338
  parser.add_argument("--debug", action="store_true", help="Enable debug logging")
375
- parser.add_argument(
376
- "--simulation",
377
- action="store_true",
378
- help="Run in simulation mode (no real transactions)",
379
- )
380
339
  parser.add_argument(
381
340
  "--wallet-id",
382
341
  help="Wallet ID for policy rendering (replaces FORMAT_WALLET_ID in policies)",
@@ -395,13 +354,11 @@ def main():
395
354
  strategy_name=args.strategy,
396
355
  config_path=args.config,
397
356
  action=args.action,
398
- manifest_path=args.manifest,
399
357
  amount=args.amount,
400
358
  main_token_amount=args.main_token_amount,
401
359
  gas_token_amount=args.gas_token_amount,
402
360
  interval=args.interval,
403
361
  duration=args.duration,
404
- simulation=args.simulation,
405
362
  wallet_id=getattr(args, "wallet_id", None),
406
363
  )
407
364
  )
@@ -5,7 +5,7 @@ Create a new strategy from template and generate a dedicated wallet for it.
5
5
  This script:
6
6
  1. Copies the strategy template to a new directory
7
7
  2. Generates a wallet with label matching the strategy name
8
- 3. Updates the manifest with the strategy name
8
+ 3. Updates the strategy files with the correct names
9
9
  """
10
10
 
11
11
  import argparse
@@ -13,8 +13,6 @@ import re
13
13
  import shutil
14
14
  from pathlib import Path
15
15
 
16
- import yaml
17
-
18
16
  from wayfinder_paths.core.utils.wallets import make_random_wallet, write_wallet_to_json
19
17
 
20
18
 
@@ -29,18 +27,6 @@ def sanitize_name(name: str) -> str:
29
27
  return name.lower()
30
28
 
31
29
 
32
- def update_manifest(manifest_path: Path, strategy_name: str, entrypoint: str) -> None:
33
- """Update manifest.yaml with strategy name and entrypoint."""
34
- with open(manifest_path) as f:
35
- manifest_data = yaml.safe_load(f)
36
-
37
- manifest_data["name"] = strategy_name
38
- manifest_data["entrypoint"] = entrypoint
39
-
40
- with open(manifest_path, "w") as f:
41
- yaml.dump(manifest_data, f, default_flow_style=False, sort_keys=False)
42
-
43
-
44
30
  def update_strategy_file(strategy_path: Path, class_name: str) -> None:
45
31
  """Update strategy.py with new class name."""
46
32
  content = strategy_path.read_text()
@@ -111,7 +97,6 @@ def main():
111
97
  # Copy template files
112
98
  template_files = [
113
99
  "strategy.py",
114
- "manifest.yaml",
115
100
  "test_strategy.py",
116
101
  "examples.json",
117
102
  "README.md",
@@ -135,15 +120,6 @@ def main():
135
120
  update_strategy_file(strategy_file, class_name)
136
121
  print(f" Updated strategy.py with class name: {class_name}")
137
122
 
138
- # Generate entrypoint path
139
- entrypoint = f"strategies.{dir_name}.strategy.{class_name}"
140
-
141
- # Update manifest with name (using directory name) and entrypoint
142
- manifest_path = strategy_dir / "manifest.yaml"
143
- if manifest_path.exists():
144
- update_manifest(manifest_path, dir_name, entrypoint)
145
- print(f" Updated manifest.yaml with name: {dir_name}")
146
-
147
123
  # Generate wallet with label matching directory name (strategy identifier)
148
124
  # If wallets.json doesn't exist, create it with a main wallet first
149
125
  if not args.wallets_file.exists():
@@ -169,11 +145,10 @@ def main():
169
145
  print(f" Directory: {strategy_dir}")
170
146
  print(f" Name: {dir_name}")
171
147
  print(f" Class: {class_name}")
172
- print(f" Entrypoint: {entrypoint}")
173
148
  print(f" Wallet: {wallet['address']}")
174
149
  print("\nNext steps:")
175
150
  print(f" 1. Edit {strategy_dir / 'strategy.py'} to implement your strategy")
176
- print(f" 2. Update {strategy_dir / 'manifest.yaml'} with required adapters")
151
+ print(" 2. Add required adapters in __init__")
177
152
  print(f" 3. Test with: just test-strategy {dir_name}")
178
153
 
179
154
 
@@ -15,27 +15,6 @@ def to_keystore_json(private_key_hex: str, password: str):
15
15
  return Account.encrypt(private_key_hex, password)
16
16
 
17
17
 
18
- def write_env(rows: list[dict[str, str]], out_dir: Path) -> None:
19
- with open(out_dir / ".env.example", "w") as f:
20
- if rows:
21
- label_to_wallet = {r.get("label"): r for r in rows if r.get("label")}
22
- main_w = (
23
- label_to_wallet.get("main") or label_to_wallet.get("default") or rows[0]
24
- )
25
- strategy_w = label_to_wallet.get("strategy")
26
-
27
- f.write("RPC_URL=https://rpc.ankr.com/eth\n")
28
- # Back-compat defaults
29
- f.write(f"PRIVATE_KEY={main_w['private_key_hex']}\n")
30
- f.write(f"FROM_ADDRESS={main_w['address']}\n")
31
- # Explicit main/strategy variables
32
- f.write(f"MAIN_WALLET_ADDRESS={main_w['address']}\n")
33
- if strategy_w:
34
- f.write(f"STRATEGY_WALLET_ADDRESS={strategy_w['address']}\n")
35
- # Optional: expose strategy private key for local dev only
36
- f.write(f"PRIVATE_KEY_STRATEGY={strategy_w['private_key_hex']}\n")
37
-
38
-
39
18
  def main():
40
19
  parser = argparse.ArgumentParser(description="Generate local dev wallets")
41
20
  parser.add_argument(
@@ -48,7 +27,7 @@ def main():
48
27
  "--out-dir",
49
28
  type=Path,
50
29
  default=Path("."),
51
- help="Output directory for wallets.json (and .env/keystore)",
30
+ help="Output directory for wallets.json (and keystore files)",
52
31
  )
53
32
  parser.add_argument(
54
33
  "--keystore-password",
@@ -161,9 +140,6 @@ def main():
161
140
  ks_path.write_text(json.dumps(ks))
162
141
  index += 1
163
142
 
164
- # Convenience outputs
165
- write_env(rows, args.out_dir)
166
-
167
143
 
168
144
  if __name__ == "__main__":
169
145
  main()
@@ -14,6 +14,15 @@ def _load_wallets(path: Path) -> list[dict[str, Any]]:
14
14
  return [w for w in data if isinstance(w, dict)]
15
15
 
16
16
 
17
+ def _load_config(path: Path) -> dict[str, Any]:
18
+ if not path.exists():
19
+ return {}
20
+ data = json.loads(path.read_text(encoding="utf-8"))
21
+ if not isinstance(data, dict):
22
+ raise ValueError(f"Expected a dict in {path}")
23
+ return data
24
+
25
+
17
26
  def _find_wallet(wallets: list[dict[str, Any]], label: str) -> dict[str, Any]:
18
27
  for w in wallets:
19
28
  if w.get("label") == label:
@@ -36,6 +45,13 @@ def _get_strategy_class(strategy: str):
36
45
 
37
46
  return HyperlendStableYieldStrategy
38
47
 
48
+ if strategy == "moonwell_wsteth_loop_strategy":
49
+ from wayfinder_paths.strategies.moonwell_wsteth_loop_strategy.strategy import (
50
+ MoonwellWstethLoopStrategy,
51
+ )
52
+
53
+ return MoonwellWstethLoopStrategy
54
+
39
55
  raise ValueError(f"Unknown strategy: {strategy}")
40
56
 
41
57
 
@@ -44,19 +60,25 @@ async def _run(args: argparse.Namespace) -> int:
44
60
  wallets_path = (
45
61
  Path(args.wallets).resolve() if args.wallets else repo_root / "wallets.json"
46
62
  )
63
+ config_path = (
64
+ Path(args.config).resolve() if args.config else repo_root / "config.json"
65
+ )
66
+
47
67
  wallets = _load_wallets(wallets_path)
68
+ config = _load_config(config_path)
48
69
 
49
70
  main_wallet = _find_wallet(wallets, args.main_wallet_label)
50
71
  strategy_wallet = _find_wallet(wallets, args.strategy_wallet_label)
51
72
 
73
+ # Merge config with wallet info
74
+ strategy_config = {
75
+ "main_wallet": main_wallet,
76
+ "strategy_wallet": strategy_wallet,
77
+ **config.get("strategy", {}),
78
+ }
79
+
52
80
  strategy_class = _get_strategy_class(args.strategy)
53
- s = strategy_class(
54
- {
55
- "main_wallet": main_wallet,
56
- "strategy_wallet": strategy_wallet,
57
- },
58
- simulation=args.simulation,
59
- )
81
+ s = strategy_class(strategy_config)
60
82
 
61
83
  await s.setup()
62
84
 
@@ -94,14 +116,20 @@ def main() -> int:
94
116
  p.add_argument(
95
117
  "--strategy",
96
118
  default="basis_trading_strategy",
97
- choices=["basis_trading_strategy", "hyperlend_stable_yield_strategy"],
119
+ choices=[
120
+ "basis_trading_strategy",
121
+ "hyperlend_stable_yield_strategy",
122
+ "moonwell_wsteth_loop_strategy",
123
+ ],
98
124
  )
99
125
  p.add_argument(
100
126
  "--wallets", default=None, help="Path to wallets.json (default: repo root)"
101
127
  )
128
+ p.add_argument(
129
+ "--config", default=None, help="Path to config.json (default: repo root)"
130
+ )
102
131
  p.add_argument("--main-wallet-label", default="main")
103
132
  p.add_argument("--strategy-wallet-label", default="basis_trading_strategy")
104
- p.add_argument("--simulation", action="store_true")
105
133
 
106
134
  sub = p.add_subparsers(dest="command", required=True)
107
135
 
@@ -8,7 +8,6 @@ from __future__ import annotations
8
8
 
9
9
  import asyncio
10
10
  import json
11
- import os
12
11
  import time
13
12
  from datetime import UTC, datetime
14
13
  from decimal import ROUND_DOWN, Decimal
@@ -1007,5 +1006,4 @@ class BasisSnapshotMixin:
1007
1006
  )
1008
1007
  if isinstance(val, str) and val.strip():
1009
1008
  return val.strip()
1010
- env = os.getenv("BASIS_SNAPSHOT_PATH")
1011
- return env.strip() if isinstance(env, str) and env.strip() else None
1009
+ return None