wayfinder-paths 0.1.7__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 (149) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +399 -0
  2. wayfinder_paths/__init__.py +22 -0
  3. wayfinder_paths/abis/generic/erc20.json +383 -0
  4. wayfinder_paths/adapters/__init__.py +0 -0
  5. wayfinder_paths/adapters/balance_adapter/README.md +94 -0
  6. wayfinder_paths/adapters/balance_adapter/adapter.py +238 -0
  7. wayfinder_paths/adapters/balance_adapter/examples.json +6 -0
  8. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  9. wayfinder_paths/adapters/balance_adapter/test_adapter.py +59 -0
  10. wayfinder_paths/adapters/brap_adapter/README.md +249 -0
  11. wayfinder_paths/adapters/brap_adapter/__init__.py +7 -0
  12. wayfinder_paths/adapters/brap_adapter/adapter.py +726 -0
  13. wayfinder_paths/adapters/brap_adapter/examples.json +175 -0
  14. wayfinder_paths/adapters/brap_adapter/manifest.yaml +11 -0
  15. wayfinder_paths/adapters/brap_adapter/test_adapter.py +286 -0
  16. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
  17. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +305 -0
  18. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +10 -0
  19. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +274 -0
  20. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +18 -0
  21. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1093 -0
  22. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +549 -0
  23. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +8 -0
  24. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +1050 -0
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +126 -0
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +219 -0
  27. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +220 -0
  28. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +134 -0
  29. wayfinder_paths/adapters/ledger_adapter/README.md +145 -0
  30. wayfinder_paths/adapters/ledger_adapter/__init__.py +7 -0
  31. wayfinder_paths/adapters/ledger_adapter/adapter.py +289 -0
  32. wayfinder_paths/adapters/ledger_adapter/examples.json +137 -0
  33. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +11 -0
  34. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +205 -0
  35. wayfinder_paths/adapters/pool_adapter/README.md +206 -0
  36. wayfinder_paths/adapters/pool_adapter/__init__.py +7 -0
  37. wayfinder_paths/adapters/pool_adapter/adapter.py +282 -0
  38. wayfinder_paths/adapters/pool_adapter/examples.json +143 -0
  39. wayfinder_paths/adapters/pool_adapter/manifest.yaml +10 -0
  40. wayfinder_paths/adapters/pool_adapter/test_adapter.py +220 -0
  41. wayfinder_paths/adapters/token_adapter/README.md +101 -0
  42. wayfinder_paths/adapters/token_adapter/__init__.py +3 -0
  43. wayfinder_paths/adapters/token_adapter/adapter.py +96 -0
  44. wayfinder_paths/adapters/token_adapter/examples.json +26 -0
  45. wayfinder_paths/adapters/token_adapter/manifest.yaml +6 -0
  46. wayfinder_paths/adapters/token_adapter/test_adapter.py +125 -0
  47. wayfinder_paths/config.example.json +22 -0
  48. wayfinder_paths/conftest.py +31 -0
  49. wayfinder_paths/core/__init__.py +18 -0
  50. wayfinder_paths/core/adapters/BaseAdapter.py +65 -0
  51. wayfinder_paths/core/adapters/__init__.py +5 -0
  52. wayfinder_paths/core/adapters/base.py +5 -0
  53. wayfinder_paths/core/adapters/models.py +46 -0
  54. wayfinder_paths/core/analytics/__init__.py +11 -0
  55. wayfinder_paths/core/analytics/bootstrap.py +57 -0
  56. wayfinder_paths/core/analytics/stats.py +48 -0
  57. wayfinder_paths/core/analytics/test_analytics.py +170 -0
  58. wayfinder_paths/core/clients/AuthClient.py +83 -0
  59. wayfinder_paths/core/clients/BRAPClient.py +109 -0
  60. wayfinder_paths/core/clients/ClientManager.py +210 -0
  61. wayfinder_paths/core/clients/HyperlendClient.py +192 -0
  62. wayfinder_paths/core/clients/LedgerClient.py +443 -0
  63. wayfinder_paths/core/clients/PoolClient.py +128 -0
  64. wayfinder_paths/core/clients/SimulationClient.py +192 -0
  65. wayfinder_paths/core/clients/TokenClient.py +89 -0
  66. wayfinder_paths/core/clients/TransactionClient.py +63 -0
  67. wayfinder_paths/core/clients/WalletClient.py +94 -0
  68. wayfinder_paths/core/clients/WayfinderClient.py +269 -0
  69. wayfinder_paths/core/clients/__init__.py +48 -0
  70. wayfinder_paths/core/clients/protocols.py +392 -0
  71. wayfinder_paths/core/clients/sdk_example.py +110 -0
  72. wayfinder_paths/core/config.py +458 -0
  73. wayfinder_paths/core/constants/__init__.py +26 -0
  74. wayfinder_paths/core/constants/base.py +42 -0
  75. wayfinder_paths/core/constants/erc20_abi.py +118 -0
  76. wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
  77. wayfinder_paths/core/engine/StrategyJob.py +188 -0
  78. wayfinder_paths/core/engine/__init__.py +5 -0
  79. wayfinder_paths/core/engine/manifest.py +97 -0
  80. wayfinder_paths/core/services/__init__.py +0 -0
  81. wayfinder_paths/core/services/base.py +179 -0
  82. wayfinder_paths/core/services/local_evm_txn.py +430 -0
  83. wayfinder_paths/core/services/local_token_txn.py +231 -0
  84. wayfinder_paths/core/services/web3_service.py +45 -0
  85. wayfinder_paths/core/settings.py +61 -0
  86. wayfinder_paths/core/strategies/Strategy.py +280 -0
  87. wayfinder_paths/core/strategies/__init__.py +5 -0
  88. wayfinder_paths/core/strategies/base.py +7 -0
  89. wayfinder_paths/core/strategies/descriptors.py +81 -0
  90. wayfinder_paths/core/utils/__init__.py +1 -0
  91. wayfinder_paths/core/utils/evm_helpers.py +206 -0
  92. wayfinder_paths/core/utils/wallets.py +77 -0
  93. wayfinder_paths/core/wallets/README.md +91 -0
  94. wayfinder_paths/core/wallets/WalletManager.py +56 -0
  95. wayfinder_paths/core/wallets/__init__.py +7 -0
  96. wayfinder_paths/policies/enso.py +17 -0
  97. wayfinder_paths/policies/erc20.py +34 -0
  98. wayfinder_paths/policies/evm.py +21 -0
  99. wayfinder_paths/policies/hyper_evm.py +19 -0
  100. wayfinder_paths/policies/hyperlend.py +12 -0
  101. wayfinder_paths/policies/hyperliquid.py +30 -0
  102. wayfinder_paths/policies/moonwell.py +54 -0
  103. wayfinder_paths/policies/prjx.py +30 -0
  104. wayfinder_paths/policies/util.py +27 -0
  105. wayfinder_paths/run_strategy.py +411 -0
  106. wayfinder_paths/scripts/__init__.py +0 -0
  107. wayfinder_paths/scripts/create_strategy.py +181 -0
  108. wayfinder_paths/scripts/make_wallets.py +169 -0
  109. wayfinder_paths/scripts/run_strategy.py +124 -0
  110. wayfinder_paths/scripts/validate_manifests.py +213 -0
  111. wayfinder_paths/strategies/__init__.py +0 -0
  112. wayfinder_paths/strategies/basis_trading_strategy/README.md +213 -0
  113. wayfinder_paths/strategies/basis_trading_strategy/__init__.py +3 -0
  114. wayfinder_paths/strategies/basis_trading_strategy/constants.py +1 -0
  115. wayfinder_paths/strategies/basis_trading_strategy/examples.json +16 -0
  116. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +23 -0
  117. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1011 -0
  118. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +4522 -0
  119. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +727 -0
  120. wayfinder_paths/strategies/basis_trading_strategy/types.py +39 -0
  121. wayfinder_paths/strategies/config.py +85 -0
  122. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +100 -0
  123. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +8 -0
  124. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
  125. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +2270 -0
  126. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +352 -0
  127. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +96 -0
  128. wayfinder_paths/strategies/stablecoin_yield_strategy/examples.json +17 -0
  129. wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
  130. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1810 -0
  131. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +520 -0
  132. wayfinder_paths/templates/adapter/README.md +105 -0
  133. wayfinder_paths/templates/adapter/adapter.py +26 -0
  134. wayfinder_paths/templates/adapter/examples.json +8 -0
  135. wayfinder_paths/templates/adapter/manifest.yaml +6 -0
  136. wayfinder_paths/templates/adapter/test_adapter.py +49 -0
  137. wayfinder_paths/templates/strategy/README.md +153 -0
  138. wayfinder_paths/templates/strategy/examples.json +11 -0
  139. wayfinder_paths/templates/strategy/manifest.yaml +8 -0
  140. wayfinder_paths/templates/strategy/strategy.py +57 -0
  141. wayfinder_paths/templates/strategy/test_strategy.py +197 -0
  142. wayfinder_paths/tests/__init__.py +0 -0
  143. wayfinder_paths/tests/test_smoke_manifest.py +48 -0
  144. wayfinder_paths/tests/test_test_coverage.py +212 -0
  145. wayfinder_paths/tests/test_utils.py +64 -0
  146. wayfinder_paths-0.1.7.dist-info/LICENSE +21 -0
  147. wayfinder_paths-0.1.7.dist-info/METADATA +777 -0
  148. wayfinder_paths-0.1.7.dist-info/RECORD +149 -0
  149. wayfinder_paths-0.1.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,54 @@
1
+ from wayfinder_paths.policies.util import allow_functions
2
+
3
+ WETH = "0x4200000000000000000000000000000000000006"
4
+
5
+ M_USDC = "0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22"
6
+ M_WETH = "0x628ff693426583D9a7FB391E54366292F509D457"
7
+ M_WSTETH = "0x627Fe393Bc6EdDA28e99AE648fD6fF362514304b"
8
+
9
+ COMPTROLLER = "0xfbb21d0380bee3312b33c4353c8936a0f13ef26c"
10
+
11
+
12
+ async def weth_deposit():
13
+ return await allow_functions(
14
+ policy_name="Allow WETH Deposit",
15
+ abi_chain_id=8453,
16
+ address=WETH,
17
+ function_names=["deposit"],
18
+ )
19
+
20
+
21
+ async def musdc_mint_or_approve_or_redeem():
22
+ return await allow_functions(
23
+ policy_name="Allow MUSDC Mint or Approve or Redeem",
24
+ abi_chain_id=8453,
25
+ address=M_USDC,
26
+ function_names=["mint", "approve", "redeem"],
27
+ )
28
+
29
+
30
+ async def mweth_approve_or_borrow_or_repay():
31
+ return await allow_functions(
32
+ policy_name="Allow MWETH Approve or Borrow or Repay",
33
+ abi_chain_id=8453,
34
+ address=M_WETH,
35
+ function_names=["approve", "borrow", "repayBorrow"],
36
+ )
37
+
38
+
39
+ async def mwsteth_approve_or_mint_or_redeem():
40
+ return await allow_functions(
41
+ policy_name="Allow MWSTETH Approve or Mint or Redeem",
42
+ abi_chain_id=8453,
43
+ address=M_WSTETH,
44
+ function_names=["approve", "mint", "redeem"],
45
+ )
46
+
47
+
48
+ async def moonwell_comptroller_enter_markets_or_claim_rewards():
49
+ return await allow_functions(
50
+ policy_name="Allow Moonwell Comptroller Enter Markets or Claim Rewards",
51
+ abi_chain_id=8453,
52
+ address=COMPTROLLER,
53
+ function_names=["enterMarkets", "claimReward"],
54
+ )
@@ -0,0 +1,30 @@
1
+ from wayfinder_paths.policies.util import allow_functions
2
+
3
+ PRJX_ROUTER = "0x1ebdfc75ffe3ba3de61e7138a3e8706ac841af9b"
4
+ PRJX_NPM = "0xeAd19AE861c29bBb2101E834922B2FEee69B9091"
5
+
6
+
7
+ async def prjx_swap():
8
+ return await allow_functions(
9
+ policy_name="Allow PRJX Swap",
10
+ abi_chain_id=999,
11
+ address=PRJX_ROUTER,
12
+ function_names=[
13
+ "exactInput",
14
+ "exactInputSingle",
15
+ "exactOutput",
16
+ "exactOutputSingle",
17
+ ],
18
+ )
19
+
20
+
21
+ async def prjx_npm():
22
+ return await allow_functions(
23
+ policy_name="Allow PRJX NPM",
24
+ abi_chain_id=999,
25
+ address=PRJX_NPM,
26
+ function_names=[
27
+ "increaseLiquidity",
28
+ "decreaseLiquidity",
29
+ ],
30
+ )
@@ -0,0 +1,27 @@
1
+ from wayfinder_paths.core.utils.evm_helpers import get_abi_filtered
2
+
3
+
4
+ async def allow_functions(
5
+ policy_name: str, abi_chain_id: int, address: str, function_names: list[str]
6
+ ):
7
+ # Note: ChainID is just for fetching ABI, doesn't appear in the final policy. Doesn't bind a strict chain.
8
+ return {
9
+ "name": policy_name,
10
+ "method": "eth_signTransaction",
11
+ "action": "ALLOW",
12
+ "conditions": [
13
+ {
14
+ "field_source": "ethereum_transaction",
15
+ "field": "to",
16
+ "operator": "eq",
17
+ "value": address,
18
+ },
19
+ {
20
+ "field_source": "ethereum_calldata",
21
+ "field": "function_name",
22
+ "abi": await get_abi_filtered(abi_chain_id, address, function_names),
23
+ "operator": "in",
24
+ "value": function_names,
25
+ },
26
+ ],
27
+ }
@@ -0,0 +1,411 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Strategy Runner
4
+ Main entry point for running strategies locally
5
+ """
6
+
7
+ import argparse
8
+ import asyncio
9
+ import json
10
+ import sys
11
+ from pathlib import Path
12
+
13
+ from loguru import logger
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
17
+ from wayfinder_paths.core.engine.StrategyJob import StrategyJob
18
+
19
+
20
+ def load_strategy(
21
+ strategy_name: str,
22
+ *,
23
+ strategy_config: dict | None = None,
24
+ simulation: bool = False,
25
+ api_key: str | None = None,
26
+ ):
27
+ """
28
+ Dynamically load a strategy by name using its manifest
29
+
30
+ Args:
31
+ strategy_name: Name of the strategy to load (directory name in strategies/)
32
+ strategy_config: Configuration dict for the strategy
33
+ simulation: Enable simulation mode for testing
34
+ api_key: Optional API key for service account authentication
35
+
36
+ Returns:
37
+ Strategy instance
38
+ """
39
+ # Find strategy manifest by scanning for manifest.yaml in the strategy directory
40
+ strategies_dir = Path(__file__).parent / "strategies"
41
+ strategy_dir = strategies_dir / strategy_name
42
+ manifest_path = strategy_dir / "manifest.yaml"
43
+
44
+ if not manifest_path.exists():
45
+ # List available strategies for better error message
46
+ available = []
47
+ if strategies_dir.exists():
48
+ for path in strategies_dir.iterdir():
49
+ if path.is_dir() and (path / "manifest.yaml").exists():
50
+ available.append(path.name)
51
+ available_str = ", ".join(available) if available else "none"
52
+ raise ValueError(
53
+ f"Unknown strategy: {strategy_name}. Available strategies: {available_str}"
54
+ )
55
+
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)
61
+
62
+ return strategy_class(
63
+ config=strategy_config, simulation=simulation, api_key=api_key
64
+ )
65
+
66
+
67
+ def load_config(
68
+ config_path: str | None = None, strategy_name: str | None = None
69
+ ) -> StrategyJobConfig:
70
+ """
71
+ Load configuration from file or environment
72
+
73
+ Args:
74
+ config_path: Optional path to config file
75
+ strategy_name: Optional strategy name for per-strategy wallet lookup
76
+
77
+ Returns:
78
+ StrategyJobConfig instance
79
+ """
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
+
93
+
94
+ async def run_strategy(
95
+ strategy_name: str | None = None,
96
+ config_path: str | None = None,
97
+ action: str = "run",
98
+ manifest_path: str | None = None,
99
+ simulation: bool = False,
100
+ **kwargs,
101
+ ):
102
+ """
103
+ Run a strategy
104
+
105
+ Args:
106
+ strategy_name: Name of the strategy to run
107
+ config_path: Optional path to config file
108
+ action: Action to perform (run, deposit, withdraw, status)
109
+ **kwargs: Additional arguments for the action
110
+ """
111
+ 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
139
+
140
+ # Load configuration with strategy name for wallet lookup
141
+ logger.debug(f"Config path provided: {config_path}")
142
+ config = load_config(config_path, strategy_name=strategy_name_for_wallet)
143
+ logger.debug(
144
+ "Loaded config: creds=%s wallets(main=%s strategy=%s)",
145
+ "yes"
146
+ if (config.user.username and config.user.password)
147
+ or config.user.refresh_token
148
+ else "no",
149
+ (config.user.main_wallet_address or "none"),
150
+ (config.user.strategy_wallet_address or "none"),
151
+ )
152
+
153
+ # Validate required configuration
154
+ # No user id required; authentication is via credentials or refresh token
155
+
156
+ # 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}")
175
+
176
+ # Create strategy job
177
+ strategy_job = StrategyJob(strategy, config)
178
+
179
+ # Setup strategy job
180
+ logger.info("Setting up strategy job...")
181
+ logger.debug(
182
+ "Auth mode: %s",
183
+ "credentials"
184
+ if (config.user.username and config.user.password)
185
+ or config.user.refresh_token
186
+ else "missing",
187
+ )
188
+ await strategy_job.setup()
189
+
190
+ # Execute action
191
+ if action == "run":
192
+ logger.info("Starting continuous execution...")
193
+ await strategy_job.run_continuous(interval_seconds=kwargs.get("interval"))
194
+
195
+ elif action == "deposit":
196
+ main_token_amount = kwargs.get("main_token_amount")
197
+ gas_token_amount = kwargs.get("gas_token_amount")
198
+
199
+ if main_token_amount is None and gas_token_amount is None:
200
+ raise ValueError(
201
+ "Either main token amount or gas token amount required for deposit (use --main-token-amount and/or --gas-token-amount)"
202
+ )
203
+
204
+ # Default to 0.0 if not provided
205
+ if main_token_amount is None:
206
+ main_token_amount = 0.0
207
+ if gas_token_amount is None:
208
+ gas_token_amount = 0.0
209
+
210
+ result = await strategy_job.execute_strategy(
211
+ "deposit",
212
+ main_token_amount=main_token_amount,
213
+ gas_token_amount=gas_token_amount,
214
+ )
215
+ logger.info(f"Deposit result: {result}")
216
+
217
+ elif action == "withdraw":
218
+ amount = kwargs.get("amount")
219
+ result = await strategy_job.execute_strategy("withdraw", amount=amount)
220
+ logger.info(f"Withdraw result: {result}")
221
+
222
+ elif action == "status":
223
+ result = await strategy_job.execute_strategy("status")
224
+ logger.info(f"Status: {json.dumps(result, indent=2)}")
225
+
226
+ elif action == "update":
227
+ result = await strategy_job.execute_strategy("update")
228
+ logger.info(f"Update result: {result}")
229
+
230
+ elif action == "partial-liquidate":
231
+ usd_value = kwargs.get("amount")
232
+ if not usd_value:
233
+ raise ValueError("Amount (USD value) required for partial-liquidate")
234
+ result = await strategy_job.execute_strategy(
235
+ "partial_liquidate", usd_value=usd_value
236
+ )
237
+ logger.info(f"Partial liquidation result: {result}")
238
+
239
+ elif action == "policy":
240
+ policies: list[str] = []
241
+
242
+ try:
243
+ spols = getattr(strategy, "policies", None)
244
+ if callable(spols):
245
+ result = spols() # type: ignore[misc]
246
+ if isinstance(result, list) and result:
247
+ policies = [p for p in result if isinstance(p, str)]
248
+ except Exception:
249
+ pass
250
+
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
+ seen = set()
262
+ deduped: list[str] = []
263
+ for p in policies:
264
+ if p not in seen:
265
+ seen.add(p)
266
+ deduped.append(p)
267
+
268
+ # Get wallet_id from CLI arg, config, or leave as None
269
+ wallet_id = kwargs.get("wallet_id")
270
+ if not wallet_id:
271
+ wallet_id = config.strategy_config.get("wallet_id")
272
+ if not wallet_id:
273
+ wallet_id = config.system.wallet_id
274
+
275
+ # Render policies with wallet_id if available
276
+ if wallet_id:
277
+ rendered = [
278
+ p.replace("FORMAT_WALLET_ID", str(wallet_id)) for p in deduped
279
+ ]
280
+ else:
281
+ rendered = deduped
282
+ logger.info(
283
+ "Policy rendering without wallet_id - policies contain FORMAT_WALLET_ID placeholder"
284
+ )
285
+
286
+ logger.info(json.dumps({"policies": rendered}, indent=2))
287
+
288
+ elif action == "script":
289
+ duration = kwargs.get("duration") or 300
290
+ logger.info(f"Running script mode for {duration}s...")
291
+ task = asyncio.create_task(
292
+ strategy_job.run_continuous(
293
+ interval_seconds=kwargs.get("interval") or 60
294
+ )
295
+ )
296
+ await asyncio.sleep(duration)
297
+ task.cancel()
298
+ try:
299
+ await task
300
+ except asyncio.CancelledError:
301
+ logger.info("Script mode execution completed")
302
+
303
+ else:
304
+ raise ValueError(f"Unknown action: {action}")
305
+
306
+ except KeyboardInterrupt:
307
+ logger.info("Shutting down...")
308
+ except Exception as e:
309
+ logger.error(f"Error: {e}")
310
+ sys.exit(1)
311
+ finally:
312
+ if "strategy_job" in locals():
313
+ await strategy_job.stop()
314
+
315
+
316
+ def main():
317
+ """Main entry point"""
318
+ parser = argparse.ArgumentParser(description="Run strategy strategies")
319
+ parser.add_argument(
320
+ "strategy",
321
+ nargs="?",
322
+ help="Strategy to run (stablecoin_yield_strategy)",
323
+ )
324
+ 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)"
330
+ )
331
+ parser.add_argument(
332
+ "--action",
333
+ default="run",
334
+ choices=[
335
+ "run",
336
+ "deposit",
337
+ "withdraw",
338
+ "status",
339
+ "update",
340
+ "policy",
341
+ "script",
342
+ "partial-liquidate",
343
+ ],
344
+ help="Action to perform (default: run)",
345
+ )
346
+ parser.add_argument(
347
+ "--amount",
348
+ type=float,
349
+ help="Amount for withdraw/partial-liquidate actions",
350
+ )
351
+ parser.add_argument(
352
+ "--main-token-amount",
353
+ "--main_token_amount",
354
+ type=float,
355
+ dest="main_token_amount",
356
+ help="Main token amount for deposit action",
357
+ )
358
+ parser.add_argument(
359
+ "--gas-token-amount",
360
+ "--gas_token_amount",
361
+ type=float,
362
+ dest="gas_token_amount",
363
+ default=0.0,
364
+ help="Gas token amount for deposit action (default: 0.0)",
365
+ )
366
+ parser.add_argument(
367
+ "--interval",
368
+ type=int,
369
+ help="Update interval in seconds for continuous/script modes",
370
+ )
371
+ parser.add_argument(
372
+ "--duration", type=int, help="Duration in seconds for script action"
373
+ )
374
+ 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
+ parser.add_argument(
381
+ "--wallet-id",
382
+ help="Wallet ID for policy rendering (replaces FORMAT_WALLET_ID in policies)",
383
+ )
384
+
385
+ args = parser.parse_args()
386
+
387
+ # Configure logging
388
+ log_level = "DEBUG" if args.debug else "INFO"
389
+ logger.remove()
390
+ logger.add(sys.stderr, level=log_level)
391
+
392
+ # Run strategy
393
+ asyncio.run(
394
+ run_strategy(
395
+ strategy_name=args.strategy,
396
+ config_path=args.config,
397
+ action=args.action,
398
+ manifest_path=args.manifest,
399
+ amount=args.amount,
400
+ main_token_amount=args.main_token_amount,
401
+ gas_token_amount=args.gas_token_amount,
402
+ interval=args.interval,
403
+ duration=args.duration,
404
+ simulation=args.simulation,
405
+ wallet_id=getattr(args, "wallet_id", None),
406
+ )
407
+ )
408
+
409
+
410
+ if __name__ == "__main__":
411
+ main()
File without changes