wayfinder-paths 0.1.19__py3-none-any.whl → 0.1.20__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.
- wayfinder_paths/__init__.py +0 -2
- wayfinder_paths/adapters/balance_adapter/README.md +59 -45
- wayfinder_paths/adapters/balance_adapter/adapter.py +0 -21
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -14
- wayfinder_paths/adapters/brap_adapter/README.md +61 -184
- wayfinder_paths/adapters/brap_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/brap_adapter/adapter.py +0 -147
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +0 -15
- wayfinder_paths/adapters/hyperlend_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -9
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +0 -17
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +3 -312
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +1 -71
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +0 -57
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +0 -17
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +2 -42
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +1 -9
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +15 -47
- wayfinder_paths/adapters/hyperliquid_adapter/utils.py +0 -7
- wayfinder_paths/adapters/ledger_adapter/README.md +54 -74
- wayfinder_paths/adapters/ledger_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/ledger_adapter/adapter.py +0 -106
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +0 -12
- wayfinder_paths/adapters/moonwell_adapter/README.md +67 -106
- wayfinder_paths/adapters/moonwell_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +9 -121
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +84 -83
- wayfinder_paths/adapters/pool_adapter/README.md +30 -51
- wayfinder_paths/adapters/pool_adapter/__init__.py +0 -4
- wayfinder_paths/adapters/pool_adapter/adapter.py +0 -19
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -8
- wayfinder_paths/adapters/token_adapter/README.md +41 -49
- wayfinder_paths/adapters/token_adapter/adapter.py +0 -32
- wayfinder_paths/adapters/token_adapter/test_adapter.py +1 -12
- wayfinder_paths/conftest.py +0 -8
- wayfinder_paths/core/__init__.py +0 -2
- wayfinder_paths/core/adapters/BaseAdapter.py +0 -22
- wayfinder_paths/core/adapters/__init__.py +0 -5
- wayfinder_paths/core/adapters/models.py +0 -5
- wayfinder_paths/core/analytics/__init__.py +0 -2
- wayfinder_paths/core/analytics/bootstrap.py +0 -16
- wayfinder_paths/core/analytics/stats.py +0 -7
- wayfinder_paths/core/analytics/test_analytics.py +5 -34
- wayfinder_paths/core/clients/BRAPClient.py +0 -35
- wayfinder_paths/core/clients/ClientManager.py +0 -51
- wayfinder_paths/core/clients/HyperlendClient.py +0 -77
- wayfinder_paths/core/clients/LedgerClient.py +2 -122
- wayfinder_paths/core/clients/PoolClient.py +0 -2
- wayfinder_paths/core/clients/TokenClient.py +0 -39
- wayfinder_paths/core/clients/WalletClient.py +0 -15
- wayfinder_paths/core/clients/WayfinderClient.py +0 -24
- wayfinder_paths/core/clients/__init__.py +0 -4
- wayfinder_paths/core/clients/protocols.py +25 -98
- wayfinder_paths/core/config.py +0 -24
- wayfinder_paths/core/constants/__init__.py +0 -7
- wayfinder_paths/core/constants/base.py +2 -9
- wayfinder_paths/core/constants/erc20_abi.py +0 -5
- wayfinder_paths/core/constants/hyperlend_abi.py +0 -7
- wayfinder_paths/core/constants/moonwell_abi.py +0 -35
- wayfinder_paths/core/engine/StrategyJob.py +0 -32
- wayfinder_paths/core/strategies/Strategy.py +0 -99
- wayfinder_paths/core/strategies/__init__.py +0 -2
- wayfinder_paths/core/utils/__init__.py +0 -1
- wayfinder_paths/core/utils/erc20_service.py +0 -1
- wayfinder_paths/core/utils/evm_helpers.py +0 -50
- wayfinder_paths/core/utils/transaction.py +0 -1
- wayfinder_paths/run_strategy.py +0 -46
- wayfinder_paths/scripts/create_strategy.py +0 -17
- wayfinder_paths/scripts/make_wallets.py +1 -4
- wayfinder_paths/strategies/basis_trading_strategy/README.md +71 -163
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +0 -24
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +36 -400
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +15 -64
- wayfinder_paths/strategies/basis_trading_strategy/types.py +0 -4
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +65 -56
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +4 -27
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -10
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +71 -72
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +23 -227
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +120 -113
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +64 -59
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +4 -44
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +2 -35
- wayfinder_paths/templates/adapter/README.md +107 -46
- wayfinder_paths/templates/adapter/adapter.py +0 -9
- wayfinder_paths/templates/adapter/test_adapter.py +0 -19
- wayfinder_paths/templates/strategy/README.md +113 -59
- wayfinder_paths/templates/strategy/strategy.py +0 -22
- wayfinder_paths/templates/strategy/test_strategy.py +0 -28
- wayfinder_paths/tests/test_test_coverage.py +2 -12
- wayfinder_paths/tests/test_utils.py +1 -31
- wayfinder_paths-0.1.20.dist-info/METADATA +355 -0
- wayfinder_paths-0.1.20.dist-info/RECORD +129 -0
- wayfinder_paths/core/adapters/base.py +0 -5
- wayfinder_paths-0.1.19.dist-info/METADATA +0 -592
- wayfinder_paths-0.1.19.dist-info/RECORD +0 -130
- {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.20.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.20.dist-info}/WHEEL +0 -0
|
@@ -16,15 +16,6 @@ class StrategyJob:
|
|
|
16
16
|
clients: dict[str, Any] | None = None,
|
|
17
17
|
skip_auth: bool = False,
|
|
18
18
|
):
|
|
19
|
-
"""
|
|
20
|
-
Initialize a StrategyJob.
|
|
21
|
-
|
|
22
|
-
Args:
|
|
23
|
-
strategy: The strategy to execute.
|
|
24
|
-
config: Strategy job configuration.
|
|
25
|
-
clients: Optional dict of pre-instantiated clients to inject directly.
|
|
26
|
-
skip_auth: If True, skips authentication (for SDK usage).
|
|
27
|
-
"""
|
|
28
19
|
self.strategy = strategy
|
|
29
20
|
self.config = config
|
|
30
21
|
|
|
@@ -32,18 +23,12 @@ class StrategyJob:
|
|
|
32
23
|
self.clients = ClientManager(clients=clients, skip_auth=skip_auth)
|
|
33
24
|
|
|
34
25
|
def _setup_strategy(self):
|
|
35
|
-
"""Setup the strategy instance"""
|
|
36
26
|
if not self.strategy:
|
|
37
27
|
raise ValueError("No strategy provided to StrategyJob")
|
|
38
28
|
|
|
39
29
|
self.strategy.log = self.log
|
|
40
30
|
|
|
41
31
|
async def setup(self):
|
|
42
|
-
"""
|
|
43
|
-
Initialize the strategy job and strategy.
|
|
44
|
-
|
|
45
|
-
Sets up authentication and initializes the strategy with merged configuration.
|
|
46
|
-
"""
|
|
47
32
|
self._setup_strategy()
|
|
48
33
|
|
|
49
34
|
# Ensure API key is set for API calls
|
|
@@ -62,7 +47,6 @@ class StrategyJob:
|
|
|
62
47
|
await self.strategy.setup()
|
|
63
48
|
|
|
64
49
|
async def execute_strategy(self, action: str, **kwargs) -> dict[str, Any]:
|
|
65
|
-
"""Execute a strategy action (deposit, withdraw, update, status, exit, partial_liquidate)"""
|
|
66
50
|
try:
|
|
67
51
|
if action == "deposit":
|
|
68
52
|
result = await self.strategy.deposit(**kwargs)
|
|
@@ -96,7 +80,6 @@ class StrategyJob:
|
|
|
96
80
|
return {"success": False, "error": str(e)}
|
|
97
81
|
|
|
98
82
|
async def run_continuous(self, interval_seconds: int | None = None):
|
|
99
|
-
"""Run the strategy continuously at specified intervals"""
|
|
100
83
|
interval = interval_seconds or self.config.system.update_interval
|
|
101
84
|
logger.info(
|
|
102
85
|
f"Starting continuous execution for strategy: {self.strategy.name} with interval {interval}s"
|
|
@@ -115,27 +98,12 @@ class StrategyJob:
|
|
|
115
98
|
await asyncio.sleep(interval)
|
|
116
99
|
|
|
117
100
|
async def log(self, msg: str):
|
|
118
|
-
"""Log messages for the job"""
|
|
119
101
|
logger.info(f"Job {self.job_id}: {msg}")
|
|
120
102
|
|
|
121
103
|
async def handle_error(self, error_data: dict[str, Any]) -> None:
|
|
122
|
-
"""
|
|
123
|
-
Handle errors that occur during strategy execution.
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
error_data: Dictionary containing error information. Expected keys:
|
|
127
|
-
- error: Error message or exception string
|
|
128
|
-
- action: Strategy action that failed (e.g., "deposit", "update")
|
|
129
|
-
|
|
130
|
-
Note:
|
|
131
|
-
Base implementation is a no-op. Subclasses or external systems
|
|
132
|
-
can override this method to implement custom error handling,
|
|
133
|
-
logging, alerting, or recovery logic.
|
|
134
|
-
"""
|
|
135
104
|
pass
|
|
136
105
|
|
|
137
106
|
async def stop(self):
|
|
138
|
-
"""Stop the strategy job and cleanup"""
|
|
139
107
|
if hasattr(self.strategy, "stop"):
|
|
140
108
|
await self.strategy.stop()
|
|
141
109
|
|
|
@@ -23,8 +23,6 @@ StatusTuple = tuple[bool, str]
|
|
|
23
23
|
|
|
24
24
|
|
|
25
25
|
class WalletConfig(TypedDict, total=False):
|
|
26
|
-
"""Wallet configuration structure - allows additional fields for flexibility"""
|
|
27
|
-
|
|
28
26
|
address: str
|
|
29
27
|
private_key: str | None
|
|
30
28
|
private_key_hex: str | None
|
|
@@ -32,8 +30,6 @@ class WalletConfig(TypedDict, total=False):
|
|
|
32
30
|
|
|
33
31
|
|
|
34
32
|
class StrategyConfig(TypedDict, total=False):
|
|
35
|
-
"""Base strategy configuration structure - allows additional fields for flexibility"""
|
|
36
|
-
|
|
37
33
|
main_wallet: WalletConfig | None
|
|
38
34
|
strategy_wallet: WalletConfig | None
|
|
39
35
|
wallet_type: str | None
|
|
@@ -68,19 +64,15 @@ class Strategy(ABC):
|
|
|
68
64
|
self.strategy_wallet_signing_callback = strategy_wallet_signing_callback
|
|
69
65
|
|
|
70
66
|
async def setup(self) -> None:
|
|
71
|
-
"""Initialize strategy-specific setup after construction"""
|
|
72
67
|
pass
|
|
73
68
|
|
|
74
69
|
async def log(self, msg: str) -> None:
|
|
75
|
-
"""Log messages - can be overridden by subclasses"""
|
|
76
70
|
self.logger.info(msg)
|
|
77
71
|
|
|
78
72
|
async def quote(self) -> None:
|
|
79
|
-
"""Get quotes for potential trades - optional for strategies"""
|
|
80
73
|
pass
|
|
81
74
|
|
|
82
75
|
def _get_strategy_wallet_address(self) -> str:
|
|
83
|
-
"""Get strategy wallet address with validation."""
|
|
84
76
|
strategy_wallet = self.config.get("strategy_wallet")
|
|
85
77
|
if not strategy_wallet or not isinstance(strategy_wallet, dict):
|
|
86
78
|
raise ValueError("strategy_wallet not configured in strategy config")
|
|
@@ -90,7 +82,6 @@ class Strategy(ABC):
|
|
|
90
82
|
return str(address)
|
|
91
83
|
|
|
92
84
|
def _get_main_wallet_address(self) -> str:
|
|
93
|
-
"""Get main wallet address with validation."""
|
|
94
85
|
main_wallet = self.config.get("main_wallet")
|
|
95
86
|
if not main_wallet or not isinstance(main_wallet, dict):
|
|
96
87
|
raise ValueError("main_wallet not configured in strategy config")
|
|
@@ -101,37 +92,9 @@ class Strategy(ABC):
|
|
|
101
92
|
|
|
102
93
|
@abstractmethod
|
|
103
94
|
async def deposit(self, **kwargs) -> StatusTuple:
|
|
104
|
-
"""
|
|
105
|
-
Deposit funds into the strategy.
|
|
106
|
-
|
|
107
|
-
Args:
|
|
108
|
-
**kwargs: Strategy-specific deposit parameters. Common parameters include:
|
|
109
|
-
- main_token_amount: Amount of main token to deposit (float)
|
|
110
|
-
- gas_token_amount: Amount of gas token to deposit (float)
|
|
111
|
-
|
|
112
|
-
Returns:
|
|
113
|
-
Tuple of (success: bool, message: str)
|
|
114
|
-
|
|
115
|
-
Raises:
|
|
116
|
-
ValueError: If required parameters are missing or invalid.
|
|
117
|
-
"""
|
|
118
95
|
pass
|
|
119
96
|
|
|
120
97
|
async def withdraw(self, **kwargs) -> StatusTuple:
|
|
121
|
-
"""
|
|
122
|
-
Withdraw funds from the strategy.
|
|
123
|
-
Default implementation unwinds all operations.
|
|
124
|
-
|
|
125
|
-
Args:
|
|
126
|
-
**kwargs: Strategy-specific withdrawal parameters (optional).
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
Tuple of (success: bool, message: str)
|
|
130
|
-
|
|
131
|
-
Note:
|
|
132
|
-
Subclasses may override this method to add validation or custom
|
|
133
|
-
withdrawal logic. The base implementation unwinds all ledger operations.
|
|
134
|
-
"""
|
|
135
98
|
if hasattr(self, "ledger_adapter") and self.ledger_adapter:
|
|
136
99
|
while self.ledger_adapter.positions.operations:
|
|
137
100
|
node = self.ledger_adapter.positions.operations[-1]
|
|
@@ -146,46 +109,21 @@ class Strategy(ABC):
|
|
|
146
109
|
|
|
147
110
|
@abstractmethod
|
|
148
111
|
async def update(self) -> StatusTuple:
|
|
149
|
-
"""
|
|
150
|
-
Deploy funds to protocols (no main wallet access).
|
|
151
|
-
Called after deposit() has transferred assets to strategy wallet.
|
|
152
|
-
|
|
153
|
-
Returns:
|
|
154
|
-
Tuple of (success: bool, message: str)
|
|
155
|
-
"""
|
|
156
112
|
pass
|
|
157
113
|
|
|
158
114
|
@abstractmethod
|
|
159
115
|
async def exit(self, **kwargs) -> StatusTuple:
|
|
160
|
-
"""
|
|
161
|
-
Transfer funds from strategy wallet to main wallet.
|
|
162
|
-
Called after withdraw() has liquidated all positions.
|
|
163
|
-
|
|
164
|
-
Returns:
|
|
165
|
-
Tuple of (success: bool, message: str)
|
|
166
|
-
"""
|
|
167
116
|
pass
|
|
168
117
|
|
|
169
118
|
@staticmethod
|
|
170
119
|
async def policies() -> list[str]:
|
|
171
|
-
"""Return policy strings for this strategy."""
|
|
172
120
|
raise NotImplementedError
|
|
173
121
|
|
|
174
122
|
@abstractmethod
|
|
175
123
|
async def _status(self) -> StatusDict:
|
|
176
|
-
"""
|
|
177
|
-
Return status payload. Subclasses should implement this.
|
|
178
|
-
Should include keys (portfolio_value, net_deposit, strategy_status).
|
|
179
|
-
Backward-compatible keys (active_amount, total_earned) may also be included.
|
|
180
|
-
"""
|
|
181
124
|
pass
|
|
182
125
|
|
|
183
126
|
async def status(self) -> StatusDict:
|
|
184
|
-
"""
|
|
185
|
-
Wrapper to compute and return strategy status and record a snapshot.
|
|
186
|
-
Here we simply delegate to _status for compatibility.
|
|
187
|
-
"""
|
|
188
|
-
|
|
189
127
|
status = await self._status()
|
|
190
128
|
await self.ledger_adapter.record_strategy_snapshot(
|
|
191
129
|
wallet_address=self._get_strategy_wallet_address(),
|
|
@@ -195,7 +133,6 @@ class Strategy(ABC):
|
|
|
195
133
|
return status
|
|
196
134
|
|
|
197
135
|
def register_adapters(self, adapters: list[Any]) -> None:
|
|
198
|
-
"""Register adapters for use by the strategy"""
|
|
199
136
|
self.adapters = {}
|
|
200
137
|
for adapter in adapters:
|
|
201
138
|
if hasattr(adapter, "adapter_type"):
|
|
@@ -206,11 +143,6 @@ class Strategy(ABC):
|
|
|
206
143
|
def unwind_on_error(
|
|
207
144
|
self, func: Callable[..., Awaitable[StatusTuple]]
|
|
208
145
|
) -> Callable[..., Awaitable[StatusTuple]]:
|
|
209
|
-
"""
|
|
210
|
-
Decorator to unwind operations on error
|
|
211
|
-
Useful for deposit operations that need cleanup on failure
|
|
212
|
-
"""
|
|
213
|
-
|
|
214
146
|
async def wrapper(*args: Any, **kwargs: Any) -> StatusTuple:
|
|
215
147
|
try:
|
|
216
148
|
return await func(*args, **kwargs)
|
|
@@ -236,17 +168,6 @@ class Strategy(ABC):
|
|
|
236
168
|
|
|
237
169
|
@classmethod
|
|
238
170
|
def get_metadata(cls) -> dict[str, Any]:
|
|
239
|
-
"""
|
|
240
|
-
Return metadata about this strategy.
|
|
241
|
-
Can be overridden to provide discovery information.
|
|
242
|
-
|
|
243
|
-
Returns:
|
|
244
|
-
Dictionary containing strategy metadata. The following keys are optional
|
|
245
|
-
and will be None if not defined on the class:
|
|
246
|
-
- name: Strategy name
|
|
247
|
-
- description: Strategy description
|
|
248
|
-
- summary: Strategy summary
|
|
249
|
-
"""
|
|
250
171
|
return {
|
|
251
172
|
"name": getattr(cls, "name", None),
|
|
252
173
|
"description": getattr(cls, "description", None),
|
|
@@ -254,9 +175,6 @@ class Strategy(ABC):
|
|
|
254
175
|
}
|
|
255
176
|
|
|
256
177
|
async def health_check(self) -> dict[str, Any]:
|
|
257
|
-
"""
|
|
258
|
-
Check strategy health and dependencies
|
|
259
|
-
"""
|
|
260
178
|
health = {"status": "healthy", "strategy": self.name, "adapters": {}}
|
|
261
179
|
|
|
262
180
|
for name, adapter in self.adapters.items():
|
|
@@ -270,23 +188,6 @@ class Strategy(ABC):
|
|
|
270
188
|
async def partial_liquidate(
|
|
271
189
|
self, usd_value: float
|
|
272
190
|
) -> tuple[bool, LiquidationResult]:
|
|
273
|
-
"""
|
|
274
|
-
Partially liquidate strategy positions by USD value.
|
|
275
|
-
Optional method that can be overridden by subclasses.
|
|
276
|
-
|
|
277
|
-
Args:
|
|
278
|
-
usd_value: USD value to liquidate (must be positive).
|
|
279
|
-
|
|
280
|
-
Returns:
|
|
281
|
-
Tuple of (success: bool, message: str)
|
|
282
|
-
|
|
283
|
-
Raises:
|
|
284
|
-
ValueError: If usd_value is not positive.
|
|
285
|
-
|
|
286
|
-
Note:
|
|
287
|
-
Base implementation returns failure. Subclasses should override
|
|
288
|
-
to implement partial liquidation logic.
|
|
289
|
-
"""
|
|
290
191
|
if usd_value <= 0:
|
|
291
192
|
raise ValueError(f"usd_value must be positive, got {usd_value}")
|
|
292
193
|
return (False, "Partial liquidation not implemented for this strategy")
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
"""Utility helpers for local functionality not tied to external APIs."""
|
|
@@ -49,7 +49,6 @@ async def build_approve_transaction(
|
|
|
49
49
|
spender_address: str,
|
|
50
50
|
amount: int,
|
|
51
51
|
) -> dict:
|
|
52
|
-
"""Build an ERC20 approve transaction."""
|
|
53
52
|
async with web3_from_chain_id(chain_id) as web3:
|
|
54
53
|
contract = web3.eth.contract(
|
|
55
54
|
address=web3.to_checksum_address(token_address), abi=ERC20_ABI
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
EVM helper utilities for common blockchain operations.
|
|
3
|
-
|
|
4
|
-
This module provides reusable functions for EVM-related operations that are shared
|
|
5
|
-
across multiple adapters.
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
1
|
import json
|
|
9
2
|
import os
|
|
10
3
|
from typing import Any
|
|
@@ -15,31 +8,12 @@ from wayfinder_paths.core.constants.base import CHAIN_CODE_TO_ID
|
|
|
15
8
|
|
|
16
9
|
|
|
17
10
|
def chain_code_to_chain_id(chain_code: str | None) -> int | None:
|
|
18
|
-
"""
|
|
19
|
-
Convert chain code to chain ID.
|
|
20
|
-
|
|
21
|
-
Args:
|
|
22
|
-
chain_code: Chain code string (e.g., "ethereum", "base")
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
Chain ID as integer, or None if not found
|
|
26
|
-
"""
|
|
27
11
|
if not chain_code:
|
|
28
12
|
return None
|
|
29
13
|
return CHAIN_CODE_TO_ID.get(chain_code.lower())
|
|
30
14
|
|
|
31
15
|
|
|
32
16
|
def resolve_chain_id(token_info: dict[str, Any], logger_instance=None) -> int | None:
|
|
33
|
-
"""
|
|
34
|
-
Extract chain ID from token_info dictionary.
|
|
35
|
-
|
|
36
|
-
Args:
|
|
37
|
-
token_info: Dictionary containing token information with 'chain' key
|
|
38
|
-
logger_instance: Optional logger instance for debug messages
|
|
39
|
-
|
|
40
|
-
Returns:
|
|
41
|
-
Chain ID as integer, or None if not found
|
|
42
|
-
"""
|
|
43
17
|
log = logger_instance or logger
|
|
44
18
|
chain_meta = token_info.get("chain") or {}
|
|
45
19
|
chain_id = chain_meta.get("id")
|
|
@@ -56,20 +30,6 @@ def resolve_rpc_url(
|
|
|
56
30
|
config: dict[str, Any],
|
|
57
31
|
explicit_rpc_url: str | None = None,
|
|
58
32
|
) -> str:
|
|
59
|
-
"""
|
|
60
|
-
Resolve RPC URL from config.
|
|
61
|
-
|
|
62
|
-
Args:
|
|
63
|
-
chain_id: Chain ID to look up RPC URL for
|
|
64
|
-
config: Configuration dictionary
|
|
65
|
-
explicit_rpc_url: Explicitly provided RPC URL (takes precedence)
|
|
66
|
-
|
|
67
|
-
Returns:
|
|
68
|
-
RPC URL string
|
|
69
|
-
|
|
70
|
-
Raises:
|
|
71
|
-
ValueError: If RPC URL cannot be resolved
|
|
72
|
-
"""
|
|
73
33
|
if explicit_rpc_url:
|
|
74
34
|
return explicit_rpc_url
|
|
75
35
|
strategy_cfg = config.get("strategy") or {}
|
|
@@ -91,16 +51,6 @@ def resolve_rpc_url(
|
|
|
91
51
|
def resolve_private_key_for_from_address(
|
|
92
52
|
from_address: str, config: dict[str, Any]
|
|
93
53
|
) -> str | None:
|
|
94
|
-
"""
|
|
95
|
-
Resolve private key for the given address from config.
|
|
96
|
-
|
|
97
|
-
Args:
|
|
98
|
-
from_address: Address to resolve private key for
|
|
99
|
-
config: Configuration dictionary containing wallet information
|
|
100
|
-
|
|
101
|
-
Returns:
|
|
102
|
-
Private key string, or None if not found
|
|
103
|
-
"""
|
|
104
54
|
from_addr_norm = (from_address or "").lower()
|
|
105
55
|
main_wallet = config.get("main_wallet")
|
|
106
56
|
strategy_wallet = config.get("strategy_wallet")
|
|
@@ -91,7 +91,6 @@ async def gas_price_transaction(
|
|
|
91
91
|
base_fee = max(base_fees)
|
|
92
92
|
priority_fee = max(priority_fees)
|
|
93
93
|
|
|
94
|
-
# The next block can grow base fee by up to 12.5%, we give a flew blocks of landing room. log_1.125(2) ~ 6 blocks of landing room. GPT says this is also what Metamask does.
|
|
95
94
|
max_base_fee_growth_multiplier = 2
|
|
96
95
|
transaction["maxFeePerGas"] = int(
|
|
97
96
|
base_fee * max_base_fee_growth_multiplier
|
wayfinder_paths/run_strategy.py
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Strategy Runner
|
|
4
|
-
Main entry point for running strategies locally
|
|
5
|
-
"""
|
|
6
2
|
|
|
7
3
|
import argparse
|
|
8
4
|
import asyncio
|
|
@@ -23,17 +19,6 @@ def load_strategy(
|
|
|
23
19
|
*,
|
|
24
20
|
config: StrategyJobConfig,
|
|
25
21
|
):
|
|
26
|
-
"""
|
|
27
|
-
Dynamically load a strategy by name
|
|
28
|
-
|
|
29
|
-
Args:
|
|
30
|
-
strategy_name: Name of the strategy to load (directory name in strategies/)
|
|
31
|
-
config: StrategyJobConfig instance containing user and strategy configuration
|
|
32
|
-
|
|
33
|
-
Returns:
|
|
34
|
-
Strategy instance
|
|
35
|
-
"""
|
|
36
|
-
# Build the expected module path from strategy name
|
|
37
22
|
strategies_dir = Path(__file__).parent / "strategies"
|
|
38
23
|
strategy_dir = strategies_dir / strategy_name
|
|
39
24
|
|
|
@@ -49,7 +34,6 @@ def load_strategy(
|
|
|
49
34
|
f"Unknown strategy: {strategy_name}. Available strategies: {available_str}"
|
|
50
35
|
)
|
|
51
36
|
|
|
52
|
-
# Import strategy module and find Strategy class
|
|
53
37
|
module_path = f"strategies.{strategy_name}.strategy"
|
|
54
38
|
module = __import__(module_path, fromlist=[""])
|
|
55
39
|
|
|
@@ -70,7 +54,6 @@ def load_strategy(
|
|
|
70
54
|
if strategy_class is None:
|
|
71
55
|
raise ValueError(f"No Strategy class found in {module_path}")
|
|
72
56
|
|
|
73
|
-
# Get wallet addresses from strategy_config (enriched from wallets array in config.json)
|
|
74
57
|
main_wallet = config.strategy_config.get("main_wallet") or {}
|
|
75
58
|
strategy_wallet = config.strategy_config.get("strategy_wallet") or {}
|
|
76
59
|
main_wallet_address = main_wallet.get("address")
|
|
@@ -103,19 +86,6 @@ def load_strategy(
|
|
|
103
86
|
def load_config(
|
|
104
87
|
config_path: str | None = None, strategy_name: str | None = None
|
|
105
88
|
) -> StrategyJobConfig:
|
|
106
|
-
"""
|
|
107
|
-
Load configuration from config.json file
|
|
108
|
-
|
|
109
|
-
Args:
|
|
110
|
-
config_path: Path to config file (defaults to "config.json")
|
|
111
|
-
strategy_name: Optional strategy name for per-strategy wallet lookup
|
|
112
|
-
|
|
113
|
-
Returns:
|
|
114
|
-
StrategyJobConfig instance
|
|
115
|
-
|
|
116
|
-
Raises:
|
|
117
|
-
FileNotFoundError: If config file does not exist
|
|
118
|
-
"""
|
|
119
89
|
# Default to config.json if not provided
|
|
120
90
|
if not config_path:
|
|
121
91
|
config_path = "config.json"
|
|
@@ -144,22 +114,12 @@ async def run_strategy(
|
|
|
144
114
|
action: str = "run",
|
|
145
115
|
**kwargs,
|
|
146
116
|
):
|
|
147
|
-
"""
|
|
148
|
-
Run a strategy
|
|
149
|
-
|
|
150
|
-
Args:
|
|
151
|
-
strategy_name: Name of the strategy to run
|
|
152
|
-
config_path: Optional path to config file
|
|
153
|
-
action: Action to perform (run, deposit, withdraw, status)
|
|
154
|
-
**kwargs: Additional arguments for the action
|
|
155
|
-
"""
|
|
156
117
|
try:
|
|
157
118
|
if not strategy_name:
|
|
158
119
|
raise ValueError("strategy_name is required")
|
|
159
120
|
|
|
160
121
|
logger.debug(f"Loading strategy by name: {strategy_name}")
|
|
161
122
|
|
|
162
|
-
# Load configuration with strategy name for wallet lookup
|
|
163
123
|
logger.debug(f"Config path provided: {config_path}")
|
|
164
124
|
config = load_config(config_path, strategy_name=strategy_name)
|
|
165
125
|
main_wallet_cfg = config.strategy_config.get("main_wallet") or {}
|
|
@@ -170,20 +130,16 @@ async def run_strategy(
|
|
|
170
130
|
strategy_wallet_cfg.get("address") or "none",
|
|
171
131
|
)
|
|
172
132
|
|
|
173
|
-
# Validate required configuration
|
|
174
133
|
# Authentication is via system.api_key in config.json
|
|
175
134
|
|
|
176
|
-
# Load strategy with the enriched config
|
|
177
135
|
strategy = load_strategy(
|
|
178
136
|
strategy_name,
|
|
179
137
|
config=config,
|
|
180
138
|
)
|
|
181
139
|
logger.info(f"Loaded strategy: {strategy.name}")
|
|
182
140
|
|
|
183
|
-
# Create strategy job
|
|
184
141
|
strategy_job = StrategyJob(strategy, config)
|
|
185
142
|
|
|
186
|
-
# Setup strategy job
|
|
187
143
|
logger.info("Setting up strategy job...")
|
|
188
144
|
logger.debug("Auth mode: API key (from system.api_key)")
|
|
189
145
|
await strategy_job.setup()
|
|
@@ -260,7 +216,6 @@ async def run_strategy(
|
|
|
260
216
|
seen.add(p)
|
|
261
217
|
deduped.append(p)
|
|
262
218
|
|
|
263
|
-
# Get wallet_id from CLI arg, config, or leave as None
|
|
264
219
|
wallet_id = kwargs.get("wallet_id")
|
|
265
220
|
if not wallet_id:
|
|
266
221
|
wallet_id = config.strategy_config.get("wallet_id")
|
|
@@ -309,7 +264,6 @@ async def run_strategy(
|
|
|
309
264
|
|
|
310
265
|
|
|
311
266
|
def main():
|
|
312
|
-
"""Main entry point"""
|
|
313
267
|
parser = argparse.ArgumentParser(description="Run strategy strategies")
|
|
314
268
|
parser.add_argument(
|
|
315
269
|
"strategy",
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
Create a new strategy from template and generate a dedicated wallet for it.
|
|
4
|
-
|
|
5
|
-
This script:
|
|
6
|
-
1. Copies the strategy template to a new directory
|
|
7
|
-
2. Generates a wallet with label matching the strategy name
|
|
8
|
-
3. Updates the strategy files with the correct names
|
|
9
|
-
"""
|
|
10
2
|
|
|
11
3
|
import argparse
|
|
12
4
|
import re
|
|
@@ -17,18 +9,14 @@ from wayfinder_paths.core.utils.wallets import make_random_wallet, write_wallet_
|
|
|
17
9
|
|
|
18
10
|
|
|
19
11
|
def sanitize_name(name: str) -> str:
|
|
20
|
-
"""Convert a strategy name to a valid directory/identifier name."""
|
|
21
12
|
# Replace spaces and special chars with underscores, lowercase
|
|
22
13
|
name = re.sub(r"[^a-zA-Z0-9_-]", "_", name)
|
|
23
|
-
# Remove multiple underscores
|
|
24
14
|
name = re.sub(r"_+", "_", name)
|
|
25
|
-
# Remove leading/trailing underscores
|
|
26
15
|
name = name.strip("_")
|
|
27
16
|
return name.lower()
|
|
28
17
|
|
|
29
18
|
|
|
30
19
|
def update_strategy_file(strategy_path: Path, class_name: str) -> None:
|
|
31
|
-
"""Update strategy.py with new class name."""
|
|
32
20
|
content = strategy_path.read_text()
|
|
33
21
|
# Replace MyStrategy with the new class name
|
|
34
22
|
content = content.replace("MyStrategy", class_name)
|
|
@@ -76,18 +64,15 @@ def main():
|
|
|
76
64
|
dir_name = sanitize_name(args.name)
|
|
77
65
|
strategy_dir = args.strategies_dir / dir_name
|
|
78
66
|
|
|
79
|
-
# Check if directory already exists
|
|
80
67
|
if strategy_dir.exists() and not args.override:
|
|
81
68
|
raise SystemExit(
|
|
82
69
|
f"Strategy directory already exists: {strategy_dir}\n"
|
|
83
70
|
"Use --override to replace it"
|
|
84
71
|
)
|
|
85
72
|
|
|
86
|
-
# Check template exists
|
|
87
73
|
if not args.template_dir.exists():
|
|
88
74
|
raise SystemExit(f"Template directory not found: {args.template_dir}")
|
|
89
75
|
|
|
90
|
-
# Create strategy directory
|
|
91
76
|
if strategy_dir.exists():
|
|
92
77
|
print(f"Removing existing directory: {strategy_dir}")
|
|
93
78
|
shutil.rmtree(strategy_dir)
|
|
@@ -109,12 +94,10 @@ def main():
|
|
|
109
94
|
print(f" Copied {filename}")
|
|
110
95
|
|
|
111
96
|
# Generate class name from strategy name
|
|
112
|
-
# Convert "my_awesome_strategy" -> "MyAwesomeStrategy"
|
|
113
97
|
class_name = "".join(word.capitalize() for word in dir_name.split("_"))
|
|
114
98
|
if not class_name.endswith("Strategy"):
|
|
115
99
|
class_name += "Strategy"
|
|
116
100
|
|
|
117
|
-
# Update strategy.py with new class name
|
|
118
101
|
strategy_file = strategy_dir / "strategy.py"
|
|
119
102
|
if strategy_file.exists():
|
|
120
103
|
update_strategy_file(strategy_file, class_name)
|
|
@@ -54,7 +54,6 @@ def main():
|
|
|
54
54
|
|
|
55
55
|
args.out_dir.mkdir(parents=True, exist_ok=True)
|
|
56
56
|
|
|
57
|
-
# Load existing wallets
|
|
58
57
|
existing = load_wallets(args.out_dir, "config.json")
|
|
59
58
|
has_main = any(w.get("label") in ("main", "default") for w in existing)
|
|
60
59
|
|
|
@@ -67,7 +66,6 @@ def main():
|
|
|
67
66
|
if any(w.get("label") == args.label for w in existing):
|
|
68
67
|
print(f"Wallet with label '{args.label}' already exists, skipping...")
|
|
69
68
|
else:
|
|
70
|
-
# Create wallet with specified label
|
|
71
69
|
w = make_random_wallet()
|
|
72
70
|
w["label"] = args.label
|
|
73
71
|
rows.append(w)
|
|
@@ -96,9 +94,8 @@ def main():
|
|
|
96
94
|
ks_path.write_text(json.dumps(ks))
|
|
97
95
|
index += 1
|
|
98
96
|
else:
|
|
99
|
-
# Create wallets with auto-generated labels: first one is "main" if main doesn't exist, others are "temporary_N"
|
|
100
97
|
if args.n == 0:
|
|
101
|
-
args.n = 1
|
|
98
|
+
args.n = 1
|
|
102
99
|
|
|
103
100
|
# Find next temporary number
|
|
104
101
|
existing_labels = {
|