wayfinder-paths 0.1.10__py3-none-any.whl → 0.1.13__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/adapters/balance_adapter/adapter.py +3 -7
- wayfinder_paths/adapters/brap_adapter/adapter.py +10 -13
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +6 -9
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1 -1
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +44 -5
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +104 -0
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +0 -3
- wayfinder_paths/adapters/pool_adapter/README.md +4 -19
- wayfinder_paths/adapters/pool_adapter/adapter.py +4 -29
- wayfinder_paths/adapters/pool_adapter/examples.json +6 -7
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +8 -8
- wayfinder_paths/core/clients/AuthClient.py +2 -2
- wayfinder_paths/core/clients/BRAPClient.py +2 -2
- wayfinder_paths/core/clients/HyperlendClient.py +2 -2
- wayfinder_paths/core/clients/PoolClient.py +18 -54
- wayfinder_paths/core/clients/TokenClient.py +3 -3
- wayfinder_paths/core/clients/WalletClient.py +2 -2
- wayfinder_paths/core/clients/WayfinderClient.py +9 -10
- wayfinder_paths/core/clients/protocols.py +1 -7
- wayfinder_paths/core/config.py +60 -224
- wayfinder_paths/core/services/base.py +0 -55
- wayfinder_paths/core/services/local_evm_txn.py +37 -134
- wayfinder_paths/core/strategies/Strategy.py +3 -3
- wayfinder_paths/core/strategies/descriptors.py +7 -0
- wayfinder_paths/core/utils/evm_helpers.py +5 -28
- wayfinder_paths/core/utils/wallets.py +12 -19
- wayfinder_paths/core/wallets/README.md +1 -1
- wayfinder_paths/run_strategy.py +10 -8
- wayfinder_paths/scripts/create_strategy.py +5 -5
- wayfinder_paths/scripts/make_wallets.py +5 -5
- wayfinder_paths/scripts/run_strategy.py +3 -3
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -1
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +196 -515
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +228 -11
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +1 -0
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +1 -1
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +8 -7
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +2 -2
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +25 -25
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +28 -9
- wayfinder_paths/templates/adapter/README.md +1 -1
- {wayfinder_paths-0.1.10.dist-info → wayfinder_paths-0.1.13.dist-info}/METADATA +15 -18
- {wayfinder_paths-0.1.10.dist-info → wayfinder_paths-0.1.13.dist-info}/RECORD +46 -48
- wayfinder_paths/CONFIG_GUIDE.md +0 -390
- wayfinder_paths/config.example.json +0 -22
- wayfinder_paths/core/settings.py +0 -61
- {wayfinder_paths-0.1.10.dist-info → wayfinder_paths-0.1.13.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.10.dist-info → wayfinder_paths-0.1.13.dist-info}/WHEEL +0 -0
wayfinder_paths/core/config.py
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Core Configuration System
|
|
3
|
-
Separates user-provided configuration from system configuration
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
1
|
import json
|
|
7
2
|
from dataclasses import dataclass, field
|
|
8
3
|
from pathlib import Path
|
|
@@ -18,143 +13,51 @@ from wayfinder_paths.core.constants.base import (
|
|
|
18
13
|
)
|
|
19
14
|
|
|
20
15
|
|
|
16
|
+
def _load_config_file() -> dict[str, Any]:
|
|
17
|
+
path = Path("config.json")
|
|
18
|
+
if not path.exists():
|
|
19
|
+
return {}
|
|
20
|
+
try:
|
|
21
|
+
return json.loads(path.read_text())
|
|
22
|
+
except Exception as e:
|
|
23
|
+
logger.warning(f"Failed to read config file at config.json: {e}")
|
|
24
|
+
return {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
CONFIG = _load_config_file()
|
|
28
|
+
|
|
29
|
+
|
|
21
30
|
@dataclass
|
|
22
31
|
class UserConfig:
|
|
23
|
-
"""
|
|
24
|
-
User-provided configuration
|
|
25
|
-
These are values that users MUST provide to run strategies
|
|
26
|
-
"""
|
|
27
|
-
|
|
28
|
-
# Credential-based auth (JWT)
|
|
29
32
|
username: str | None = None
|
|
30
33
|
password: str | None = None
|
|
31
34
|
refresh_token: str | None = None
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
# Optional user preferences
|
|
38
|
-
default_slippage: float = 0.005 # Default slippage tolerance (0.5%)
|
|
39
|
-
gas_multiplier: float = 1.2 # Gas limit multiplier for safety
|
|
40
|
-
|
|
41
|
-
@classmethod
|
|
42
|
-
def from_dict(cls, data: dict[str, Any]) -> "UserConfig":
|
|
43
|
-
"""Create UserConfig from dictionary"""
|
|
44
|
-
return cls(
|
|
45
|
-
username=data.get("username"),
|
|
46
|
-
password=data.get("password"),
|
|
47
|
-
refresh_token=data.get("refresh_token"),
|
|
48
|
-
main_wallet_address=data.get("main_wallet_address"),
|
|
49
|
-
strategy_wallet_address=data.get("strategy_wallet_address"),
|
|
50
|
-
default_slippage=data.get("default_slippage", 0.005),
|
|
51
|
-
gas_multiplier=data.get("gas_multiplier", 1.2),
|
|
52
|
-
)
|
|
53
|
-
|
|
54
|
-
def to_dict(self) -> dict[str, Any]:
|
|
55
|
-
"""Convert to dictionary"""
|
|
56
|
-
return {
|
|
57
|
-
"username": self.username,
|
|
58
|
-
"password": self.password,
|
|
59
|
-
"refresh_token": self.refresh_token,
|
|
60
|
-
"main_wallet_address": self.main_wallet_address,
|
|
61
|
-
"strategy_wallet_address": self.strategy_wallet_address,
|
|
62
|
-
"default_slippage": self.default_slippage,
|
|
63
|
-
"gas_multiplier": self.gas_multiplier,
|
|
64
|
-
}
|
|
35
|
+
main_wallet_address: str | None = None
|
|
36
|
+
strategy_wallet_address: str | None = None
|
|
37
|
+
default_slippage: float = 0.005
|
|
38
|
+
gas_multiplier: float = 1.2
|
|
65
39
|
|
|
66
40
|
|
|
67
41
|
@dataclass
|
|
68
42
|
class SystemConfig:
|
|
69
|
-
"""
|
|
70
|
-
System-level configuration
|
|
71
|
-
These are values managed by the Wayfinder system
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
# API endpoints (populated from config.json or defaults)
|
|
75
43
|
api_base_url: str = field(default="https://api.wayfinder.ai")
|
|
76
|
-
|
|
77
|
-
# Job configuration
|
|
78
44
|
job_id: str | None = None
|
|
79
45
|
job_type: str = "strategy"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
max_retries: int = 3 # Maximum retries for failed operations
|
|
84
|
-
retry_delay: int = 5 # Delay between retries in seconds
|
|
85
|
-
|
|
86
|
-
# System paths
|
|
46
|
+
update_interval: int = 60
|
|
47
|
+
max_retries: int = 3
|
|
48
|
+
retry_delay: int = 5
|
|
87
49
|
log_path: str | None = None
|
|
88
50
|
data_path: str | None = None
|
|
89
|
-
|
|
90
|
-
# Local wallets.json path used to auto-populate wallet addresses when not provided
|
|
91
|
-
wallets_path: str | None = "wallets.json"
|
|
92
|
-
|
|
93
|
-
# Optional wallet_id for policy rendering
|
|
94
51
|
wallet_id: str | None = None
|
|
95
52
|
|
|
96
|
-
@classmethod
|
|
97
|
-
def from_dict(cls, data: dict[str, Any]) -> "SystemConfig":
|
|
98
|
-
"""Create SystemConfig from dictionary"""
|
|
99
|
-
return cls(
|
|
100
|
-
api_base_url=data.get("api_base_url", "https://api.wayfinder.ai"),
|
|
101
|
-
job_id=data.get("job_id"),
|
|
102
|
-
job_type=data.get("job_type", "strategy"),
|
|
103
|
-
update_interval=data.get("update_interval", 60),
|
|
104
|
-
max_retries=data.get("max_retries", 3),
|
|
105
|
-
retry_delay=data.get("retry_delay", 5),
|
|
106
|
-
log_path=data.get("log_path"),
|
|
107
|
-
data_path=data.get("data_path"),
|
|
108
|
-
wallets_path=data.get("wallets_path", "wallets.json"),
|
|
109
|
-
wallet_id=data.get("wallet_id"),
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
def to_dict(self) -> dict[str, Any]:
|
|
113
|
-
"""Convert to dictionary"""
|
|
114
|
-
return {
|
|
115
|
-
"api_base_url": self.api_base_url,
|
|
116
|
-
"job_id": self.job_id,
|
|
117
|
-
"job_type": self.job_type,
|
|
118
|
-
"update_interval": self.update_interval,
|
|
119
|
-
"max_retries": self.max_retries,
|
|
120
|
-
"retry_delay": self.retry_delay,
|
|
121
|
-
"log_path": self.log_path,
|
|
122
|
-
"data_path": self.data_path,
|
|
123
|
-
"wallets_path": self.wallets_path,
|
|
124
|
-
"wallet_id": self.wallet_id,
|
|
125
|
-
}
|
|
126
|
-
|
|
127
53
|
|
|
128
54
|
@dataclass
|
|
129
55
|
class StrategyJobConfig:
|
|
130
|
-
"""
|
|
131
|
-
Complete configuration for a strategy job
|
|
132
|
-
Combines user and system configurations
|
|
133
|
-
"""
|
|
134
|
-
|
|
135
56
|
user: UserConfig
|
|
136
57
|
system: SystemConfig
|
|
137
|
-
strategy_config: dict[str, Any] = field(
|
|
138
|
-
default_factory=dict
|
|
139
|
-
) # Strategy-specific configuration
|
|
58
|
+
strategy_config: dict[str, Any] = field(default_factory=dict)
|
|
140
59
|
|
|
141
60
|
def __post_init__(self) -> None:
|
|
142
|
-
"""
|
|
143
|
-
Enrich strategy_config with wallet addresses and private keys from wallets.json.
|
|
144
|
-
|
|
145
|
-
This method automatically loads wallet information from wallets.json to populate
|
|
146
|
-
main_wallet and strategy_wallet addresses in strategy_config. Only uses wallets
|
|
147
|
-
with exact label matches (no fallbacks).
|
|
148
|
-
|
|
149
|
-
Wallet enrichment is conditional and can be skipped:
|
|
150
|
-
- Skipped if wallet_type is explicitly set to a non-"local" value
|
|
151
|
-
- Only performed if wallet_type is None, "local", or not specified
|
|
152
|
-
- Allows custom wallet providers (Privy/Turnkey) to opt out of file-based enrichment
|
|
153
|
-
|
|
154
|
-
Note:
|
|
155
|
-
This method never raises exceptions - all errors are logged but do not
|
|
156
|
-
prevent config construction failures.
|
|
157
|
-
"""
|
|
158
61
|
try:
|
|
159
62
|
if not isinstance(self.strategy_config, dict):
|
|
160
63
|
self.strategy_config = {}
|
|
@@ -169,21 +72,11 @@ class StrategyJobConfig:
|
|
|
169
72
|
if wallet_type in (None, "local"):
|
|
170
73
|
self._enrich_wallet_private_keys(by_addr)
|
|
171
74
|
except Exception as e:
|
|
172
|
-
# Defensive: never allow config construction to fail on enrichment
|
|
173
75
|
logger.warning(
|
|
174
76
|
f"Failed to enrich strategy config with wallet information: {e}"
|
|
175
77
|
)
|
|
176
78
|
|
|
177
79
|
def _get_wallet_type(self) -> str | None:
|
|
178
|
-
"""
|
|
179
|
-
Determine the wallet type from strategy config.
|
|
180
|
-
|
|
181
|
-
Checks strategy_config, main_wallet, and strategy_wallet for wallet_type.
|
|
182
|
-
Returns the first wallet_type found, or None if not specified.
|
|
183
|
-
|
|
184
|
-
Returns:
|
|
185
|
-
Wallet type string or None if not specified.
|
|
186
|
-
"""
|
|
187
80
|
wallet_type = self.strategy_config.get("wallet_type")
|
|
188
81
|
if wallet_type:
|
|
189
82
|
return wallet_type
|
|
@@ -205,26 +98,16 @@ class StrategyJobConfig:
|
|
|
205
98
|
def _load_wallets_from_file(
|
|
206
99
|
self,
|
|
207
100
|
) -> tuple[dict[str, dict[str, Any]], dict[str, dict[str, Any]]]:
|
|
208
|
-
|
|
209
|
-
Load wallets from wallets.json file and index by label and address.
|
|
210
|
-
|
|
211
|
-
Returns:
|
|
212
|
-
Tuple of (by_label, by_addr) dictionaries:
|
|
213
|
-
- by_label: Maps wallet label to wallet entry
|
|
214
|
-
- by_addr: Maps wallet address (lowercase) to wallet entry
|
|
215
|
-
"""
|
|
216
|
-
entries = _read_wallets_file(self.system.wallets_path)
|
|
101
|
+
entries = _read_wallets_from_config()
|
|
217
102
|
by_label: dict[str, dict[str, Any]] = {}
|
|
218
103
|
by_addr: dict[str, dict[str, Any]] = {}
|
|
219
104
|
|
|
220
105
|
if entries and isinstance(entries, list):
|
|
221
106
|
for e in entries:
|
|
222
107
|
if isinstance(e, dict):
|
|
223
|
-
# Index by label
|
|
224
108
|
label = e.get("label")
|
|
225
109
|
if isinstance(label, str):
|
|
226
110
|
by_label[label] = e
|
|
227
|
-
# Index by address
|
|
228
111
|
addr = e.get("address")
|
|
229
112
|
if isinstance(addr, str):
|
|
230
113
|
by_addr[addr.lower()] = e
|
|
@@ -232,16 +115,6 @@ class StrategyJobConfig:
|
|
|
232
115
|
return by_label, by_addr
|
|
233
116
|
|
|
234
117
|
def _enrich_wallet_addresses(self, by_label: dict[str, dict[str, Any]]) -> None:
|
|
235
|
-
"""
|
|
236
|
-
Enrich strategy_config with wallet addresses from wallets.json.
|
|
237
|
-
|
|
238
|
-
Loads main_wallet and strategy_wallet addresses by exact label match.
|
|
239
|
-
Only sets addresses if they are not already present in strategy_config.
|
|
240
|
-
|
|
241
|
-
Args:
|
|
242
|
-
by_label: Dictionary mapping wallet labels to wallet entries.
|
|
243
|
-
"""
|
|
244
|
-
# Load main wallet by exact label match only
|
|
245
118
|
if "main_wallet" not in self.strategy_config:
|
|
246
119
|
main_wallet = by_label.get("main")
|
|
247
120
|
if main_wallet:
|
|
@@ -249,33 +122,21 @@ class StrategyJobConfig:
|
|
|
249
122
|
"address": main_wallet["address"]
|
|
250
123
|
}
|
|
251
124
|
|
|
252
|
-
# Load strategy wallet by strategy name label match only
|
|
253
125
|
strategy_name = self.strategy_config.get("_strategy_name")
|
|
254
126
|
if strategy_name and isinstance(strategy_name, str):
|
|
255
127
|
strategy_wallet = by_label.get(strategy_name)
|
|
256
128
|
if strategy_wallet:
|
|
257
|
-
# Use strategy-specific wallet as strategy_wallet
|
|
258
129
|
if "strategy_wallet" not in self.strategy_config:
|
|
259
130
|
self.strategy_config["strategy_wallet"] = {
|
|
260
131
|
"address": strategy_wallet["address"]
|
|
261
132
|
}
|
|
262
133
|
elif isinstance(self.strategy_config.get("strategy_wallet"), dict):
|
|
263
|
-
# Ensure address is set if not already
|
|
264
134
|
if not self.strategy_config["strategy_wallet"].get("address"):
|
|
265
135
|
self.strategy_config["strategy_wallet"]["address"] = (
|
|
266
136
|
strategy_wallet["address"]
|
|
267
137
|
)
|
|
268
138
|
|
|
269
139
|
def _enrich_wallet_private_keys(self, by_addr: dict[str, dict[str, Any]]) -> None:
|
|
270
|
-
"""
|
|
271
|
-
Enrich wallet configs with private keys from wallets.json.
|
|
272
|
-
|
|
273
|
-
Only enriches private keys if using local wallet type (or defaulting to local).
|
|
274
|
-
This ensures custom wallet providers don't get private keys from files.
|
|
275
|
-
|
|
276
|
-
Args:
|
|
277
|
-
by_addr: Dictionary mapping wallet addresses (lowercase) to wallet entries.
|
|
278
|
-
"""
|
|
279
140
|
try:
|
|
280
141
|
for key in ("main_wallet", "strategy_wallet"):
|
|
281
142
|
wallet_obj = self.strategy_config.get(key)
|
|
@@ -292,24 +153,38 @@ class StrategyJobConfig:
|
|
|
292
153
|
wallet_obj["private_key_hex"] = pk
|
|
293
154
|
except Exception as e:
|
|
294
155
|
logger.warning(
|
|
295
|
-
f"Failed to enrich wallet private keys from
|
|
156
|
+
f"Failed to enrich wallet private keys from config.json: {e}"
|
|
296
157
|
)
|
|
297
158
|
|
|
298
159
|
@classmethod
|
|
299
160
|
def from_dict(
|
|
300
161
|
cls, data: dict[str, Any], strategy_name: str | None = None
|
|
301
162
|
) -> "StrategyJobConfig":
|
|
302
|
-
""
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
163
|
+
user_data = data.get("user", {})
|
|
164
|
+
user_cfg = UserConfig(
|
|
165
|
+
username=user_data.get("username"),
|
|
166
|
+
password=user_data.get("password"),
|
|
167
|
+
refresh_token=user_data.get("refresh_token"),
|
|
168
|
+
main_wallet_address=user_data.get("main_wallet_address"),
|
|
169
|
+
strategy_wallet_address=user_data.get("strategy_wallet_address"),
|
|
170
|
+
default_slippage=user_data.get("default_slippage", 0.005),
|
|
171
|
+
gas_multiplier=user_data.get("gas_multiplier", 1.2),
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
system_data = data.get("system", {})
|
|
175
|
+
sys_cfg = SystemConfig(
|
|
176
|
+
api_base_url=system_data.get("api_base_url", "https://api.wayfinder.ai"),
|
|
177
|
+
job_id=system_data.get("job_id"),
|
|
178
|
+
job_type=system_data.get("job_type", "strategy"),
|
|
179
|
+
update_interval=system_data.get("update_interval", 60),
|
|
180
|
+
max_retries=system_data.get("max_retries", 3),
|
|
181
|
+
retry_delay=system_data.get("retry_delay", 5),
|
|
182
|
+
log_path=system_data.get("log_path"),
|
|
183
|
+
data_path=system_data.get("data_path"),
|
|
184
|
+
wallet_id=system_data.get("wallet_id"),
|
|
185
|
+
)
|
|
186
|
+
|
|
311
187
|
strategy_config = data.get("strategy", {})
|
|
312
|
-
# Store strategy name in config for wallet lookup
|
|
313
188
|
if strategy_name:
|
|
314
189
|
strategy_config["_strategy_name"] = strategy_name
|
|
315
190
|
return cls(
|
|
@@ -318,25 +193,11 @@ class StrategyJobConfig:
|
|
|
318
193
|
strategy_config=strategy_config,
|
|
319
194
|
)
|
|
320
195
|
|
|
321
|
-
def to_dict(self) -> dict[str, Any]:
|
|
322
|
-
"""Convert to dictionary"""
|
|
323
|
-
return {
|
|
324
|
-
"user": self.user.to_dict(),
|
|
325
|
-
"system": self.system.to_dict(),
|
|
326
|
-
"strategy": self.strategy_config,
|
|
327
|
-
}
|
|
328
|
-
|
|
329
196
|
def get_adapter_config(self, adapter_name: str) -> dict[str, Any]:
|
|
330
|
-
"""
|
|
331
|
-
Get configuration for a specific adapter
|
|
332
|
-
Combines relevant user and system settings
|
|
333
|
-
"""
|
|
334
197
|
config = {
|
|
335
198
|
"api_base_url": self.system.api_base_url,
|
|
336
199
|
}
|
|
337
200
|
|
|
338
|
-
# Add wallet configuration if needed
|
|
339
|
-
# Only use wallets from strategy_config (matched by label) - no fallbacks
|
|
340
201
|
if adapter_name in [
|
|
341
202
|
ADAPTER_BALANCE,
|
|
342
203
|
ADAPTER_BRAP,
|
|
@@ -359,60 +220,35 @@ class StrategyJobConfig:
|
|
|
359
220
|
and main_wallet.get("address")
|
|
360
221
|
else {}
|
|
361
222
|
)
|
|
362
|
-
# user_wallet uses strategy_wallet if available, otherwise main_wallet
|
|
363
223
|
config["user_wallet"] = (
|
|
364
224
|
config.get("strategy_wallet") or config.get("main_wallet") or {}
|
|
365
225
|
)
|
|
366
226
|
|
|
367
|
-
# Add specific settings
|
|
368
227
|
if adapter_name == ADAPTER_BRAP:
|
|
369
228
|
config["default_slippage"] = self.user.default_slippage
|
|
370
229
|
config["gas_multiplier"] = self.user.gas_multiplier
|
|
371
230
|
|
|
372
|
-
# Add any strategy-specific adapter config
|
|
373
231
|
if adapter_name in self.strategy_config.get("adapters", {}):
|
|
374
232
|
config.update(self.strategy_config["adapters"][adapter_name])
|
|
375
233
|
|
|
376
234
|
return config
|
|
377
235
|
|
|
378
236
|
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
237
|
+
def get_api_base_url() -> str:
|
|
238
|
+
system = CONFIG.get("system", {}) if isinstance(CONFIG, dict) else {}
|
|
239
|
+
api_url = system.get("api_base_url")
|
|
240
|
+
if api_url and isinstance(api_url, str):
|
|
241
|
+
return api_url.strip()
|
|
242
|
+
return "https://wayfinder.ai/api/v1"
|
|
385
243
|
|
|
386
|
-
Args:
|
|
387
|
-
wallets_path: Path to the wallets.json file. If None or empty, returns empty list.
|
|
388
244
|
|
|
389
|
-
|
|
390
|
-
List of wallet dictionaries. Each wallet dict should contain:
|
|
391
|
-
- label: Wallet label (str)
|
|
392
|
-
- address: Wallet address (str)
|
|
393
|
-
- private_key or private_key_hex: Private key (str, optional)
|
|
394
|
-
|
|
395
|
-
Returns empty list if file doesn't exist, is invalid JSON, or contains
|
|
396
|
-
non-list data.
|
|
397
|
-
|
|
398
|
-
Note:
|
|
399
|
-
All errors are logged but do not raise exceptions. This allows the
|
|
400
|
-
config system to continue functioning even if wallets.json is missing
|
|
401
|
-
or malformed.
|
|
402
|
-
"""
|
|
403
|
-
if not wallets_path:
|
|
404
|
-
return []
|
|
405
|
-
path = Path(wallets_path)
|
|
406
|
-
if not path.exists():
|
|
407
|
-
return []
|
|
245
|
+
def _read_wallets_from_config() -> list[dict[str, Any]]:
|
|
408
246
|
try:
|
|
409
|
-
|
|
410
|
-
if isinstance(
|
|
411
|
-
return
|
|
412
|
-
|
|
413
|
-
except (FileNotFoundError, json.JSONDecodeError, OSError) as e:
|
|
414
|
-
logger.warning(f"Failed to read wallets file at {wallets_path}: {e}")
|
|
247
|
+
wallets = CONFIG.get("wallets", [])
|
|
248
|
+
if isinstance(wallets, list):
|
|
249
|
+
return wallets
|
|
250
|
+
logger.warning("Wallets section in config.json is not a list")
|
|
415
251
|
return []
|
|
416
252
|
except Exception as e:
|
|
417
|
-
logger.warning(f"
|
|
253
|
+
logger.warning(f"Failed to read wallets from config.json: {e}")
|
|
418
254
|
return []
|
|
@@ -50,61 +50,6 @@ class EvmTxn(ABC):
|
|
|
50
50
|
transaction confirmations.
|
|
51
51
|
"""
|
|
52
52
|
|
|
53
|
-
@abstractmethod
|
|
54
|
-
async def get_balance(
|
|
55
|
-
self,
|
|
56
|
-
address: str,
|
|
57
|
-
token_address: str | None,
|
|
58
|
-
chain_id: int,
|
|
59
|
-
block_identifier: int | str | None = None,
|
|
60
|
-
) -> tuple[bool, Any]:
|
|
61
|
-
"""
|
|
62
|
-
Get balance for an address at a specific block.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
address: Address to query balance for
|
|
66
|
-
token_address: ERC20 token address, or None for native token
|
|
67
|
-
chain_id: Chain ID
|
|
68
|
-
block_identifier: Block to query at. Can be:
|
|
69
|
-
- int: specific block number (for pinning to tx block)
|
|
70
|
-
- "safe": OP Stack safe block (data posted to L1)
|
|
71
|
-
- "finalized": fully finalized block
|
|
72
|
-
- None/"latest": current head (default, but avoid after txs)
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
Tuple of (success, balance_integer_or_error_message)
|
|
76
|
-
"""
|
|
77
|
-
pass
|
|
78
|
-
|
|
79
|
-
@abstractmethod
|
|
80
|
-
async def approve_token(
|
|
81
|
-
self,
|
|
82
|
-
token_address: str,
|
|
83
|
-
spender: str,
|
|
84
|
-
amount: int,
|
|
85
|
-
from_address: str,
|
|
86
|
-
chain_id: int,
|
|
87
|
-
wait_for_receipt: bool = True,
|
|
88
|
-
timeout: int = DEFAULT_TRANSACTION_TIMEOUT,
|
|
89
|
-
) -> tuple[bool, Any]:
|
|
90
|
-
"""
|
|
91
|
-
Approve a spender to spend tokens on behalf of from_address.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
token_address: ERC20 token contract address
|
|
95
|
-
spender: Address being approved to spend tokens
|
|
96
|
-
amount: Amount to approve (in token units, not human-readable)
|
|
97
|
-
from_address: Address approving the tokens
|
|
98
|
-
chain_id: Chain ID
|
|
99
|
-
wait_for_receipt: Whether to wait for the transaction receipt
|
|
100
|
-
timeout: Receipt timeout in seconds
|
|
101
|
-
|
|
102
|
-
Returns:
|
|
103
|
-
Tuple of (success, transaction_result_dict_or_error_message)
|
|
104
|
-
Transaction result should include 'tx_hash' and optionally 'receipt'
|
|
105
|
-
"""
|
|
106
|
-
pass
|
|
107
|
-
|
|
108
53
|
@abstractmethod
|
|
109
54
|
async def broadcast_transaction(
|
|
110
55
|
self,
|