wayfinder-paths 0.1.13__py3-none-any.whl → 0.1.14__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/README.md +13 -14
- wayfinder_paths/adapters/balance_adapter/adapter.py +33 -32
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +123 -0
- wayfinder_paths/adapters/brap_adapter/README.md +11 -16
- wayfinder_paths/adapters/brap_adapter/adapter.py +78 -63
- wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +121 -59
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +16 -14
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +114 -60
- wayfinder_paths/adapters/pool_adapter/README.md +9 -10
- wayfinder_paths/adapters/pool_adapter/adapter.py +9 -10
- wayfinder_paths/adapters/token_adapter/README.md +2 -14
- wayfinder_paths/adapters/token_adapter/adapter.py +16 -10
- wayfinder_paths/adapters/token_adapter/examples.json +4 -8
- wayfinder_paths/adapters/token_adapter/test_adapter.py +5 -3
- wayfinder_paths/core/clients/BRAPClient.py +102 -61
- wayfinder_paths/core/clients/ClientManager.py +1 -68
- wayfinder_paths/core/clients/HyperlendClient.py +125 -64
- wayfinder_paths/core/clients/LedgerClient.py +1 -4
- wayfinder_paths/core/clients/PoolClient.py +122 -48
- wayfinder_paths/core/clients/TokenClient.py +91 -36
- wayfinder_paths/core/clients/WalletClient.py +26 -56
- wayfinder_paths/core/clients/WayfinderClient.py +28 -160
- wayfinder_paths/core/clients/__init__.py +0 -2
- wayfinder_paths/core/clients/protocols.py +35 -46
- wayfinder_paths/core/clients/sdk_example.py +37 -22
- wayfinder_paths/core/engine/StrategyJob.py +7 -55
- wayfinder_paths/core/services/local_evm_txn.py +6 -6
- wayfinder_paths/core/services/local_token_txn.py +1 -1
- wayfinder_paths/core/strategies/Strategy.py +0 -2
- wayfinder_paths/core/utils/evm_helpers.py +2 -2
- wayfinder_paths/run_strategy.py +8 -19
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +10 -11
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +40 -25
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +3 -3
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +12 -6
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +1 -1
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +88 -56
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +16 -12
- wayfinder_paths/templates/strategy/README.md +3 -3
- wayfinder_paths/templates/strategy/test_strategy.py +3 -2
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/METADATA +14 -49
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/RECORD +46 -47
- wayfinder_paths/core/clients/AuthClient.py +0 -83
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/WHEEL +0 -0
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import os
|
|
3
2
|
import time
|
|
4
3
|
from typing import Any
|
|
5
4
|
|
|
@@ -11,13 +10,11 @@ from wayfinder_paths.core.constants.base import DEFAULT_HTTP_TIMEOUT
|
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
class WayfinderClient:
|
|
14
|
-
def __init__(self
|
|
13
|
+
def __init__(self):
|
|
15
14
|
"""
|
|
16
15
|
Initialize WayfinderClient.
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
api_key: Optional API key for service account authentication.
|
|
20
|
-
If provided, uses API key auth. Otherwise falls back to config.json.
|
|
17
|
+
API key is loaded from system.api_key in config.json.
|
|
21
18
|
"""
|
|
22
19
|
self.api_base_url = f"{get_api_base_url()}/"
|
|
23
20
|
timeout = httpx.Timeout(DEFAULT_HTTP_TIMEOUT)
|
|
@@ -27,153 +24,51 @@ class WayfinderClient:
|
|
|
27
24
|
"Content-Type": "application/json",
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
self._api_key: str | None = api_key
|
|
31
|
-
self._access_token: str | None = None
|
|
32
|
-
self._refresh_token: str | None = None
|
|
33
|
-
|
|
34
|
-
def set_bearer_token(self, token: str) -> None:
|
|
35
|
-
"""
|
|
36
|
-
Set runtime OAuth/JWT Bearer token for Wayfinder services.
|
|
37
|
-
"""
|
|
38
|
-
self.headers["Authorization"] = f"Bearer {token}"
|
|
39
|
-
self._access_token = token
|
|
40
|
-
|
|
41
|
-
def set_tokens(self, access: str | None, refresh: str | None) -> None:
|
|
42
|
-
"""Set both access and refresh tokens and configure Authorization header."""
|
|
43
|
-
if access:
|
|
44
|
-
self.set_bearer_token(access)
|
|
45
|
-
if refresh:
|
|
46
|
-
self._refresh_token = refresh
|
|
47
|
-
|
|
48
27
|
def clear_auth(self) -> None:
|
|
49
|
-
"""Clear
|
|
50
|
-
self.headers.pop("
|
|
51
|
-
|
|
52
|
-
async def _refresh_access_token(self) -> bool:
|
|
53
|
-
"""Attempt to refresh access token using stored refresh token."""
|
|
54
|
-
if not self._refresh_token:
|
|
55
|
-
logger.debug("No refresh token available")
|
|
56
|
-
return False
|
|
57
|
-
try:
|
|
58
|
-
logger.info("Attempting to refresh access token")
|
|
59
|
-
start_time = time.time()
|
|
60
|
-
url = f"{get_api_base_url()}/auth/token/refresh/"
|
|
61
|
-
payload = {"refresh": self._refresh_token}
|
|
62
|
-
response = await self.client.post(
|
|
63
|
-
url, json=payload, headers={"Content-Type": "application/json"}
|
|
64
|
-
)
|
|
65
|
-
if response.status_code != 200:
|
|
66
|
-
logger.warning(
|
|
67
|
-
f"Token refresh failed with status {response.status_code}"
|
|
68
|
-
)
|
|
69
|
-
return False
|
|
70
|
-
data = response.json()
|
|
71
|
-
new_access = data.get("access") or data.get("access_token")
|
|
72
|
-
if not new_access:
|
|
73
|
-
logger.warning("No access token in refresh response")
|
|
74
|
-
return False
|
|
75
|
-
self.set_bearer_token(new_access)
|
|
76
|
-
elapsed = time.time() - start_time
|
|
77
|
-
logger.info(f"Access token refreshed successfully in {elapsed:.2f}s")
|
|
78
|
-
return True
|
|
79
|
-
except Exception as e:
|
|
80
|
-
elapsed = time.time() - start_time
|
|
81
|
-
logger.error(f"Token refresh failed after {elapsed:.2f}s: {e}")
|
|
82
|
-
return False
|
|
28
|
+
"""Clear X-API-KEY header."""
|
|
29
|
+
self.headers.pop("X-API-KEY", None)
|
|
83
30
|
|
|
84
31
|
def _load_config_credentials(self) -> dict[str, str | None]:
|
|
85
32
|
"""
|
|
86
|
-
Load
|
|
33
|
+
Load API key from config.json.
|
|
87
34
|
Expected shape:
|
|
88
35
|
{
|
|
89
|
-
"user": { "username": ..., "password": ..., "refresh_token": ..., "api_key": ... },
|
|
90
36
|
"system": { "api_key": ... }
|
|
91
37
|
}
|
|
92
38
|
"""
|
|
93
39
|
try:
|
|
94
40
|
with open("config.json") as f:
|
|
95
41
|
cfg = json.load(f)
|
|
96
|
-
user = cfg.get("user", {}) if isinstance(cfg, dict) else {}
|
|
97
42
|
system = cfg.get("system", {}) if isinstance(cfg, dict) else {}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
"password": user.get("password"),
|
|
101
|
-
"refresh_token": user.get("refresh_token"),
|
|
102
|
-
"api_key": user.get("api_key") or system.get("api_key"),
|
|
103
|
-
}
|
|
43
|
+
api_key = system.get("api_key")
|
|
44
|
+
return {"api_key": api_key}
|
|
104
45
|
except (FileNotFoundError, json.JSONDecodeError, OSError) as e:
|
|
105
46
|
logger.debug(f"Could not load config file at config.json: {e}")
|
|
106
|
-
return {
|
|
107
|
-
"username": None,
|
|
108
|
-
"password": None,
|
|
109
|
-
"refresh_token": None,
|
|
110
|
-
"api_key": None,
|
|
111
|
-
}
|
|
47
|
+
return {"api_key": None}
|
|
112
48
|
except Exception as e:
|
|
113
49
|
logger.warning(f"Unexpected error loading config file at config.json: {e}")
|
|
114
|
-
return {
|
|
115
|
-
"username": None,
|
|
116
|
-
"password": None,
|
|
117
|
-
"refresh_token": None,
|
|
118
|
-
"api_key": None,
|
|
119
|
-
}
|
|
50
|
+
return {"api_key": None}
|
|
120
51
|
|
|
121
|
-
|
|
52
|
+
def _ensure_api_key(self) -> bool:
|
|
122
53
|
"""
|
|
123
|
-
Ensure
|
|
124
|
-
Raises PermissionError if no
|
|
54
|
+
Ensure X-API-KEY header is set from system.api_key in config.json.
|
|
55
|
+
Raises PermissionError if no API key found.
|
|
125
56
|
"""
|
|
126
|
-
if self.headers.get("
|
|
57
|
+
if self.headers.get("X-API-KEY"):
|
|
127
58
|
return True
|
|
128
59
|
|
|
129
|
-
|
|
130
|
-
api_key =
|
|
131
|
-
if not api_key:
|
|
132
|
-
creds = self._load_config_credentials()
|
|
133
|
-
api_key = creds.get("api_key") or os.getenv("WAYFINDER_API_KEY")
|
|
60
|
+
creds = self._load_config_credentials()
|
|
61
|
+
api_key = creds.get("api_key")
|
|
134
62
|
|
|
135
63
|
if api_key:
|
|
136
64
|
api_key = api_key.strip() if isinstance(api_key, str) else api_key
|
|
137
65
|
if not api_key:
|
|
138
66
|
raise ValueError("API key cannot be empty")
|
|
139
|
-
self.headers["
|
|
67
|
+
self.headers["X-API-KEY"] = api_key
|
|
140
68
|
return True
|
|
141
69
|
|
|
142
|
-
# Fall back to OAuth token-based auth
|
|
143
|
-
creds = self._load_config_credentials()
|
|
144
|
-
refresh = creds.get("refresh_token")
|
|
145
|
-
|
|
146
|
-
if refresh:
|
|
147
|
-
self._refresh_token = refresh
|
|
148
|
-
refreshed = await self._refresh_access_token()
|
|
149
|
-
if refreshed:
|
|
150
|
-
return True
|
|
151
|
-
|
|
152
|
-
username = creds.get("username")
|
|
153
|
-
password = creds.get("password")
|
|
154
|
-
|
|
155
|
-
if username and password:
|
|
156
|
-
try:
|
|
157
|
-
url = f"{get_api_base_url()}/auth/token/"
|
|
158
|
-
payload = {"username": username, "password": password}
|
|
159
|
-
response = await self.client.post(
|
|
160
|
-
url,
|
|
161
|
-
json=payload,
|
|
162
|
-
headers={"Content-Type": "application/json"},
|
|
163
|
-
)
|
|
164
|
-
if response.status_code == 200:
|
|
165
|
-
data = response.json()
|
|
166
|
-
access = data.get("access") or data.get("access_token")
|
|
167
|
-
refresh = data.get("refresh") or data.get("refresh_token")
|
|
168
|
-
self.set_tokens(access, refresh)
|
|
169
|
-
return bool(access)
|
|
170
|
-
except Exception as e:
|
|
171
|
-
logger.debug(f"Failed to authenticate with username/password: {e}")
|
|
172
|
-
pass
|
|
173
|
-
|
|
174
70
|
raise PermissionError(
|
|
175
|
-
"Not authenticated: provide api_key
|
|
176
|
-
"or username+password/refresh_token in config.json for personal access"
|
|
71
|
+
"Not authenticated: provide api_key in system.api_key in config.json"
|
|
177
72
|
)
|
|
178
73
|
|
|
179
74
|
async def _request(
|
|
@@ -182,49 +77,32 @@ class WayfinderClient:
|
|
|
182
77
|
url: str,
|
|
183
78
|
*,
|
|
184
79
|
headers: dict[str, str] | None = None,
|
|
185
|
-
retry_on_401: bool =
|
|
80
|
+
retry_on_401: bool = False,
|
|
186
81
|
**kwargs: Any,
|
|
187
82
|
) -> httpx.Response:
|
|
188
83
|
"""
|
|
189
|
-
Wrapper around httpx that injects
|
|
190
|
-
Ensures API key
|
|
84
|
+
Wrapper around httpx that injects X-API-KEY header.
|
|
85
|
+
Ensures API key is set in headers when available (for authentication and rate limiting).
|
|
191
86
|
"""
|
|
192
87
|
logger.debug(f"Making {method} request to {url}")
|
|
193
88
|
start_time = time.time()
|
|
194
89
|
|
|
195
|
-
# Ensure API key
|
|
90
|
+
# Ensure API key is set in headers if available and not already set
|
|
196
91
|
# This ensures API keys are passed to all endpoints (including public ones) for rate limiting
|
|
197
|
-
if not self.headers.get("
|
|
198
|
-
|
|
199
|
-
api_key =
|
|
200
|
-
if not api_key:
|
|
201
|
-
creds = self._load_config_credentials()
|
|
202
|
-
api_key = creds.get("api_key") or os.getenv("WAYFINDER_API_KEY")
|
|
92
|
+
if not self.headers.get("X-API-KEY"):
|
|
93
|
+
creds = self._load_config_credentials()
|
|
94
|
+
api_key = creds.get("api_key")
|
|
203
95
|
|
|
204
96
|
if api_key:
|
|
205
97
|
api_key = api_key.strip() if isinstance(api_key, str) else api_key
|
|
206
98
|
if api_key:
|
|
207
|
-
self.headers["
|
|
99
|
+
self.headers["X-API-KEY"] = api_key
|
|
208
100
|
|
|
209
101
|
merged_headers = dict(self.headers)
|
|
210
102
|
if headers:
|
|
211
103
|
merged_headers.update(headers)
|
|
212
104
|
resp = await self.client.request(method, url, headers=merged_headers, **kwargs)
|
|
213
105
|
|
|
214
|
-
if resp.status_code == 401 and retry_on_401 and self._refresh_token:
|
|
215
|
-
logger.info("Received 401, attempting token refresh and retry")
|
|
216
|
-
refreshed = await self._refresh_access_token()
|
|
217
|
-
if refreshed:
|
|
218
|
-
merged_headers = dict(self.headers)
|
|
219
|
-
if headers:
|
|
220
|
-
merged_headers.update(headers)
|
|
221
|
-
resp = await self.client.request(
|
|
222
|
-
method, url, headers=merged_headers, **kwargs
|
|
223
|
-
)
|
|
224
|
-
logger.info("Retry after token refresh successful")
|
|
225
|
-
else:
|
|
226
|
-
logger.error("Token refresh failed, request will fail")
|
|
227
|
-
|
|
228
106
|
elapsed = time.time() - start_time
|
|
229
107
|
if resp.status_code >= 400:
|
|
230
108
|
logger.warning(
|
|
@@ -247,17 +125,7 @@ class WayfinderClient:
|
|
|
247
125
|
**kwargs: Any,
|
|
248
126
|
) -> httpx.Response:
|
|
249
127
|
"""
|
|
250
|
-
Ensure
|
|
251
|
-
Retries once on 401 by re-acquiring tokens.
|
|
128
|
+
Ensure X-API-KEY header is set (from system.api_key in config.json) and perform the request.
|
|
252
129
|
"""
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
raise PermissionError("Not authenticated: set env tokens or credentials")
|
|
256
|
-
try:
|
|
257
|
-
return await self._request(method, url, headers=headers, **kwargs)
|
|
258
|
-
except httpx.HTTPStatusError as e:
|
|
259
|
-
if e.response is not None and e.response.status_code == 401:
|
|
260
|
-
# Retry after attempting re-acquire/refresh
|
|
261
|
-
await self._ensure_bearer_token()
|
|
262
|
-
return await self._request(method, url, headers=headers, **kwargs)
|
|
263
|
-
raise
|
|
130
|
+
self._ensure_api_key()
|
|
131
|
+
return await self._request(method, url, headers=headers, **kwargs)
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
Core client modules for API communication
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from wayfinder_paths.core.clients.AuthClient import AuthClient
|
|
6
5
|
from wayfinder_paths.core.clients.BRAPClient import BRAPClient
|
|
7
6
|
from wayfinder_paths.core.clients.ClientManager import ClientManager
|
|
8
7
|
from wayfinder_paths.core.clients.HyperlendClient import HyperlendClient
|
|
@@ -23,7 +22,6 @@ from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
|
|
|
23
22
|
__all__ = [
|
|
24
23
|
"WayfinderClient",
|
|
25
24
|
"ClientManager",
|
|
26
|
-
"AuthClient",
|
|
27
25
|
"TokenClient",
|
|
28
26
|
"WalletClient",
|
|
29
27
|
"LedgerClient",
|
|
@@ -4,7 +4,7 @@ Protocol definitions for API clients.
|
|
|
4
4
|
These protocols define the interface that all client implementations must satisfy.
|
|
5
5
|
When used as an SDK, users can provide custom implementations that match these protocols.
|
|
6
6
|
|
|
7
|
-
Note:
|
|
7
|
+
Note: Authentication is handled via X-API-KEY header in WayfinderClient base class.
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
from __future__ import annotations
|
|
@@ -12,19 +12,19 @@ from __future__ import annotations
|
|
|
12
12
|
from typing import TYPE_CHECKING, Any, Protocol
|
|
13
13
|
|
|
14
14
|
if TYPE_CHECKING:
|
|
15
|
-
from wayfinder_paths.core.clients.BRAPClient import
|
|
15
|
+
from wayfinder_paths.core.clients.BRAPClient import BRAPQuoteResponse
|
|
16
16
|
from wayfinder_paths.core.clients.HyperlendClient import (
|
|
17
17
|
AssetsView,
|
|
18
18
|
LendRateHistory,
|
|
19
19
|
MarketEntry,
|
|
20
|
-
|
|
20
|
+
StableMarketsHeadroomResponse,
|
|
21
21
|
)
|
|
22
22
|
from wayfinder_paths.core.clients.LedgerClient import (
|
|
23
23
|
StrategyTransactionList,
|
|
24
24
|
TransactionRecord,
|
|
25
25
|
)
|
|
26
26
|
from wayfinder_paths.core.clients.PoolClient import (
|
|
27
|
-
|
|
27
|
+
LlamaMatchesResponse,
|
|
28
28
|
PoolList,
|
|
29
29
|
)
|
|
30
30
|
from wayfinder_paths.core.clients.TokenClient import (
|
|
@@ -32,8 +32,7 @@ if TYPE_CHECKING:
|
|
|
32
32
|
TokenDetails,
|
|
33
33
|
)
|
|
34
34
|
from wayfinder_paths.core.clients.WalletClient import (
|
|
35
|
-
|
|
36
|
-
TokenBalance,
|
|
35
|
+
AddressBalance,
|
|
37
36
|
)
|
|
38
37
|
|
|
39
38
|
|
|
@@ -41,7 +40,10 @@ class TokenClientProtocol(Protocol):
|
|
|
41
40
|
"""Protocol for token-related operations"""
|
|
42
41
|
|
|
43
42
|
async def get_token_details(
|
|
44
|
-
self,
|
|
43
|
+
self,
|
|
44
|
+
query: str,
|
|
45
|
+
market_data: bool = True,
|
|
46
|
+
chain_id: int | None = None,
|
|
45
47
|
) -> TokenDetails:
|
|
46
48
|
"""Get token data including price from the token-details endpoint"""
|
|
47
49
|
...
|
|
@@ -57,19 +59,16 @@ class HyperlendClientProtocol(Protocol):
|
|
|
57
59
|
async def get_stable_markets(
|
|
58
60
|
self,
|
|
59
61
|
*,
|
|
60
|
-
chain_id: int,
|
|
61
62
|
required_underlying_tokens: float | None = None,
|
|
62
63
|
buffer_bps: int | None = None,
|
|
63
64
|
min_buffer_tokens: float | None = None,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
"""Fetch stable markets from Hyperlend"""
|
|
65
|
+
) -> StableMarketsHeadroomResponse:
|
|
66
|
+
"""Fetch stable markets headroom from Hyperlend"""
|
|
67
67
|
...
|
|
68
68
|
|
|
69
69
|
async def get_assets_view(
|
|
70
70
|
self,
|
|
71
71
|
*,
|
|
72
|
-
chain_id: int,
|
|
73
72
|
user_address: str,
|
|
74
73
|
) -> AssetsView:
|
|
75
74
|
"""Fetch assets view for a user address from Hyperlend"""
|
|
@@ -78,8 +77,7 @@ class HyperlendClientProtocol(Protocol):
|
|
|
78
77
|
async def get_market_entry(
|
|
79
78
|
self,
|
|
80
79
|
*,
|
|
81
|
-
|
|
82
|
-
token_address: str,
|
|
80
|
+
token: str,
|
|
83
81
|
) -> MarketEntry:
|
|
84
82
|
"""Fetch market entry from Hyperlend"""
|
|
85
83
|
...
|
|
@@ -87,9 +85,9 @@ class HyperlendClientProtocol(Protocol):
|
|
|
87
85
|
async def get_lend_rate_history(
|
|
88
86
|
self,
|
|
89
87
|
*,
|
|
90
|
-
|
|
91
|
-
token_address: str,
|
|
88
|
+
token: str,
|
|
92
89
|
lookback_hours: int,
|
|
90
|
+
force_refresh: bool | None = None,
|
|
93
91
|
) -> LendRateHistory:
|
|
94
92
|
"""Fetch lend rate history from Hyperlend"""
|
|
95
93
|
...
|
|
@@ -161,25 +159,14 @@ class LedgerClientProtocol(Protocol):
|
|
|
161
159
|
class WalletClientProtocol(Protocol):
|
|
162
160
|
"""Protocol for wallet-related operations"""
|
|
163
161
|
|
|
164
|
-
async def
|
|
162
|
+
async def get_token_balance_for_address(
|
|
165
163
|
self,
|
|
166
164
|
*,
|
|
167
|
-
token_id: str,
|
|
168
165
|
wallet_address: str,
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
async def get_pool_balance_for_wallet(
|
|
175
|
-
self,
|
|
176
|
-
*,
|
|
177
|
-
pool_address: str,
|
|
178
|
-
chain_id: int,
|
|
179
|
-
user_address: str,
|
|
180
|
-
human_readable: bool = True,
|
|
181
|
-
) -> PoolBalance:
|
|
182
|
-
"""Fetch a wallet's LP/share balance for a given pool address and chain"""
|
|
166
|
+
query: str,
|
|
167
|
+
chain_id: int | None = None,
|
|
168
|
+
) -> AddressBalance:
|
|
169
|
+
"""Fetch a balance for an address + chain + query (supports compound query formats)"""
|
|
183
170
|
...
|
|
184
171
|
|
|
185
172
|
|
|
@@ -189,13 +176,18 @@ class PoolClientProtocol(Protocol):
|
|
|
189
176
|
async def get_pools_by_ids(
|
|
190
177
|
self,
|
|
191
178
|
*,
|
|
192
|
-
pool_ids: str,
|
|
179
|
+
pool_ids: list[str] | str,
|
|
193
180
|
) -> PoolList:
|
|
194
|
-
"""Fetch pools by comma-separated
|
|
181
|
+
"""Fetch pools by pool IDs (list or comma-separated string)"""
|
|
195
182
|
...
|
|
196
183
|
|
|
197
|
-
async def get_pools(
|
|
198
|
-
|
|
184
|
+
async def get_pools(
|
|
185
|
+
self,
|
|
186
|
+
*,
|
|
187
|
+
chain_id: int | None = None,
|
|
188
|
+
project: str | None = None,
|
|
189
|
+
) -> LlamaMatchesResponse:
|
|
190
|
+
"""Fetch pools (optionally filtered by chain_id and project)"""
|
|
199
191
|
...
|
|
200
192
|
|
|
201
193
|
|
|
@@ -205,16 +197,13 @@ class BRAPClientProtocol(Protocol):
|
|
|
205
197
|
async def get_quote(
|
|
206
198
|
self,
|
|
207
199
|
*,
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
slippage: float | None = None,
|
|
216
|
-
wayfinder_fee: float | None = None,
|
|
217
|
-
) -> BRAPQuote:
|
|
200
|
+
from_token: str,
|
|
201
|
+
to_token: str,
|
|
202
|
+
from_chain: int,
|
|
203
|
+
to_chain: int,
|
|
204
|
+
from_wallet: str,
|
|
205
|
+
from_amount: str,
|
|
206
|
+
) -> BRAPQuoteResponse:
|
|
218
207
|
"""Get a quote for a bridge/swap operation"""
|
|
219
208
|
...
|
|
220
209
|
|
|
@@ -38,59 +38,74 @@ class MockHyperlendClient:
|
|
|
38
38
|
async def get_stable_markets(
|
|
39
39
|
self,
|
|
40
40
|
*,
|
|
41
|
-
chain_id: int,
|
|
42
41
|
required_underlying_tokens: float | None = None,
|
|
43
42
|
buffer_bps: int | None = None,
|
|
44
43
|
min_buffer_tokens: float | None = None,
|
|
45
|
-
is_stable_symbol: bool | None = None,
|
|
46
44
|
) -> dict[str, Any]:
|
|
47
45
|
return {
|
|
48
|
-
"markets":
|
|
49
|
-
{
|
|
50
|
-
"chain_id": chain_id,
|
|
51
|
-
"token_address": "0xMockToken",
|
|
46
|
+
"markets": {
|
|
47
|
+
"0xMockToken": {
|
|
52
48
|
"symbol": "USDC",
|
|
53
|
-
"
|
|
54
|
-
"
|
|
49
|
+
"symbol_canonical": "usdc",
|
|
50
|
+
"display_symbol": "USDC",
|
|
51
|
+
"reserve": {},
|
|
52
|
+
"decimals": 6,
|
|
53
|
+
"headroom": 1000000000000,
|
|
54
|
+
"supply_cap": 5000000000000,
|
|
55
55
|
}
|
|
56
|
-
|
|
56
|
+
},
|
|
57
|
+
"notes": [],
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
async def get_assets_view(
|
|
60
61
|
self,
|
|
61
62
|
*,
|
|
62
|
-
chain_id: int,
|
|
63
63
|
user_address: str,
|
|
64
64
|
) -> dict[str, Any]:
|
|
65
65
|
return {
|
|
66
|
-
"
|
|
67
|
-
"
|
|
66
|
+
"block_number": 12345,
|
|
67
|
+
"user": user_address,
|
|
68
|
+
"native_balance_wei": 0,
|
|
69
|
+
"native_balance": 0.0,
|
|
68
70
|
"assets": [],
|
|
71
|
+
"account_data": {
|
|
72
|
+
"total_collateral_base": 0,
|
|
73
|
+
"total_debt_base": 0,
|
|
74
|
+
"available_borrows_base": 0,
|
|
75
|
+
"current_liquidation_threshold": 0,
|
|
76
|
+
"ltv": 0,
|
|
77
|
+
"health_factor_wad": 0,
|
|
78
|
+
"health_factor": 0.0,
|
|
79
|
+
},
|
|
80
|
+
"base_currency_info": {
|
|
81
|
+
"marketReferenceCurrencyUnit": 100000000,
|
|
82
|
+
"marketReferenceCurrencyPriceInUsd": 100000000,
|
|
83
|
+
"networkBaseTokenPriceInUsd": 0,
|
|
84
|
+
"networkBaseTokenPriceDecimals": 8,
|
|
85
|
+
},
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
async def get_market_entry(
|
|
72
89
|
self,
|
|
73
90
|
*,
|
|
74
|
-
|
|
75
|
-
token_address: str,
|
|
91
|
+
token: str,
|
|
76
92
|
) -> dict[str, Any]:
|
|
77
93
|
return {
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
94
|
+
"symbol": "USDC",
|
|
95
|
+
"symbol_canonical": "usdc",
|
|
96
|
+
"display_symbol": "USDC",
|
|
97
|
+
"reserve": {},
|
|
81
98
|
}
|
|
82
99
|
|
|
83
100
|
async def get_lend_rate_history(
|
|
84
101
|
self,
|
|
85
102
|
*,
|
|
86
|
-
|
|
87
|
-
token_address: str,
|
|
103
|
+
token: str,
|
|
88
104
|
lookback_hours: int,
|
|
105
|
+
force_refresh: bool | None = None,
|
|
89
106
|
) -> dict[str, Any]:
|
|
90
107
|
return {
|
|
91
|
-
"
|
|
92
|
-
"token_address": token_address,
|
|
93
|
-
"rates": [],
|
|
108
|
+
"history": [],
|
|
94
109
|
}
|
|
95
110
|
|
|
96
111
|
|
|
@@ -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
|
|
@@ -16,7 +15,6 @@ class StrategyJob:
|
|
|
16
15
|
config: StrategyJobConfig,
|
|
17
16
|
clients: dict[str, Any] | None = None,
|
|
18
17
|
skip_auth: bool = False,
|
|
19
|
-
api_key: str | None = None,
|
|
20
18
|
):
|
|
21
19
|
"""
|
|
22
20
|
Initialize a StrategyJob.
|
|
@@ -26,16 +24,12 @@ class StrategyJob:
|
|
|
26
24
|
config: Strategy job configuration.
|
|
27
25
|
clients: Optional dict of pre-instantiated clients to inject directly.
|
|
28
26
|
skip_auth: If True, skips authentication (for SDK usage).
|
|
29
|
-
api_key: Optional API key for service account authentication.
|
|
30
|
-
If provided, will be passed to ClientManager and strategy.
|
|
31
27
|
"""
|
|
32
28
|
self.strategy = strategy
|
|
33
29
|
self.config = config
|
|
34
30
|
|
|
35
31
|
self.job_id = strategy.name or "unknown"
|
|
36
|
-
self.clients = ClientManager(
|
|
37
|
-
clients=clients, skip_auth=skip_auth, api_key=api_key
|
|
38
|
-
)
|
|
32
|
+
self.clients = ClientManager(clients=clients, skip_auth=skip_auth)
|
|
39
33
|
|
|
40
34
|
def _setup_strategy(self):
|
|
41
35
|
"""Setup the strategy instance"""
|
|
@@ -44,23 +38,6 @@ class StrategyJob:
|
|
|
44
38
|
|
|
45
39
|
self.strategy.log = self.log
|
|
46
40
|
|
|
47
|
-
def _is_using_api_key(self) -> bool:
|
|
48
|
-
"""Check if API key authentication is being used."""
|
|
49
|
-
if self.clients._api_key:
|
|
50
|
-
return True
|
|
51
|
-
|
|
52
|
-
if self.clients.auth:
|
|
53
|
-
try:
|
|
54
|
-
creds = self.clients.auth._load_config_credentials()
|
|
55
|
-
if creds.get("api_key"):
|
|
56
|
-
return True
|
|
57
|
-
if os.getenv("WAYFINDER_API_KEY"):
|
|
58
|
-
return True
|
|
59
|
-
except Exception:
|
|
60
|
-
pass
|
|
61
|
-
|
|
62
|
-
return False
|
|
63
|
-
|
|
64
41
|
async def setup(self):
|
|
65
42
|
"""
|
|
66
43
|
Initialize the strategy job and strategy.
|
|
@@ -69,38 +46,13 @@ class StrategyJob:
|
|
|
69
46
|
"""
|
|
70
47
|
self._setup_strategy()
|
|
71
48
|
|
|
72
|
-
# Ensure
|
|
49
|
+
# Ensure API key is set for API calls
|
|
50
|
+
# All clients inherit from WayfinderClient and have _ensure_api_key()
|
|
73
51
|
if not self.clients._skip_auth:
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if
|
|
77
|
-
|
|
78
|
-
if self.clients.auth:
|
|
79
|
-
await self.clients.auth._ensure_bearer_token()
|
|
80
|
-
else:
|
|
81
|
-
# Try to ensure bearer token is set, authenticate if needed
|
|
82
|
-
try:
|
|
83
|
-
if self.clients.auth:
|
|
84
|
-
await self.clients.auth._ensure_bearer_token()
|
|
85
|
-
except (PermissionError, Exception) as e:
|
|
86
|
-
if not isinstance(e, PermissionError):
|
|
87
|
-
logger.warning(
|
|
88
|
-
f"Authentication failed: {e}, trying OAuth fallback"
|
|
89
|
-
)
|
|
90
|
-
username = self.config.user.username
|
|
91
|
-
password = self.config.user.password
|
|
92
|
-
refresh_token = self.config.user.refresh_token
|
|
93
|
-
if refresh_token or (username and password):
|
|
94
|
-
await self.clients.authenticate(
|
|
95
|
-
username=username,
|
|
96
|
-
password=password,
|
|
97
|
-
refresh_token=refresh_token,
|
|
98
|
-
)
|
|
99
|
-
else:
|
|
100
|
-
raise ValueError(
|
|
101
|
-
"Authentication required: provide api_key parameter for service account auth, "
|
|
102
|
-
"or username+password/refresh_token in config.json for personal access"
|
|
103
|
-
) from e
|
|
52
|
+
# Ensure API key on any client (they all share the same method)
|
|
53
|
+
token_client = self.clients.token
|
|
54
|
+
if token_client:
|
|
55
|
+
token_client._ensure_api_key()
|
|
104
56
|
|
|
105
57
|
existing_cfg = dict(getattr(self.strategy, "config", {}) or {})
|
|
106
58
|
strategy_cfg = dict(self.config.strategy_config or {})
|
|
@@ -155,13 +155,13 @@ class LocalEvmTxn(EvmTxn):
|
|
|
155
155
|
gas_price = await self._get_gas_price(w3)
|
|
156
156
|
|
|
157
157
|
transaction["gasPrice"] = int(gas_price * SUGGESTED_GAS_PRICE_MULTIPLIER)
|
|
158
|
-
elif chain_id == 999:
|
|
159
|
-
|
|
158
|
+
# elif chain_id == 999:
|
|
159
|
+
# big_block_gas_price = await w3.hype.big_block_gas_price()
|
|
160
160
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
161
|
+
# transaction["maxFeePerGas"] = int(
|
|
162
|
+
# big_block_gas_price * SUGGESTED_PRIORITY_FEE_MULTIPLIER
|
|
163
|
+
# )
|
|
164
|
+
# transaction["maxPriorityFeePerGas"] = 0
|
|
165
165
|
else:
|
|
166
166
|
base_fee = await self._get_base_fee(w3)
|
|
167
167
|
priority_fee = await self._get_priority_fee(w3)
|