wayfinder-paths 0.1.1__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 (115) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +394 -0
  2. wayfinder_paths/__init__.py +21 -0
  3. wayfinder_paths/config.example.json +20 -0
  4. wayfinder_paths/conftest.py +31 -0
  5. wayfinder_paths/core/__init__.py +13 -0
  6. wayfinder_paths/core/adapters/BaseAdapter.py +48 -0
  7. wayfinder_paths/core/adapters/__init__.py +5 -0
  8. wayfinder_paths/core/adapters/base.py +5 -0
  9. wayfinder_paths/core/clients/AuthClient.py +83 -0
  10. wayfinder_paths/core/clients/BRAPClient.py +90 -0
  11. wayfinder_paths/core/clients/ClientManager.py +231 -0
  12. wayfinder_paths/core/clients/HyperlendClient.py +151 -0
  13. wayfinder_paths/core/clients/LedgerClient.py +222 -0
  14. wayfinder_paths/core/clients/PoolClient.py +96 -0
  15. wayfinder_paths/core/clients/SimulationClient.py +180 -0
  16. wayfinder_paths/core/clients/TokenClient.py +73 -0
  17. wayfinder_paths/core/clients/TransactionClient.py +47 -0
  18. wayfinder_paths/core/clients/WalletClient.py +90 -0
  19. wayfinder_paths/core/clients/WayfinderClient.py +258 -0
  20. wayfinder_paths/core/clients/__init__.py +48 -0
  21. wayfinder_paths/core/clients/protocols.py +295 -0
  22. wayfinder_paths/core/clients/sdk_example.py +115 -0
  23. wayfinder_paths/core/config.py +369 -0
  24. wayfinder_paths/core/constants/__init__.py +26 -0
  25. wayfinder_paths/core/constants/base.py +25 -0
  26. wayfinder_paths/core/constants/erc20_abi.py +118 -0
  27. wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
  28. wayfinder_paths/core/engine/VaultJob.py +182 -0
  29. wayfinder_paths/core/engine/__init__.py +5 -0
  30. wayfinder_paths/core/engine/manifest.py +97 -0
  31. wayfinder_paths/core/services/__init__.py +0 -0
  32. wayfinder_paths/core/services/base.py +177 -0
  33. wayfinder_paths/core/services/local_evm_txn.py +429 -0
  34. wayfinder_paths/core/services/local_token_txn.py +231 -0
  35. wayfinder_paths/core/services/web3_service.py +45 -0
  36. wayfinder_paths/core/settings.py +61 -0
  37. wayfinder_paths/core/strategies/Strategy.py +183 -0
  38. wayfinder_paths/core/strategies/__init__.py +5 -0
  39. wayfinder_paths/core/strategies/base.py +7 -0
  40. wayfinder_paths/core/utils/__init__.py +1 -0
  41. wayfinder_paths/core/utils/evm_helpers.py +165 -0
  42. wayfinder_paths/core/utils/wallets.py +77 -0
  43. wayfinder_paths/core/wallets/README.md +91 -0
  44. wayfinder_paths/core/wallets/WalletManager.py +56 -0
  45. wayfinder_paths/core/wallets/__init__.py +7 -0
  46. wayfinder_paths/run_strategy.py +409 -0
  47. wayfinder_paths/scripts/__init__.py +0 -0
  48. wayfinder_paths/scripts/create_strategy.py +181 -0
  49. wayfinder_paths/scripts/make_wallets.py +160 -0
  50. wayfinder_paths/scripts/validate_manifests.py +213 -0
  51. wayfinder_paths/tests/__init__.py +0 -0
  52. wayfinder_paths/tests/test_smoke_manifest.py +48 -0
  53. wayfinder_paths/tests/test_test_coverage.py +212 -0
  54. wayfinder_paths/tests/test_utils.py +64 -0
  55. wayfinder_paths/vaults/__init__.py +0 -0
  56. wayfinder_paths/vaults/adapters/__init__.py +0 -0
  57. wayfinder_paths/vaults/adapters/balance_adapter/README.md +104 -0
  58. wayfinder_paths/vaults/adapters/balance_adapter/adapter.py +257 -0
  59. wayfinder_paths/vaults/adapters/balance_adapter/examples.json +6 -0
  60. wayfinder_paths/vaults/adapters/balance_adapter/manifest.yaml +8 -0
  61. wayfinder_paths/vaults/adapters/balance_adapter/test_adapter.py +83 -0
  62. wayfinder_paths/vaults/adapters/brap_adapter/README.md +249 -0
  63. wayfinder_paths/vaults/adapters/brap_adapter/__init__.py +7 -0
  64. wayfinder_paths/vaults/adapters/brap_adapter/adapter.py +717 -0
  65. wayfinder_paths/vaults/adapters/brap_adapter/examples.json +175 -0
  66. wayfinder_paths/vaults/adapters/brap_adapter/manifest.yaml +11 -0
  67. wayfinder_paths/vaults/adapters/brap_adapter/test_adapter.py +288 -0
  68. wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +7 -0
  69. wayfinder_paths/vaults/adapters/hyperlend_adapter/adapter.py +298 -0
  70. wayfinder_paths/vaults/adapters/hyperlend_adapter/manifest.yaml +10 -0
  71. wayfinder_paths/vaults/adapters/hyperlend_adapter/test_adapter.py +267 -0
  72. wayfinder_paths/vaults/adapters/ledger_adapter/README.md +158 -0
  73. wayfinder_paths/vaults/adapters/ledger_adapter/__init__.py +7 -0
  74. wayfinder_paths/vaults/adapters/ledger_adapter/adapter.py +286 -0
  75. wayfinder_paths/vaults/adapters/ledger_adapter/examples.json +131 -0
  76. wayfinder_paths/vaults/adapters/ledger_adapter/manifest.yaml +11 -0
  77. wayfinder_paths/vaults/adapters/ledger_adapter/test_adapter.py +202 -0
  78. wayfinder_paths/vaults/adapters/pool_adapter/README.md +218 -0
  79. wayfinder_paths/vaults/adapters/pool_adapter/__init__.py +7 -0
  80. wayfinder_paths/vaults/adapters/pool_adapter/adapter.py +289 -0
  81. wayfinder_paths/vaults/adapters/pool_adapter/examples.json +143 -0
  82. wayfinder_paths/vaults/adapters/pool_adapter/manifest.yaml +10 -0
  83. wayfinder_paths/vaults/adapters/pool_adapter/test_adapter.py +222 -0
  84. wayfinder_paths/vaults/adapters/token_adapter/README.md +101 -0
  85. wayfinder_paths/vaults/adapters/token_adapter/__init__.py +3 -0
  86. wayfinder_paths/vaults/adapters/token_adapter/adapter.py +92 -0
  87. wayfinder_paths/vaults/adapters/token_adapter/examples.json +26 -0
  88. wayfinder_paths/vaults/adapters/token_adapter/manifest.yaml +6 -0
  89. wayfinder_paths/vaults/adapters/token_adapter/test_adapter.py +135 -0
  90. wayfinder_paths/vaults/strategies/__init__.py +0 -0
  91. wayfinder_paths/vaults/strategies/config.py +85 -0
  92. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/README.md +99 -0
  93. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/examples.json +16 -0
  94. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
  95. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/strategy.py +2328 -0
  96. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/test_strategy.py +319 -0
  97. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/README.md +95 -0
  98. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/examples.json +17 -0
  99. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
  100. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/strategy.py +1684 -0
  101. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/test_strategy.py +350 -0
  102. wayfinder_paths/vaults/templates/adapter/README.md +105 -0
  103. wayfinder_paths/vaults/templates/adapter/adapter.py +26 -0
  104. wayfinder_paths/vaults/templates/adapter/examples.json +8 -0
  105. wayfinder_paths/vaults/templates/adapter/manifest.yaml +6 -0
  106. wayfinder_paths/vaults/templates/adapter/test_adapter.py +49 -0
  107. wayfinder_paths/vaults/templates/strategy/README.md +152 -0
  108. wayfinder_paths/vaults/templates/strategy/examples.json +11 -0
  109. wayfinder_paths/vaults/templates/strategy/manifest.yaml +8 -0
  110. wayfinder_paths/vaults/templates/strategy/strategy.py +57 -0
  111. wayfinder_paths/vaults/templates/strategy/test_strategy.py +197 -0
  112. wayfinder_paths-0.1.1.dist-info/LICENSE +21 -0
  113. wayfinder_paths-0.1.1.dist-info/METADATA +727 -0
  114. wayfinder_paths-0.1.1.dist-info/RECORD +115 -0
  115. wayfinder_paths-0.1.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,115 @@
1
+ """
2
+ SDK Usage Examples
3
+
4
+ Demonstrates how to use the SDK with custom client implementations.
5
+ Use cases: mocks for testing, caching layers, alternative endpoints, rate limiting.
6
+ """
7
+
8
+ from typing import Any
9
+
10
+ from wayfinder_paths.core.clients.ClientManager import ClientManager
11
+ from wayfinder_paths.core.clients.TokenClient import TokenClient
12
+
13
+
14
+ class CachedTokenClient:
15
+ """Token client with in-memory caching"""
16
+
17
+ def __init__(self):
18
+ self._cache: dict[str, dict[str, Any]] = {}
19
+ self._default_client = TokenClient()
20
+
21
+ async def get_token_details(
22
+ self, token_id: str, force_refresh: bool = False
23
+ ) -> dict[str, Any]:
24
+ cache_key = f"token_{token_id}"
25
+ if not force_refresh and cache_key in self._cache:
26
+ return self._cache[cache_key]
27
+ data = await self._default_client.get_token_details(token_id, force_refresh)
28
+ self._cache[cache_key] = data
29
+ return data
30
+
31
+ async def get_gas_token(self, chain_code: str) -> dict[str, Any]:
32
+ return await self._default_client.get_gas_token(chain_code)
33
+
34
+ async def is_native_token(
35
+ self, token_address: str, chain_id: int
36
+ ) -> dict[str, Any]:
37
+ return await self._default_client.is_native_token(token_address, chain_id)
38
+
39
+
40
+ class MockHyperlendClient:
41
+ """Mock client for testing"""
42
+
43
+ async def get_stable_markets(
44
+ self,
45
+ *,
46
+ chain_id: int,
47
+ required_underlying_tokens: float | None = None,
48
+ buffer_bps: int | None = None,
49
+ min_buffer_tokens: float | None = None,
50
+ is_stable_symbol: bool | None = None,
51
+ ) -> dict[str, Any]:
52
+ return {
53
+ "markets": [
54
+ {
55
+ "chain_id": chain_id,
56
+ "token_address": "0xMockToken",
57
+ "symbol": "USDC",
58
+ "lend_rate": 0.05,
59
+ "available_liquidity": 1000000.0,
60
+ }
61
+ ]
62
+ }
63
+
64
+ async def get_assets_view(
65
+ self,
66
+ *,
67
+ chain_id: int,
68
+ user_address: str,
69
+ ) -> dict[str, Any]:
70
+ return {
71
+ "user_address": user_address,
72
+ "chain_id": chain_id,
73
+ "assets": [],
74
+ }
75
+
76
+ async def get_market_entry(
77
+ self,
78
+ *,
79
+ chain_id: int,
80
+ token_address: str,
81
+ ) -> dict[str, Any]:
82
+ return {
83
+ "chain_id": chain_id,
84
+ "token_address": token_address,
85
+ "market_data": {},
86
+ }
87
+
88
+ async def get_lend_rate_history(
89
+ self,
90
+ *,
91
+ chain_id: int,
92
+ token_address: str,
93
+ lookback_hours: int,
94
+ ) -> dict[str, Any]:
95
+ return {
96
+ "chain_id": chain_id,
97
+ "token_address": token_address,
98
+ "rates": [],
99
+ }
100
+
101
+
102
+ async def example_sdk_usage():
103
+ """Direct client injection - inject only what you customize"""
104
+
105
+ custom_token_client = CachedTokenClient()
106
+ custom_hyperlend_client = MockHyperlendClient()
107
+
108
+ ClientManager(
109
+ clients={
110
+ "token": custom_token_client,
111
+ "hyperlend": custom_hyperlend_client,
112
+ },
113
+ skip_auth=True,
114
+ )
115
+ pass
@@ -0,0 +1,369 @@
1
+ """
2
+ Core Configuration System
3
+ Separates user-provided configuration from system configuration
4
+ """
5
+
6
+ import json
7
+ import os
8
+ from dataclasses import dataclass, field
9
+ from pathlib import Path
10
+ from typing import Any
11
+
12
+
13
+ @dataclass
14
+ class UserConfig:
15
+ """
16
+ User-provided configuration
17
+ These are values that users MUST provide to run strategies
18
+ """
19
+
20
+ # Credential-based auth (JWT)
21
+ username: str | None = None
22
+ password: str | None = None
23
+ refresh_token: str | None = None
24
+
25
+ # Wallet configuration
26
+ main_wallet_address: str | None = None # User's main wallet address
27
+ vault_wallet_address: str | None = None # Dedicated vault wallet address
28
+
29
+ # Optional user preferences
30
+ default_slippage: float = 0.005 # Default slippage tolerance (0.5%)
31
+ gas_multiplier: float = 1.2 # Gas limit multiplier for safety
32
+
33
+ @classmethod
34
+ def from_dict(cls, data: dict[str, Any]) -> "UserConfig":
35
+ """Create UserConfig from dictionary"""
36
+ return cls(
37
+ username=data.get("username"),
38
+ password=data.get("password"),
39
+ refresh_token=data.get("refresh_token"),
40
+ main_wallet_address=data.get("main_wallet_address"),
41
+ vault_wallet_address=data.get("vault_wallet_address"),
42
+ default_slippage=data.get("default_slippage", 0.005),
43
+ gas_multiplier=data.get("gas_multiplier", 1.2),
44
+ )
45
+
46
+ def to_dict(self) -> dict[str, Any]:
47
+ """Convert to dictionary"""
48
+ return {
49
+ "username": self.username,
50
+ "password": self.password,
51
+ "refresh_token": self.refresh_token,
52
+ "main_wallet_address": self.main_wallet_address,
53
+ "vault_wallet_address": self.vault_wallet_address,
54
+ "default_slippage": self.default_slippage,
55
+ "gas_multiplier": self.gas_multiplier,
56
+ }
57
+
58
+
59
+ @dataclass
60
+ class SystemConfig:
61
+ """
62
+ System-level configuration
63
+ These are values managed by the Wayfinder system
64
+ """
65
+
66
+ # API endpoints (populated from environment or defaults)
67
+ api_base_url: str = field(
68
+ default_factory=lambda: os.getenv(
69
+ "WAYFINDER_API_URL", "https://api.wayfinder.ai"
70
+ )
71
+ )
72
+
73
+ # Job configuration
74
+ job_id: str | None = None
75
+ job_type: str = "vault"
76
+
77
+ # Execution settings
78
+ update_interval: int = 60 # Default update interval in seconds
79
+ max_retries: int = 3 # Maximum retries for failed operations
80
+ retry_delay: int = 5 # Delay between retries in seconds
81
+
82
+ # System paths
83
+ log_path: str | None = None
84
+ data_path: str | None = None
85
+
86
+ # Local wallets.json path used to auto-populate wallet addresses when not provided
87
+ wallets_path: str | None = "wallets.json"
88
+
89
+ # Optional wallet_id for policy rendering
90
+ wallet_id: str | None = None
91
+
92
+ @classmethod
93
+ def from_dict(cls, data: dict[str, Any]) -> "SystemConfig":
94
+ """Create SystemConfig from dictionary"""
95
+ return cls(
96
+ api_base_url=data.get(
97
+ "api_base_url",
98
+ os.getenv("WAYFINDER_API_URL", "https://api.wayfinder.ai"),
99
+ ),
100
+ job_id=data.get("job_id"),
101
+ job_type=data.get("job_type", "vault"),
102
+ update_interval=data.get("update_interval", 60),
103
+ max_retries=data.get("max_retries", 3),
104
+ retry_delay=data.get("retry_delay", 5),
105
+ log_path=data.get("log_path"),
106
+ data_path=data.get("data_path"),
107
+ wallets_path=data.get(
108
+ "wallets_path", os.getenv("WALLETS_PATH", "wallets.json")
109
+ ),
110
+ wallet_id=data.get("wallet_id") or os.getenv("WALLET_ID"),
111
+ )
112
+
113
+ def to_dict(self) -> dict[str, Any]:
114
+ """Convert to dictionary"""
115
+ return {
116
+ "api_base_url": self.api_base_url,
117
+ "job_id": self.job_id,
118
+ "job_type": self.job_type,
119
+ "update_interval": self.update_interval,
120
+ "max_retries": self.max_retries,
121
+ "retry_delay": self.retry_delay,
122
+ "log_path": self.log_path,
123
+ "data_path": self.data_path,
124
+ "wallets_path": self.wallets_path,
125
+ "wallet_id": self.wallet_id,
126
+ }
127
+
128
+
129
+ @dataclass
130
+ class VaultConfig:
131
+ """
132
+ Complete configuration for a vault job
133
+ Combines user and system configurations
134
+ """
135
+
136
+ user: UserConfig
137
+ system: SystemConfig
138
+ strategy_config: dict[str, Any] = field(
139
+ default_factory=dict
140
+ ) # Strategy-specific configuration
141
+
142
+ def __post_init__(self) -> None:
143
+ """
144
+ Enrich strategy_config with wallet addresses and private keys from wallets.json.
145
+
146
+ This method automatically loads wallet information from wallets.json to populate
147
+ main_wallet and vault_wallet addresses in strategy_config. Only uses wallets
148
+ with exact label matches (no fallbacks).
149
+
150
+ Wallet enrichment is conditional and can be skipped:
151
+ - Skipped if wallet_type is explicitly set to a non-"local" value
152
+ - Only performed if wallet_type is None, "local", or not specified
153
+ - Allows custom wallet providers (Privy/Turnkey) to opt out of file-based enrichment
154
+
155
+ Note:
156
+ This method never raises exceptions - all errors are silently caught to
157
+ prevent config construction failures.
158
+ """
159
+ try:
160
+ if not isinstance(self.strategy_config, dict):
161
+ self.strategy_config = {}
162
+
163
+ # Check wallet_type early - skip enrichment if using non-local wallet provider
164
+ wallet_type = self.strategy_config.get("wallet_type")
165
+ # Also check in main_wallet and vault_wallet configs
166
+ if not wallet_type:
167
+ main_wallet = self.strategy_config.get("main_wallet")
168
+ if isinstance(main_wallet, dict):
169
+ wallet_type = main_wallet.get("wallet_type")
170
+ if not wallet_type:
171
+ vault_wallet = self.strategy_config.get("vault_wallet")
172
+ if isinstance(vault_wallet, dict):
173
+ wallet_type = vault_wallet.get("wallet_type")
174
+
175
+ # Skip wallets.json enrichment if explicitly using non-local wallet provider
176
+ if wallet_type and wallet_type != "local":
177
+ return
178
+
179
+ # Get strategy name if available (for per-strategy wallet lookup)
180
+ strategy_name = self.strategy_config.get("_strategy_name")
181
+
182
+ # Load wallets from file for enrichment (only for local wallet types)
183
+ entries = _read_wallets_file(self.system.wallets_path)
184
+ by_label = {}
185
+ by_addr = {}
186
+ if entries and isinstance(entries, list):
187
+ for e in entries:
188
+ if isinstance(e, dict):
189
+ # Index by label
190
+ label = e.get("label")
191
+ if isinstance(label, str):
192
+ by_label[label] = e
193
+ # Index by address
194
+ addr = e.get("address")
195
+ if isinstance(addr, str):
196
+ by_addr[addr.lower()] = e
197
+
198
+ # Load main wallet by exact label match only
199
+ if "main_wallet" not in self.strategy_config:
200
+ main_wallet = by_label.get("main")
201
+ if main_wallet:
202
+ self.strategy_config["main_wallet"] = {
203
+ "address": main_wallet["address"]
204
+ }
205
+
206
+ # Load vault wallet by strategy name label match only
207
+ if strategy_name and isinstance(strategy_name, str):
208
+ strategy_wallet = by_label.get(strategy_name)
209
+ if strategy_wallet:
210
+ # Use strategy-specific wallet as vault_wallet
211
+ if "vault_wallet" not in self.strategy_config:
212
+ self.strategy_config["vault_wallet"] = {
213
+ "address": strategy_wallet["address"]
214
+ }
215
+ elif isinstance(self.strategy_config.get("vault_wallet"), dict):
216
+ # Ensure address is set if not already
217
+ if not self.strategy_config["vault_wallet"].get("address"):
218
+ self.strategy_config["vault_wallet"]["address"] = (
219
+ strategy_wallet["address"]
220
+ )
221
+
222
+ # Enrich wallet configs with private keys from wallets.json
223
+ # Only enrich private keys if using local wallet type (or defaulting to local)
224
+ # This ensures custom wallet providers don't get private keys from files
225
+ if wallet_type in (None, "local"):
226
+ try:
227
+ for key in ("main_wallet", "vault_wallet"):
228
+ wallet_obj = self.strategy_config.get(key)
229
+ if isinstance(wallet_obj, dict):
230
+ addr = (wallet_obj.get("address") or "").lower()
231
+ entry = by_addr.get(addr)
232
+ if entry:
233
+ pk = entry.get("private_key") or entry.get(
234
+ "private_key_hex"
235
+ )
236
+ if (
237
+ pk
238
+ and not wallet_obj.get("private_key")
239
+ and not wallet_obj.get("private_key_hex")
240
+ ):
241
+ wallet_obj["private_key_hex"] = pk
242
+ except Exception:
243
+ pass
244
+ except Exception:
245
+ # Defensive: never allow config construction to fail on enrichment
246
+ pass
247
+
248
+ @classmethod
249
+ def from_dict(
250
+ cls, data: dict[str, Any], strategy_name: str | None = None
251
+ ) -> "VaultConfig":
252
+ """Create VaultConfig from dictionary
253
+
254
+ Args:
255
+ data: Configuration dictionary
256
+ strategy_name: Optional strategy name for per-strategy wallet lookup
257
+ """
258
+ user_cfg = UserConfig.from_dict(data.get("user", {}))
259
+ sys_cfg = SystemConfig.from_dict(data.get("system", {}))
260
+ # No auto-population - wallets must be explicitly set in config or matched by label
261
+ strategy_config = data.get("strategy", {})
262
+ # Store strategy name in config for wallet lookup
263
+ if strategy_name:
264
+ strategy_config["_strategy_name"] = strategy_name
265
+ return cls(
266
+ user=user_cfg,
267
+ system=sys_cfg,
268
+ strategy_config=strategy_config,
269
+ )
270
+
271
+ def to_dict(self) -> dict[str, Any]:
272
+ """Convert to dictionary"""
273
+ return {
274
+ "user": self.user.to_dict(),
275
+ "system": self.system.to_dict(),
276
+ "strategy": self.strategy_config,
277
+ }
278
+
279
+ def get_adapter_config(self, adapter_name: str) -> dict[str, Any]:
280
+ """
281
+ Get configuration for a specific adapter
282
+ Combines relevant user and system settings
283
+ """
284
+ config = {
285
+ "api_base_url": self.system.api_base_url,
286
+ }
287
+
288
+ # Add wallet configuration if needed
289
+ # Only use wallets from strategy_config (matched by label) - no fallbacks
290
+ if adapter_name in ["balance", "brap", "moonwell", "hyperliquid"]:
291
+ vault_wallet = self.strategy_config.get("vault_wallet")
292
+ main_wallet = self.strategy_config.get("main_wallet")
293
+ config["vault_wallet"] = (
294
+ {"address": vault_wallet["address"]}
295
+ if vault_wallet
296
+ and isinstance(vault_wallet, dict)
297
+ and vault_wallet.get("address")
298
+ else {}
299
+ )
300
+ config["main_wallet"] = (
301
+ {"address": main_wallet["address"]}
302
+ if main_wallet
303
+ and isinstance(main_wallet, dict)
304
+ and main_wallet.get("address")
305
+ else {}
306
+ )
307
+ # user_wallet uses vault_wallet if available, otherwise main_wallet
308
+ config["user_wallet"] = (
309
+ config.get("vault_wallet") or config.get("main_wallet") or {}
310
+ )
311
+
312
+ # Add specific settings
313
+ if adapter_name == "brap":
314
+ config["default_slippage"] = self.user.default_slippage
315
+ config["gas_multiplier"] = self.user.gas_multiplier
316
+
317
+ # Add any strategy-specific adapter config
318
+ if adapter_name in self.strategy_config.get("adapters", {}):
319
+ config.update(self.strategy_config["adapters"][adapter_name])
320
+
321
+ return config
322
+
323
+
324
+ def load_config_from_env() -> VaultConfig:
325
+ """
326
+ Load configuration from environment variables
327
+ This is the simplest way for users to provide configuration
328
+ """
329
+ user_config = UserConfig(
330
+ username=os.getenv("WAYFINDER_USERNAME"),
331
+ password=os.getenv("WAYFINDER_PASSWORD"),
332
+ refresh_token=os.getenv("WAYFINDER_REFRESH_TOKEN"),
333
+ main_wallet_address=os.getenv("MAIN_WALLET_ADDRESS"),
334
+ vault_wallet_address=os.getenv("VAULT_WALLET_ADDRESS"),
335
+ default_slippage=float(os.getenv("DEFAULT_SLIPPAGE", "0.005")),
336
+ gas_multiplier=float(os.getenv("GAS_MULTIPLIER", "1.2")),
337
+ )
338
+
339
+ system_config = SystemConfig(
340
+ api_base_url=os.getenv("WAYFINDER_API_URL", "https://api.wayfinder.ai"),
341
+ job_id=os.getenv("JOB_ID"),
342
+ update_interval=int(os.getenv("UPDATE_INTERVAL", "60")),
343
+ max_retries=int(os.getenv("MAX_RETRIES", "3")),
344
+ retry_delay=int(os.getenv("RETRY_DELAY", "5")),
345
+ wallets_path=os.getenv("WALLETS_PATH", "wallets.json"),
346
+ wallet_id=os.getenv("WALLET_ID"),
347
+ )
348
+
349
+ # No auto-population - wallets must be explicitly set in environment or matched by label
350
+
351
+ return VaultConfig(user=user_config, system=system_config)
352
+
353
+
354
+ # --- Internal helpers -------------------------------------------------------
355
+
356
+
357
+ def _read_wallets_file(wallets_path: str | None) -> list[dict[str, Any]]:
358
+ if not wallets_path:
359
+ return []
360
+ path = Path(wallets_path)
361
+ if not path.exists():
362
+ return []
363
+ try:
364
+ data = json.loads(path.read_text())
365
+ if isinstance(data, list):
366
+ return data
367
+ return []
368
+ except Exception:
369
+ return []
@@ -0,0 +1,26 @@
1
+ """Constants package for wayfinder-paths.
2
+
3
+ This package contains all constants used across the system, organized by category:
4
+ - base: Fundamental constants (addresses, chain mappings, gas defaults)
5
+ - erc20_abi: ERC20 token ABI definitions for smart contract interactions
6
+ """
7
+
8
+ from .base import (
9
+ CHAIN_CODE_TO_ID,
10
+ DEFAULT_GAS_ESTIMATE_FALLBACK,
11
+ DEFAULT_NATIVE_GAS_UNITS,
12
+ DEFAULT_SLIPPAGE,
13
+ GAS_BUFFER_MULTIPLIER,
14
+ ONE_GWEI,
15
+ ZERO_ADDRESS,
16
+ )
17
+
18
+ __all__ = [
19
+ "ZERO_ADDRESS",
20
+ "CHAIN_CODE_TO_ID",
21
+ "DEFAULT_NATIVE_GAS_UNITS",
22
+ "DEFAULT_GAS_ESTIMATE_FALLBACK",
23
+ "GAS_BUFFER_MULTIPLIER",
24
+ "ONE_GWEI",
25
+ "DEFAULT_SLIPPAGE",
26
+ ]
@@ -0,0 +1,25 @@
1
+ """Base constants for adapters and strategies.
2
+
3
+ This module contains fundamental constants used across the wayfinder-paths system,
4
+ including address constants, chain mappings, and gas-related defaults.
5
+ """
6
+
7
+ # Address constants
8
+ ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
9
+
10
+ # Chain code to EVM chain id mapping
11
+ CHAIN_CODE_TO_ID = {
12
+ "base": 8453,
13
+ "arbitrum": 42161,
14
+ "arbitrum-one": 42161,
15
+ "ethereum": 1,
16
+ "mainnet": 1,
17
+ "hyperevm": 999,
18
+ }
19
+
20
+ # Gas/defaults
21
+ DEFAULT_NATIVE_GAS_UNITS = 21000
22
+ DEFAULT_GAS_ESTIMATE_FALLBACK = 100000
23
+ GAS_BUFFER_MULTIPLIER = 1.1 # 10% buffer for native sends
24
+ ONE_GWEI = 1_000_000_000
25
+ DEFAULT_SLIPPAGE = 0.005
@@ -0,0 +1,118 @@
1
+ """
2
+ ERC20 ABI constants for consistent use across the codebase.
3
+ This centralizes ABI definitions to avoid duplication and improve maintainability.
4
+ """
5
+
6
+ # Standard ERC20 ABI - includes common functions needed for token operations
7
+ ERC20_ABI = [
8
+ {
9
+ "constant": True,
10
+ "inputs": [
11
+ {"name": "_owner", "type": "address"},
12
+ {"name": "_spender", "type": "address"},
13
+ ],
14
+ "name": "allowance",
15
+ "outputs": [{"name": "", "type": "uint256"}],
16
+ "type": "function",
17
+ },
18
+ {
19
+ "constant": False,
20
+ "inputs": [
21
+ {"name": "_spender", "type": "address"},
22
+ {"name": "_value", "type": "uint256"},
23
+ ],
24
+ "name": "approve",
25
+ "outputs": [{"name": "", "type": "bool"}],
26
+ "type": "function",
27
+ },
28
+ {
29
+ "constant": True,
30
+ "inputs": [{"name": "account", "type": "address"}],
31
+ "name": "balanceOf",
32
+ "outputs": [{"name": "", "type": "uint256"}],
33
+ "type": "function",
34
+ },
35
+ {
36
+ "constant": True,
37
+ "inputs": [],
38
+ "name": "decimals",
39
+ "outputs": [{"name": "", "type": "uint8"}],
40
+ "type": "function",
41
+ },
42
+ {
43
+ "constant": True,
44
+ "inputs": [],
45
+ "name": "name",
46
+ "outputs": [{"name": "", "type": "string"}],
47
+ "type": "function",
48
+ },
49
+ {
50
+ "constant": True,
51
+ "inputs": [],
52
+ "name": "symbol",
53
+ "outputs": [{"name": "", "type": "string"}],
54
+ "type": "function",
55
+ },
56
+ {
57
+ "constant": True,
58
+ "inputs": [],
59
+ "name": "totalSupply",
60
+ "outputs": [{"name": "", "type": "uint256"}],
61
+ "type": "function",
62
+ },
63
+ {
64
+ "constant": False,
65
+ "inputs": [
66
+ {"name": "_to", "type": "address"},
67
+ {"name": "_value", "type": "uint256"},
68
+ ],
69
+ "name": "transfer",
70
+ "outputs": [{"name": "", "type": "bool"}],
71
+ "type": "function",
72
+ },
73
+ {
74
+ "constant": False,
75
+ "inputs": [
76
+ {"name": "_from", "type": "address"},
77
+ {"name": "_to", "type": "address"},
78
+ {"name": "_value", "type": "uint256"},
79
+ ],
80
+ "name": "transferFrom",
81
+ "outputs": [{"name": "", "type": "bool"}],
82
+ "type": "function",
83
+ },
84
+ ]
85
+
86
+ # Minimal ABI for specific use cases (e.g., when you only need certain functions)
87
+ ERC20_MINIMAL_ABI = [
88
+ {
89
+ "constant": True,
90
+ "inputs": [{"name": "account", "type": "address"}],
91
+ "name": "balanceOf",
92
+ "outputs": [{"name": "", "type": "uint256"}],
93
+ "type": "function",
94
+ }
95
+ ]
96
+
97
+ ERC20_APPROVAL_ABI = [
98
+ {
99
+ "constant": True,
100
+ "inputs": [
101
+ {"name": "_owner", "type": "address"},
102
+ {"name": "_spender", "type": "address"},
103
+ ],
104
+ "name": "allowance",
105
+ "outputs": [{"name": "", "type": "uint256"}],
106
+ "type": "function",
107
+ },
108
+ {
109
+ "constant": False,
110
+ "inputs": [
111
+ {"name": "_spender", "type": "address"},
112
+ {"name": "_value", "type": "uint256"},
113
+ ],
114
+ "name": "approve",
115
+ "outputs": [{"name": "", "type": "bool"}],
116
+ "type": "function",
117
+ },
118
+ ]