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.
- wayfinder_paths/CONFIG_GUIDE.md +394 -0
- wayfinder_paths/__init__.py +21 -0
- wayfinder_paths/config.example.json +20 -0
- wayfinder_paths/conftest.py +31 -0
- wayfinder_paths/core/__init__.py +13 -0
- wayfinder_paths/core/adapters/BaseAdapter.py +48 -0
- wayfinder_paths/core/adapters/__init__.py +5 -0
- wayfinder_paths/core/adapters/base.py +5 -0
- wayfinder_paths/core/clients/AuthClient.py +83 -0
- wayfinder_paths/core/clients/BRAPClient.py +90 -0
- wayfinder_paths/core/clients/ClientManager.py +231 -0
- wayfinder_paths/core/clients/HyperlendClient.py +151 -0
- wayfinder_paths/core/clients/LedgerClient.py +222 -0
- wayfinder_paths/core/clients/PoolClient.py +96 -0
- wayfinder_paths/core/clients/SimulationClient.py +180 -0
- wayfinder_paths/core/clients/TokenClient.py +73 -0
- wayfinder_paths/core/clients/TransactionClient.py +47 -0
- wayfinder_paths/core/clients/WalletClient.py +90 -0
- wayfinder_paths/core/clients/WayfinderClient.py +258 -0
- wayfinder_paths/core/clients/__init__.py +48 -0
- wayfinder_paths/core/clients/protocols.py +295 -0
- wayfinder_paths/core/clients/sdk_example.py +115 -0
- wayfinder_paths/core/config.py +369 -0
- wayfinder_paths/core/constants/__init__.py +26 -0
- wayfinder_paths/core/constants/base.py +25 -0
- wayfinder_paths/core/constants/erc20_abi.py +118 -0
- wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
- wayfinder_paths/core/engine/VaultJob.py +182 -0
- wayfinder_paths/core/engine/__init__.py +5 -0
- wayfinder_paths/core/engine/manifest.py +97 -0
- wayfinder_paths/core/services/__init__.py +0 -0
- wayfinder_paths/core/services/base.py +177 -0
- wayfinder_paths/core/services/local_evm_txn.py +429 -0
- wayfinder_paths/core/services/local_token_txn.py +231 -0
- wayfinder_paths/core/services/web3_service.py +45 -0
- wayfinder_paths/core/settings.py +61 -0
- wayfinder_paths/core/strategies/Strategy.py +183 -0
- wayfinder_paths/core/strategies/__init__.py +5 -0
- wayfinder_paths/core/strategies/base.py +7 -0
- wayfinder_paths/core/utils/__init__.py +1 -0
- wayfinder_paths/core/utils/evm_helpers.py +165 -0
- wayfinder_paths/core/utils/wallets.py +77 -0
- wayfinder_paths/core/wallets/README.md +91 -0
- wayfinder_paths/core/wallets/WalletManager.py +56 -0
- wayfinder_paths/core/wallets/__init__.py +7 -0
- wayfinder_paths/run_strategy.py +409 -0
- wayfinder_paths/scripts/__init__.py +0 -0
- wayfinder_paths/scripts/create_strategy.py +181 -0
- wayfinder_paths/scripts/make_wallets.py +160 -0
- wayfinder_paths/scripts/validate_manifests.py +213 -0
- wayfinder_paths/tests/__init__.py +0 -0
- wayfinder_paths/tests/test_smoke_manifest.py +48 -0
- wayfinder_paths/tests/test_test_coverage.py +212 -0
- wayfinder_paths/tests/test_utils.py +64 -0
- wayfinder_paths/vaults/__init__.py +0 -0
- wayfinder_paths/vaults/adapters/__init__.py +0 -0
- wayfinder_paths/vaults/adapters/balance_adapter/README.md +104 -0
- wayfinder_paths/vaults/adapters/balance_adapter/adapter.py +257 -0
- wayfinder_paths/vaults/adapters/balance_adapter/examples.json +6 -0
- wayfinder_paths/vaults/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/vaults/adapters/balance_adapter/test_adapter.py +83 -0
- wayfinder_paths/vaults/adapters/brap_adapter/README.md +249 -0
- wayfinder_paths/vaults/adapters/brap_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/brap_adapter/adapter.py +717 -0
- wayfinder_paths/vaults/adapters/brap_adapter/examples.json +175 -0
- wayfinder_paths/vaults/adapters/brap_adapter/manifest.yaml +11 -0
- wayfinder_paths/vaults/adapters/brap_adapter/test_adapter.py +288 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/adapter.py +298 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/manifest.yaml +10 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/test_adapter.py +267 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/README.md +158 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/adapter.py +286 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/examples.json +131 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/manifest.yaml +11 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/test_adapter.py +202 -0
- wayfinder_paths/vaults/adapters/pool_adapter/README.md +218 -0
- wayfinder_paths/vaults/adapters/pool_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/pool_adapter/adapter.py +289 -0
- wayfinder_paths/vaults/adapters/pool_adapter/examples.json +143 -0
- wayfinder_paths/vaults/adapters/pool_adapter/manifest.yaml +10 -0
- wayfinder_paths/vaults/adapters/pool_adapter/test_adapter.py +222 -0
- wayfinder_paths/vaults/adapters/token_adapter/README.md +101 -0
- wayfinder_paths/vaults/adapters/token_adapter/__init__.py +3 -0
- wayfinder_paths/vaults/adapters/token_adapter/adapter.py +92 -0
- wayfinder_paths/vaults/adapters/token_adapter/examples.json +26 -0
- wayfinder_paths/vaults/adapters/token_adapter/manifest.yaml +6 -0
- wayfinder_paths/vaults/adapters/token_adapter/test_adapter.py +135 -0
- wayfinder_paths/vaults/strategies/__init__.py +0 -0
- wayfinder_paths/vaults/strategies/config.py +85 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/README.md +99 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/examples.json +16 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/strategy.py +2328 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/test_strategy.py +319 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/README.md +95 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/examples.json +17 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/strategy.py +1684 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/test_strategy.py +350 -0
- wayfinder_paths/vaults/templates/adapter/README.md +105 -0
- wayfinder_paths/vaults/templates/adapter/adapter.py +26 -0
- wayfinder_paths/vaults/templates/adapter/examples.json +8 -0
- wayfinder_paths/vaults/templates/adapter/manifest.yaml +6 -0
- wayfinder_paths/vaults/templates/adapter/test_adapter.py +49 -0
- wayfinder_paths/vaults/templates/strategy/README.md +152 -0
- wayfinder_paths/vaults/templates/strategy/examples.json +11 -0
- wayfinder_paths/vaults/templates/strategy/manifest.yaml +8 -0
- wayfinder_paths/vaults/templates/strategy/strategy.py +57 -0
- wayfinder_paths/vaults/templates/strategy/test_strategy.py +197 -0
- wayfinder_paths-0.1.1.dist-info/LICENSE +21 -0
- wayfinder_paths-0.1.1.dist-info/METADATA +727 -0
- wayfinder_paths-0.1.1.dist-info/RECORD +115 -0
- 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
|
+
]
|