wayfinder-paths 0.1.8__py3-none-any.whl → 0.1.9__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.
- wayfinder_paths/CONFIG_GUIDE.md +5 -14
- wayfinder_paths/adapters/brap_adapter/README.md +1 -1
- wayfinder_paths/adapters/brap_adapter/adapter.py +0 -51
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
- wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
- wayfinder_paths/adapters/pool_adapter/README.md +1 -77
- wayfinder_paths/adapters/pool_adapter/adapter.py +0 -122
- wayfinder_paths/adapters/pool_adapter/examples.json +0 -57
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -86
- wayfinder_paths/adapters/token_adapter/README.md +1 -1
- wayfinder_paths/core/clients/AuthClient.py +0 -3
- wayfinder_paths/core/clients/ClientManager.py +1 -22
- wayfinder_paths/core/clients/WalletClient.py +0 -8
- wayfinder_paths/core/clients/WayfinderClient.py +9 -14
- wayfinder_paths/core/clients/__init__.py +0 -8
- wayfinder_paths/core/clients/protocols.py +0 -60
- wayfinder_paths/core/config.py +5 -45
- wayfinder_paths/core/engine/StrategyJob.py +0 -3
- wayfinder_paths/core/services/base.py +0 -49
- wayfinder_paths/core/services/local_evm_txn.py +3 -82
- wayfinder_paths/core/services/local_token_txn.py +61 -70
- wayfinder_paths/core/services/web3_service.py +0 -2
- wayfinder_paths/core/settings.py +8 -8
- wayfinder_paths/core/strategies/Strategy.py +1 -5
- wayfinder_paths/core/utils/evm_helpers.py +7 -12
- wayfinder_paths/core/wallets/README.md +3 -6
- wayfinder_paths/run_strategy.py +29 -32
- wayfinder_paths/scripts/make_wallets.py +1 -25
- wayfinder_paths/scripts/run_strategy.py +0 -2
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +86 -137
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +106 -28
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1 -6
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -4
- wayfinder_paths/templates/strategy/test_strategy.py +0 -4
- {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/METADATA +5 -15
- {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/RECORD +45 -47
- wayfinder_paths/core/clients/SimulationClient.py +0 -192
- wayfinder_paths/core/clients/TransactionClient.py +0 -63
- {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/WHEEL +0 -0
|
@@ -69,77 +69,6 @@ class TestPoolAdapter:
|
|
|
69
69
|
assert success is True
|
|
70
70
|
assert data == mock_response
|
|
71
71
|
|
|
72
|
-
@pytest.mark.asyncio
|
|
73
|
-
async def test_find_high_yield_pools_success(self, adapter, mock_pool_client):
|
|
74
|
-
"""Test successful high yield pool discovery"""
|
|
75
|
-
mock_llama_response = {
|
|
76
|
-
"matches": [
|
|
77
|
-
{
|
|
78
|
-
"pool_id": "pool-123",
|
|
79
|
-
"llama_apy_pct": 5.2,
|
|
80
|
-
"llama_tvl_usd": 1000000,
|
|
81
|
-
"llama_stablecoin": True,
|
|
82
|
-
"network": "base",
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
"pool_id": "pool-456",
|
|
86
|
-
"llama_apy_pct": 2.0,
|
|
87
|
-
"llama_tvl_usd": 500000,
|
|
88
|
-
"llama_stablecoin": True,
|
|
89
|
-
"network": "ethereum",
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
"pool_id": "pool-789",
|
|
93
|
-
"llama_apy_pct": 6.0,
|
|
94
|
-
"llama_tvl_usd": 2000000,
|
|
95
|
-
"llama_stablecoin": False,
|
|
96
|
-
"network": "base",
|
|
97
|
-
},
|
|
98
|
-
]
|
|
99
|
-
}
|
|
100
|
-
mock_pool_client.get_llama_matches = AsyncMock(return_value=mock_llama_response)
|
|
101
|
-
|
|
102
|
-
success, data = await adapter.find_high_yield_pools(
|
|
103
|
-
min_apy=0.03, min_tvl=500000, stablecoin_only=True, network_codes=["base"]
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
assert success is True
|
|
107
|
-
assert len(data["pools"]) == 1 # Only pool-123 meets criteria
|
|
108
|
-
assert (
|
|
109
|
-
data["pools"][0].get("pool_id") == "pool-123"
|
|
110
|
-
or data["pools"][0].get("id") == "pool-123"
|
|
111
|
-
)
|
|
112
|
-
assert data["total_found"] == 1
|
|
113
|
-
assert data["filters_applied"]["min_apy"] == 0.03
|
|
114
|
-
assert data["filters_applied"]["stablecoin_only"] is True
|
|
115
|
-
|
|
116
|
-
@pytest.mark.asyncio
|
|
117
|
-
async def test_get_pool_analytics_success(self, adapter, mock_pool_client):
|
|
118
|
-
"""Test successful pool analytics generation"""
|
|
119
|
-
mock_pool_data = {
|
|
120
|
-
"pools": [
|
|
121
|
-
{"id": "pool-123", "name": "USDC/USDT Pool", "symbol": "USDC-USDT"}
|
|
122
|
-
]
|
|
123
|
-
}
|
|
124
|
-
mock_pool_client.get_pools_by_ids = AsyncMock(return_value=mock_pool_data)
|
|
125
|
-
|
|
126
|
-
mock_llama_data = {
|
|
127
|
-
"pool-123": {
|
|
128
|
-
"llama_apy_pct": 5.2,
|
|
129
|
-
"llama_combined_apy_pct": 5.2,
|
|
130
|
-
"llama_tvl_usd": 1000000,
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
mock_pool_client.get_llama_reports = AsyncMock(return_value=mock_llama_data)
|
|
134
|
-
|
|
135
|
-
success, data = await adapter.get_pool_analytics(["pool-123"])
|
|
136
|
-
|
|
137
|
-
assert success is True
|
|
138
|
-
assert len(data["analytics"]) == 1
|
|
139
|
-
assert data["analytics"][0]["pool"]["id"] == "pool-123"
|
|
140
|
-
assert round(data["analytics"][0]["combined_apy"], 6) == round(0.052, 6)
|
|
141
|
-
assert data["analytics"][0]["tvl_usd"] == 1000000
|
|
142
|
-
|
|
143
72
|
@pytest.mark.asyncio
|
|
144
73
|
async def test_get_pools_by_ids_failure(self, adapter, mock_pool_client):
|
|
145
74
|
"""Test pool retrieval failure"""
|
|
@@ -152,21 +81,6 @@ class TestPoolAdapter:
|
|
|
152
81
|
assert success is False
|
|
153
82
|
assert "API Error" in data
|
|
154
83
|
|
|
155
|
-
@pytest.mark.asyncio
|
|
156
|
-
async def test_find_high_yield_pools_no_matches(self, adapter, mock_pool_client):
|
|
157
|
-
"""Test high yield pool discovery with no matches"""
|
|
158
|
-
mock_llama_response = {"matches": []}
|
|
159
|
-
mock_pool_client.get_llama_matches.return_value = mock_llama_response
|
|
160
|
-
|
|
161
|
-
success, data = await adapter.find_high_yield_pools(
|
|
162
|
-
min_apy=0.10,
|
|
163
|
-
min_tvl=10000000,
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
assert success is True
|
|
167
|
-
assert len(data["pools"]) == 0
|
|
168
|
-
assert data["total_found"] == 0
|
|
169
|
-
|
|
170
84
|
def test_adapter_type(self, adapter):
|
|
171
85
|
"""Test adapter has adapter_type"""
|
|
172
86
|
assert adapter.adapter_type == "POOL"
|
|
@@ -12,7 +12,7 @@ The adapter uses the TokenClient which automatically handles authentication and
|
|
|
12
12
|
|
|
13
13
|
The TokenClient will automatically:
|
|
14
14
|
- Use the WAYFINDER_API_URL from settings
|
|
15
|
-
- Handle authentication via
|
|
15
|
+
- Handle authentication via config.json
|
|
16
16
|
- Manage token refresh and retry logic
|
|
17
17
|
|
|
18
18
|
## Usage
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import os
|
|
2
1
|
from typing import Any
|
|
3
2
|
|
|
4
3
|
from loguru import logger
|
|
@@ -30,8 +29,6 @@ class AuthClient(WayfinderClient):
|
|
|
30
29
|
creds = self._load_config_credentials()
|
|
31
30
|
if creds.get("api_key"):
|
|
32
31
|
return True
|
|
33
|
-
if os.getenv("WAYFINDER_API_KEY"):
|
|
34
|
-
return True
|
|
35
32
|
except Exception:
|
|
36
33
|
pass
|
|
37
34
|
|
|
@@ -15,14 +15,10 @@ from wayfinder_paths.core.clients.protocols import (
|
|
|
15
15
|
HyperlendClientProtocol,
|
|
16
16
|
LedgerClientProtocol,
|
|
17
17
|
PoolClientProtocol,
|
|
18
|
-
SimulationClientProtocol,
|
|
19
18
|
TokenClientProtocol,
|
|
20
|
-
TransactionClientProtocol,
|
|
21
19
|
WalletClientProtocol,
|
|
22
20
|
)
|
|
23
|
-
from wayfinder_paths.core.clients.SimulationClient import SimulationClient
|
|
24
21
|
from wayfinder_paths.core.clients.TokenClient import TokenClient
|
|
25
|
-
from wayfinder_paths.core.clients.TransactionClient import TransactionClient
|
|
26
22
|
from wayfinder_paths.core.clients.WalletClient import WalletClient
|
|
27
23
|
|
|
28
24
|
|
|
@@ -32,7 +28,7 @@ class ClientManager:
|
|
|
32
28
|
|
|
33
29
|
Args:
|
|
34
30
|
clients: Optional dict of pre-instantiated clients to inject directly.
|
|
35
|
-
Keys: 'token', 'hyperlend', 'ledger', 'wallet', 'transaction', 'pool', 'brap'
|
|
31
|
+
Keys: 'token', 'hyperlend', 'ledger', 'wallet', 'transaction', 'pool', 'brap'.
|
|
36
32
|
If not provided, defaults to HTTP-based clients.
|
|
37
33
|
skip_auth: If True, skips authentication (for SDK usage).
|
|
38
34
|
"""
|
|
@@ -59,12 +55,10 @@ class ClientManager:
|
|
|
59
55
|
self._auth_client: AuthClient | None = None
|
|
60
56
|
self._token_client: TokenClientProtocol | None = None
|
|
61
57
|
self._wallet_client: WalletClientProtocol | None = None
|
|
62
|
-
self._transaction_client: TransactionClientProtocol | None = None
|
|
63
58
|
self._ledger_client: LedgerClientProtocol | None = None
|
|
64
59
|
self._pool_client: PoolClientProtocol | None = None
|
|
65
60
|
self._hyperlend_client: HyperlendClientProtocol | None = None
|
|
66
61
|
self._brap_client: BRAPClientProtocol | None = None
|
|
67
|
-
self._simulation_client: SimulationClientProtocol | None = None
|
|
68
62
|
|
|
69
63
|
def _get_or_create_client(
|
|
70
64
|
self,
|
|
@@ -109,13 +103,6 @@ class ClientManager:
|
|
|
109
103
|
"""Get or create token client"""
|
|
110
104
|
return self._get_or_create_client("_token_client", "token", TokenClient)
|
|
111
105
|
|
|
112
|
-
@property
|
|
113
|
-
def transaction(self) -> TransactionClientProtocol:
|
|
114
|
-
"""Get or create transaction client"""
|
|
115
|
-
return self._get_or_create_client(
|
|
116
|
-
"_transaction_client", "transaction", TransactionClient
|
|
117
|
-
)
|
|
118
|
-
|
|
119
106
|
@property
|
|
120
107
|
def ledger(self) -> LedgerClientProtocol:
|
|
121
108
|
"""Get or create ledger client"""
|
|
@@ -143,13 +130,6 @@ class ClientManager:
|
|
|
143
130
|
"""Get or create BRAP client"""
|
|
144
131
|
return self._get_or_create_client("_brap_client", "brap", BRAPClient)
|
|
145
132
|
|
|
146
|
-
@property
|
|
147
|
-
def simulation(self) -> SimulationClientProtocol:
|
|
148
|
-
"""Get or create simulation client"""
|
|
149
|
-
return self._get_or_create_client(
|
|
150
|
-
"_simulation_client", "simulation", SimulationClient
|
|
151
|
-
)
|
|
152
|
-
|
|
153
133
|
async def authenticate(
|
|
154
134
|
self,
|
|
155
135
|
username: str | None = None,
|
|
@@ -206,5 +186,4 @@ class ClientManager:
|
|
|
206
186
|
"wallet": self._wallet_client,
|
|
207
187
|
"hyperlend": self._hyperlend_client,
|
|
208
188
|
"brap": self._brap_client,
|
|
209
|
-
"simulation": self._simulation_client,
|
|
210
189
|
}
|
|
@@ -33,14 +33,6 @@ class PoolBalance(TypedDict):
|
|
|
33
33
|
usd_value: NotRequired[float | None]
|
|
34
34
|
|
|
35
35
|
|
|
36
|
-
class EnrichedBalances(TypedDict):
|
|
37
|
-
"""Enriched token balances response structure"""
|
|
38
|
-
|
|
39
|
-
wallet_address: Required[str]
|
|
40
|
-
balances: Required[list[TokenBalance]]
|
|
41
|
-
total_usd_value: NotRequired[float | None]
|
|
42
|
-
|
|
43
|
-
|
|
44
36
|
class WalletClient(WayfinderClient):
|
|
45
37
|
def __init__(self, api_key: str | None = None):
|
|
46
38
|
super().__init__(api_key=api_key)
|
|
@@ -121,17 +121,17 @@ class WayfinderClient:
|
|
|
121
121
|
|
|
122
122
|
async def _ensure_bearer_token(self) -> bool:
|
|
123
123
|
"""
|
|
124
|
-
Ensure Authorization header is set. Priority: existing header > constructor api_key > config.json api_key >
|
|
124
|
+
Ensure Authorization header is set. Priority: existing header > constructor api_key > config.json api_key > config.json tokens > username/password.
|
|
125
125
|
Raises PermissionError if no credentials found.
|
|
126
126
|
"""
|
|
127
127
|
if self.headers.get("Authorization"):
|
|
128
128
|
return True
|
|
129
129
|
|
|
130
|
-
# Check for API key: constructor > config.json
|
|
130
|
+
# Check for API key: constructor > config.json
|
|
131
131
|
api_key = self._api_key
|
|
132
132
|
if not api_key:
|
|
133
133
|
creds = self._load_config_credentials()
|
|
134
|
-
api_key = creds.get("api_key")
|
|
134
|
+
api_key = creds.get("api_key")
|
|
135
135
|
|
|
136
136
|
if api_key:
|
|
137
137
|
api_key = api_key.strip() if isinstance(api_key, str) else api_key
|
|
@@ -142,12 +142,7 @@ class WayfinderClient:
|
|
|
142
142
|
|
|
143
143
|
# Fall back to OAuth token-based auth
|
|
144
144
|
creds = self._load_config_credentials()
|
|
145
|
-
|
|
146
|
-
refresh = creds.get("refresh_token") or os.getenv("WAYFINDER_REFRESH_TOKEN")
|
|
147
|
-
|
|
148
|
-
if access:
|
|
149
|
-
self.set_tokens(access, refresh)
|
|
150
|
-
return True
|
|
145
|
+
refresh = creds.get("refresh_token")
|
|
151
146
|
|
|
152
147
|
if refresh:
|
|
153
148
|
self._refresh_token = refresh
|
|
@@ -155,8 +150,8 @@ class WayfinderClient:
|
|
|
155
150
|
if refreshed:
|
|
156
151
|
return True
|
|
157
152
|
|
|
158
|
-
username = creds.get("username")
|
|
159
|
-
password = creds.get("password")
|
|
153
|
+
username = creds.get("username")
|
|
154
|
+
password = creds.get("password")
|
|
160
155
|
|
|
161
156
|
if username and password:
|
|
162
157
|
try:
|
|
@@ -178,7 +173,7 @@ class WayfinderClient:
|
|
|
178
173
|
pass
|
|
179
174
|
|
|
180
175
|
raise PermissionError(
|
|
181
|
-
"Not authenticated: provide api_key (via constructor
|
|
176
|
+
"Not authenticated: provide api_key (via constructor or config.json) for service account auth, "
|
|
182
177
|
"or username+password/refresh_token in config.json for personal access"
|
|
183
178
|
)
|
|
184
179
|
|
|
@@ -201,11 +196,11 @@ class WayfinderClient:
|
|
|
201
196
|
# Ensure API key or bearer token is set in headers if available and not already set
|
|
202
197
|
# This ensures API keys are passed to all endpoints (including public ones) for rate limiting
|
|
203
198
|
if not self.headers.get("Authorization"):
|
|
204
|
-
# Try to get API key from constructor
|
|
199
|
+
# Try to get API key from constructor or config
|
|
205
200
|
api_key = self._api_key
|
|
206
201
|
if not api_key:
|
|
207
202
|
creds = self._load_config_credentials()
|
|
208
|
-
api_key = creds.get("api_key")
|
|
203
|
+
api_key = creds.get("api_key")
|
|
209
204
|
|
|
210
205
|
if api_key:
|
|
211
206
|
api_key = api_key.strip() if isinstance(api_key, str) else api_key
|
|
@@ -13,14 +13,10 @@ from wayfinder_paths.core.clients.protocols import (
|
|
|
13
13
|
HyperlendClientProtocol,
|
|
14
14
|
LedgerClientProtocol,
|
|
15
15
|
PoolClientProtocol,
|
|
16
|
-
SimulationClientProtocol,
|
|
17
16
|
TokenClientProtocol,
|
|
18
|
-
TransactionClientProtocol,
|
|
19
17
|
WalletClientProtocol,
|
|
20
18
|
)
|
|
21
|
-
from wayfinder_paths.core.clients.SimulationClient import SimulationClient
|
|
22
19
|
from wayfinder_paths.core.clients.TokenClient import TokenClient
|
|
23
|
-
from wayfinder_paths.core.clients.TransactionClient import TransactionClient
|
|
24
20
|
from wayfinder_paths.core.clients.WalletClient import WalletClient
|
|
25
21
|
from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
|
|
26
22
|
|
|
@@ -30,19 +26,15 @@ __all__ = [
|
|
|
30
26
|
"AuthClient",
|
|
31
27
|
"TokenClient",
|
|
32
28
|
"WalletClient",
|
|
33
|
-
"TransactionClient",
|
|
34
29
|
"LedgerClient",
|
|
35
30
|
"PoolClient",
|
|
36
31
|
"BRAPClient",
|
|
37
|
-
"SimulationClient",
|
|
38
32
|
"HyperlendClient",
|
|
39
33
|
# Protocols for SDK usage
|
|
40
34
|
"TokenClientProtocol",
|
|
41
35
|
"HyperlendClientProtocol",
|
|
42
36
|
"LedgerClientProtocol",
|
|
43
37
|
"WalletClientProtocol",
|
|
44
|
-
"TransactionClientProtocol",
|
|
45
38
|
"PoolClientProtocol",
|
|
46
39
|
"BRAPClientProtocol",
|
|
47
|
-
"SimulationClientProtocol",
|
|
48
40
|
]
|
|
@@ -28,12 +28,10 @@ if TYPE_CHECKING:
|
|
|
28
28
|
LlamaReport,
|
|
29
29
|
PoolList,
|
|
30
30
|
)
|
|
31
|
-
from wayfinder_paths.core.clients.SimulationClient import SimulationResult
|
|
32
31
|
from wayfinder_paths.core.clients.TokenClient import (
|
|
33
32
|
GasToken,
|
|
34
33
|
TokenDetails,
|
|
35
34
|
)
|
|
36
|
-
from wayfinder_paths.core.clients.TransactionClient import TransactionPayload
|
|
37
35
|
from wayfinder_paths.core.clients.WalletClient import (
|
|
38
36
|
PoolBalance,
|
|
39
37
|
TokenBalance,
|
|
@@ -186,21 +184,6 @@ class WalletClientProtocol(Protocol):
|
|
|
186
184
|
...
|
|
187
185
|
|
|
188
186
|
|
|
189
|
-
class TransactionClientProtocol(Protocol):
|
|
190
|
-
"""Protocol for transaction operations"""
|
|
191
|
-
|
|
192
|
-
async def build_send(
|
|
193
|
-
self,
|
|
194
|
-
from_address: str,
|
|
195
|
-
to_address: str,
|
|
196
|
-
token_address: str,
|
|
197
|
-
amount: float,
|
|
198
|
-
chain_id: int,
|
|
199
|
-
) -> TransactionPayload:
|
|
200
|
-
"""Build a send transaction payload for EVM tokens/native transfers"""
|
|
201
|
-
...
|
|
202
|
-
|
|
203
|
-
|
|
204
187
|
class PoolClientProtocol(Protocol):
|
|
205
188
|
"""Protocol for pool-related read operations"""
|
|
206
189
|
|
|
@@ -242,49 +225,6 @@ class BRAPClientProtocol(Protocol):
|
|
|
242
225
|
...
|
|
243
226
|
|
|
244
227
|
|
|
245
|
-
class SimulationClientProtocol(Protocol):
|
|
246
|
-
"""Protocol for blockchain transaction simulations"""
|
|
247
|
-
|
|
248
|
-
async def simulate_send(
|
|
249
|
-
self,
|
|
250
|
-
from_address: str,
|
|
251
|
-
to_address: str,
|
|
252
|
-
token_address: str,
|
|
253
|
-
amount: str,
|
|
254
|
-
chain_id: int,
|
|
255
|
-
initial_balances: dict[str, str],
|
|
256
|
-
) -> SimulationResult:
|
|
257
|
-
"""Simulate sending native ETH or ERC20 tokens"""
|
|
258
|
-
...
|
|
259
|
-
|
|
260
|
-
async def simulate_approve(
|
|
261
|
-
self,
|
|
262
|
-
from_address: str,
|
|
263
|
-
to_address: str,
|
|
264
|
-
token_address: str,
|
|
265
|
-
amount: str,
|
|
266
|
-
chain_id: int,
|
|
267
|
-
initial_balances: dict[str, str],
|
|
268
|
-
clear_approval_first: bool = False,
|
|
269
|
-
) -> SimulationResult:
|
|
270
|
-
"""Simulate ERC20 token approval"""
|
|
271
|
-
...
|
|
272
|
-
|
|
273
|
-
async def simulate_swap(
|
|
274
|
-
self,
|
|
275
|
-
from_token_address: str,
|
|
276
|
-
to_token_address: str,
|
|
277
|
-
from_chain_id: int,
|
|
278
|
-
to_chain_id: int,
|
|
279
|
-
amount: str,
|
|
280
|
-
from_address: str,
|
|
281
|
-
slippage: float,
|
|
282
|
-
initial_balances: dict[str, str],
|
|
283
|
-
) -> SimulationResult:
|
|
284
|
-
"""Simulate token swap operation"""
|
|
285
|
-
...
|
|
286
|
-
|
|
287
|
-
|
|
288
228
|
class HyperliquidExecutorProtocol(Protocol):
|
|
289
229
|
"""Protocol for Hyperliquid order execution operations."""
|
|
290
230
|
|
wayfinder_paths/core/config.py
CHANGED
|
@@ -4,7 +4,6 @@ Separates user-provided configuration from system configuration
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import json
|
|
7
|
-
import os
|
|
8
7
|
from dataclasses import dataclass, field
|
|
9
8
|
from pathlib import Path
|
|
10
9
|
from typing import Any
|
|
@@ -72,12 +71,8 @@ class SystemConfig:
|
|
|
72
71
|
These are values managed by the Wayfinder system
|
|
73
72
|
"""
|
|
74
73
|
|
|
75
|
-
# API endpoints (populated from
|
|
76
|
-
api_base_url: str = field(
|
|
77
|
-
default_factory=lambda: os.getenv(
|
|
78
|
-
"WAYFINDER_API_URL", "https://api.wayfinder.ai"
|
|
79
|
-
)
|
|
80
|
-
)
|
|
74
|
+
# API endpoints (populated from config.json or defaults)
|
|
75
|
+
api_base_url: str = field(default="https://api.wayfinder.ai")
|
|
81
76
|
|
|
82
77
|
# Job configuration
|
|
83
78
|
job_id: str | None = None
|
|
@@ -102,10 +97,7 @@ class SystemConfig:
|
|
|
102
97
|
def from_dict(cls, data: dict[str, Any]) -> "SystemConfig":
|
|
103
98
|
"""Create SystemConfig from dictionary"""
|
|
104
99
|
return cls(
|
|
105
|
-
api_base_url=data.get(
|
|
106
|
-
"api_base_url",
|
|
107
|
-
os.getenv("WAYFINDER_API_URL", "https://api.wayfinder.ai"),
|
|
108
|
-
),
|
|
100
|
+
api_base_url=data.get("api_base_url", "https://api.wayfinder.ai"),
|
|
109
101
|
job_id=data.get("job_id"),
|
|
110
102
|
job_type=data.get("job_type", "strategy"),
|
|
111
103
|
update_interval=data.get("update_interval", 60),
|
|
@@ -113,10 +105,8 @@ class SystemConfig:
|
|
|
113
105
|
retry_delay=data.get("retry_delay", 5),
|
|
114
106
|
log_path=data.get("log_path"),
|
|
115
107
|
data_path=data.get("data_path"),
|
|
116
|
-
wallets_path=data.get(
|
|
117
|
-
|
|
118
|
-
),
|
|
119
|
-
wallet_id=data.get("wallet_id") or os.getenv("WALLET_ID"),
|
|
108
|
+
wallets_path=data.get("wallets_path", "wallets.json"),
|
|
109
|
+
wallet_id=data.get("wallet_id"),
|
|
120
110
|
)
|
|
121
111
|
|
|
122
112
|
def to_dict(self) -> dict[str, Any]:
|
|
@@ -386,36 +376,6 @@ class StrategyJobConfig:
|
|
|
386
376
|
return config
|
|
387
377
|
|
|
388
378
|
|
|
389
|
-
def load_config_from_env() -> StrategyJobConfig:
|
|
390
|
-
"""
|
|
391
|
-
Load configuration from environment variables
|
|
392
|
-
This is the simplest way for users to provide configuration
|
|
393
|
-
"""
|
|
394
|
-
user_config = UserConfig(
|
|
395
|
-
username=os.getenv("WAYFINDER_USERNAME"),
|
|
396
|
-
password=os.getenv("WAYFINDER_PASSWORD"),
|
|
397
|
-
refresh_token=os.getenv("WAYFINDER_REFRESH_TOKEN"),
|
|
398
|
-
main_wallet_address=os.getenv("MAIN_WALLET_ADDRESS"),
|
|
399
|
-
strategy_wallet_address=os.getenv("STRATEGY_WALLET_ADDRESS"),
|
|
400
|
-
default_slippage=float(os.getenv("DEFAULT_SLIPPAGE", "0.005")),
|
|
401
|
-
gas_multiplier=float(os.getenv("GAS_MULTIPLIER", "1.2")),
|
|
402
|
-
)
|
|
403
|
-
|
|
404
|
-
system_config = SystemConfig(
|
|
405
|
-
api_base_url=os.getenv("WAYFINDER_API_URL", "https://api.wayfinder.ai"),
|
|
406
|
-
job_id=os.getenv("JOB_ID"),
|
|
407
|
-
update_interval=int(os.getenv("UPDATE_INTERVAL", "60")),
|
|
408
|
-
max_retries=int(os.getenv("MAX_RETRIES", "3")),
|
|
409
|
-
retry_delay=int(os.getenv("RETRY_DELAY", "5")),
|
|
410
|
-
wallets_path=os.getenv("WALLETS_PATH", "wallets.json"),
|
|
411
|
-
wallet_id=os.getenv("WALLET_ID"),
|
|
412
|
-
)
|
|
413
|
-
|
|
414
|
-
# No auto-population - wallets must be explicitly set in environment or matched by label
|
|
415
|
-
|
|
416
|
-
return StrategyJobConfig(user=user_config, system=system_config)
|
|
417
|
-
|
|
418
|
-
|
|
419
379
|
# --- Internal helpers -------------------------------------------------------
|
|
420
380
|
|
|
421
381
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import os
|
|
3
2
|
from typing import Any
|
|
4
3
|
|
|
5
4
|
from loguru import logger
|
|
@@ -54,8 +53,6 @@ class StrategyJob:
|
|
|
54
53
|
creds = self.clients.auth._load_config_credentials()
|
|
55
54
|
if creds.get("api_key"):
|
|
56
55
|
return True
|
|
57
|
-
if os.getenv("WAYFINDER_API_KEY"):
|
|
58
|
-
return True
|
|
59
56
|
except Exception:
|
|
60
57
|
pass
|
|
61
58
|
|
|
@@ -50,55 +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
|
-
) -> tuple[bool, Any]:
|
|
60
|
-
"""
|
|
61
|
-
Get balance for an address.
|
|
62
|
-
|
|
63
|
-
Args:
|
|
64
|
-
address: Address to query balance for
|
|
65
|
-
token_address: ERC20 token address, or None for native token
|
|
66
|
-
chain_id: Chain ID
|
|
67
|
-
|
|
68
|
-
Returns:
|
|
69
|
-
Tuple of (success, balance_integer_or_error_message)
|
|
70
|
-
"""
|
|
71
|
-
pass
|
|
72
|
-
|
|
73
|
-
@abstractmethod
|
|
74
|
-
async def approve_token(
|
|
75
|
-
self,
|
|
76
|
-
token_address: str,
|
|
77
|
-
spender: str,
|
|
78
|
-
amount: int,
|
|
79
|
-
from_address: str,
|
|
80
|
-
chain_id: int,
|
|
81
|
-
wait_for_receipt: bool = True,
|
|
82
|
-
timeout: int = DEFAULT_TRANSACTION_TIMEOUT,
|
|
83
|
-
) -> tuple[bool, Any]:
|
|
84
|
-
"""
|
|
85
|
-
Approve a spender to spend tokens on behalf of from_address.
|
|
86
|
-
|
|
87
|
-
Args:
|
|
88
|
-
token_address: ERC20 token contract address
|
|
89
|
-
spender: Address being approved to spend tokens
|
|
90
|
-
amount: Amount to approve (in token units, not human-readable)
|
|
91
|
-
from_address: Address approving the tokens
|
|
92
|
-
chain_id: Chain ID
|
|
93
|
-
wait_for_receipt: Whether to wait for the transaction receipt
|
|
94
|
-
timeout: Receipt timeout in seconds
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
Tuple of (success, transaction_result_dict_or_error_message)
|
|
98
|
-
Transaction result should include 'tx_hash' and optionally 'receipt'
|
|
99
|
-
"""
|
|
100
|
-
pass
|
|
101
|
-
|
|
102
53
|
@abstractmethod
|
|
103
54
|
async def broadcast_transaction(
|
|
104
55
|
self,
|
|
@@ -4,18 +4,13 @@ from typing import Any
|
|
|
4
4
|
from eth_account import Account
|
|
5
5
|
from eth_utils import to_checksum_address
|
|
6
6
|
from loguru import logger
|
|
7
|
-
from web3 import AsyncHTTPProvider, AsyncWeb3
|
|
7
|
+
from web3 import AsyncHTTPProvider, AsyncWeb3
|
|
8
8
|
|
|
9
9
|
from wayfinder_paths.core.constants import (
|
|
10
10
|
DEFAULT_GAS_ESTIMATE_FALLBACK,
|
|
11
11
|
ONE_GWEI,
|
|
12
|
-
ZERO_ADDRESS,
|
|
13
12
|
)
|
|
14
13
|
from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
|
|
15
|
-
from wayfinder_paths.core.constants.erc20_abi import (
|
|
16
|
-
ERC20_APPROVAL_ABI,
|
|
17
|
-
ERC20_MINIMAL_ABI,
|
|
18
|
-
)
|
|
19
14
|
from wayfinder_paths.core.services.base import EvmTxn
|
|
20
15
|
from wayfinder_paths.core.utils.evm_helpers import (
|
|
21
16
|
resolve_private_key_for_from_address,
|
|
@@ -79,10 +74,10 @@ class NonceManager:
|
|
|
79
74
|
|
|
80
75
|
class LocalEvmTxn(EvmTxn):
|
|
81
76
|
"""
|
|
82
|
-
Local wallet provider using private keys stored in config or
|
|
77
|
+
Local wallet provider using private keys stored in config.json or wallets.json.
|
|
83
78
|
|
|
84
79
|
This provider implements the current default behavior:
|
|
85
|
-
- Resolves private keys from config or
|
|
80
|
+
- Resolves private keys from config.json or wallets.json
|
|
86
81
|
- Signs transactions using eth_account
|
|
87
82
|
- Broadcasts transactions via RPC
|
|
88
83
|
"""
|
|
@@ -107,80 +102,6 @@ class LocalEvmTxn(EvmTxn):
|
|
|
107
102
|
rpc_url = self._resolve_rpc_url(chain_id)
|
|
108
103
|
return AsyncWeb3(AsyncHTTPProvider(rpc_url))
|
|
109
104
|
|
|
110
|
-
async def get_balance(
|
|
111
|
-
self,
|
|
112
|
-
address: str,
|
|
113
|
-
token_address: str | None,
|
|
114
|
-
chain_id: int,
|
|
115
|
-
) -> tuple[bool, Any]:
|
|
116
|
-
"""
|
|
117
|
-
Get balance for an address (native or ERC20 token).
|
|
118
|
-
"""
|
|
119
|
-
w3 = self.get_web3(chain_id)
|
|
120
|
-
try:
|
|
121
|
-
checksum_addr = to_checksum_address(address)
|
|
122
|
-
|
|
123
|
-
if not token_address or token_address.lower() == ZERO_ADDRESS:
|
|
124
|
-
balance = await w3.eth.get_balance(checksum_addr)
|
|
125
|
-
return (True, int(balance))
|
|
126
|
-
|
|
127
|
-
token_checksum = to_checksum_address(token_address)
|
|
128
|
-
contract = w3.eth.contract(address=token_checksum, abi=ERC20_MINIMAL_ABI)
|
|
129
|
-
balance = await contract.functions.balanceOf(checksum_addr).call()
|
|
130
|
-
return (True, int(balance))
|
|
131
|
-
|
|
132
|
-
except Exception as exc: # noqa: BLE001
|
|
133
|
-
self.logger.error(f"Failed to get balance: {exc}")
|
|
134
|
-
return (False, f"Balance query failed: {exc}")
|
|
135
|
-
finally:
|
|
136
|
-
await self._close_web3(w3)
|
|
137
|
-
|
|
138
|
-
async def approve_token(
|
|
139
|
-
self,
|
|
140
|
-
token_address: str,
|
|
141
|
-
spender: str,
|
|
142
|
-
amount: int,
|
|
143
|
-
from_address: str,
|
|
144
|
-
chain_id: int,
|
|
145
|
-
wait_for_receipt: bool = True,
|
|
146
|
-
timeout: int = DEFAULT_TRANSACTION_TIMEOUT,
|
|
147
|
-
) -> tuple[bool, Any]:
|
|
148
|
-
"""
|
|
149
|
-
Approve a spender to spend tokens on behalf of from_address.
|
|
150
|
-
"""
|
|
151
|
-
try:
|
|
152
|
-
token_checksum = to_checksum_address(token_address)
|
|
153
|
-
spender_checksum = to_checksum_address(spender)
|
|
154
|
-
from_checksum = to_checksum_address(from_address)
|
|
155
|
-
amount_int = int(amount)
|
|
156
|
-
|
|
157
|
-
w3_sync = Web3()
|
|
158
|
-
contract = w3_sync.eth.contract(
|
|
159
|
-
address=token_checksum, abi=ERC20_APPROVAL_ABI
|
|
160
|
-
)
|
|
161
|
-
transaction_data = contract.encodeABI(
|
|
162
|
-
fn_name="approve",
|
|
163
|
-
args=[spender_checksum, amount_int],
|
|
164
|
-
)
|
|
165
|
-
|
|
166
|
-
approve_txn = {
|
|
167
|
-
"from": from_checksum,
|
|
168
|
-
"chainId": int(chain_id),
|
|
169
|
-
"to": token_checksum,
|
|
170
|
-
"data": transaction_data,
|
|
171
|
-
"value": 0,
|
|
172
|
-
"gas": ERC20_APPROVAL_GAS_LIMIT,
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return await self.broadcast_transaction(
|
|
176
|
-
approve_txn,
|
|
177
|
-
wait_for_receipt=wait_for_receipt,
|
|
178
|
-
timeout=timeout,
|
|
179
|
-
)
|
|
180
|
-
except Exception as exc: # noqa: BLE001
|
|
181
|
-
self.logger.error(f"ERC20 approval failed: {exc}")
|
|
182
|
-
return (False, f"ERC20 approval failed: {exc}")
|
|
183
|
-
|
|
184
105
|
async def broadcast_transaction(
|
|
185
106
|
self,
|
|
186
107
|
transaction: dict[str, Any],
|