wayfinder-paths 0.1.13__py3-none-any.whl → 0.1.15__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 (61) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +13 -14
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +73 -32
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +123 -0
  4. wayfinder_paths/adapters/brap_adapter/README.md +11 -16
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +144 -78
  6. wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
  7. wayfinder_paths/adapters/brap_adapter/test_adapter.py +127 -65
  8. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +30 -14
  9. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +121 -67
  10. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +6 -6
  11. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +12 -12
  12. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +6 -6
  13. wayfinder_paths/adapters/moonwell_adapter/adapter.py +332 -9
  14. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +13 -13
  15. wayfinder_paths/adapters/pool_adapter/README.md +9 -10
  16. wayfinder_paths/adapters/pool_adapter/adapter.py +9 -10
  17. wayfinder_paths/adapters/pool_adapter/test_adapter.py +2 -2
  18. wayfinder_paths/adapters/token_adapter/README.md +2 -14
  19. wayfinder_paths/adapters/token_adapter/adapter.py +16 -10
  20. wayfinder_paths/adapters/token_adapter/examples.json +4 -8
  21. wayfinder_paths/adapters/token_adapter/test_adapter.py +9 -7
  22. wayfinder_paths/core/clients/BRAPClient.py +102 -61
  23. wayfinder_paths/core/clients/ClientManager.py +1 -68
  24. wayfinder_paths/core/clients/HyperlendClient.py +125 -64
  25. wayfinder_paths/core/clients/LedgerClient.py +1 -4
  26. wayfinder_paths/core/clients/PoolClient.py +122 -48
  27. wayfinder_paths/core/clients/TokenClient.py +91 -36
  28. wayfinder_paths/core/clients/WalletClient.py +26 -56
  29. wayfinder_paths/core/clients/WayfinderClient.py +28 -160
  30. wayfinder_paths/core/clients/__init__.py +0 -2
  31. wayfinder_paths/core/clients/protocols.py +35 -46
  32. wayfinder_paths/core/clients/sdk_example.py +37 -22
  33. wayfinder_paths/core/constants/erc20_abi.py +0 -11
  34. wayfinder_paths/core/engine/StrategyJob.py +10 -56
  35. wayfinder_paths/core/services/base.py +1 -0
  36. wayfinder_paths/core/services/local_evm_txn.py +25 -9
  37. wayfinder_paths/core/services/local_token_txn.py +2 -6
  38. wayfinder_paths/core/services/test_local_evm_txn.py +145 -0
  39. wayfinder_paths/core/strategies/Strategy.py +16 -4
  40. wayfinder_paths/core/utils/evm_helpers.py +2 -9
  41. wayfinder_paths/policies/erc20.py +1 -1
  42. wayfinder_paths/run_strategy.py +13 -19
  43. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +77 -11
  44. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +6 -6
  45. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +107 -23
  46. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
  47. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +6 -5
  48. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2246 -1279
  49. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +276 -109
  50. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +1 -1
  51. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +153 -56
  52. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +16 -12
  53. wayfinder_paths/templates/adapter/README.md +1 -1
  54. wayfinder_paths/templates/strategy/README.md +3 -3
  55. wayfinder_paths/templates/strategy/test_strategy.py +3 -2
  56. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/METADATA +14 -49
  57. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/RECORD +59 -60
  58. wayfinder_paths/abis/generic/erc20.json +0 -383
  59. wayfinder_paths/core/clients/AuthClient.py +0 -83
  60. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/LICENSE +0 -0
  61. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/WHEEL +0 -0
@@ -7,83 +7,138 @@ from __future__ import annotations
7
7
 
8
8
  from typing import NotRequired, Required, TypedDict
9
9
 
10
- from wayfinder_paths.core.clients.AuthClient import AuthClient
11
10
  from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
12
11
  from wayfinder_paths.core.config import get_api_base_url
13
12
 
14
13
 
14
+ class TokenLinks(TypedDict):
15
+ """Token links structure"""
16
+
17
+ github: NotRequired[list[str]]
18
+ reddit: NotRequired[str]
19
+ discord: NotRequired[str]
20
+ twitter: NotRequired[str]
21
+ homepage: NotRequired[list[str]]
22
+ telegram: NotRequired[str]
23
+
24
+
25
+ class ChainAddress(TypedDict):
26
+ """Chain address structure"""
27
+
28
+ address: Required[str]
29
+ token_id: Required[str]
30
+ is_contract: NotRequired[bool]
31
+ chain_id: NotRequired[int]
32
+
33
+
34
+ class ChainInfo(TypedDict):
35
+ """Chain information structure"""
36
+
37
+ id: Required[int]
38
+ name: Required[str]
39
+ code: Required[str]
40
+
41
+
42
+ class TokenMetadata(TypedDict):
43
+ """Token metadata structure"""
44
+
45
+ query_processed: NotRequired[str]
46
+ query_type: NotRequired[str]
47
+ has_addresses: NotRequired[bool]
48
+ address_count: NotRequired[int]
49
+ has_price_data: NotRequired[bool]
50
+
51
+
15
52
  class TokenDetails(TypedDict):
16
53
  """Token details response structure"""
17
54
 
18
- id: Required[str]
19
- address: Required[str]
20
- symbol: Required[str]
55
+ asset_id: NotRequired[str]
56
+ token_ids: NotRequired[list[str]]
21
57
  name: Required[str]
58
+ symbol: Required[str]
22
59
  decimals: Required[int]
23
- chain_id: Required[int]
24
- chain_code: Required[str]
25
- price_usd: NotRequired[float]
26
- price: NotRequired[float]
60
+ description: NotRequired[str]
61
+ links: NotRequired[TokenLinks]
62
+ categories: NotRequired[list[str]]
63
+ current_price: NotRequired[float]
64
+ market_cap: NotRequired[float]
65
+ total_volume_usd_24h: NotRequired[float]
66
+ price_change_24h: NotRequired[float]
67
+ price_change_7d: NotRequired[float]
68
+ price_change_30d: NotRequired[float]
69
+ price_change_1y: NotRequired[float]
70
+ addresses: NotRequired[dict[str, str]]
71
+ chain_addresses: NotRequired[dict[str, ChainAddress]]
72
+ chain_ids: NotRequired[dict[str, int]]
73
+ id: NotRequired[int]
74
+ token_id: Required[str]
75
+ address: Required[str]
76
+ chain: NotRequired[ChainInfo]
77
+ query: NotRequired[str]
78
+ query_type: NotRequired[str]
79
+ metadata: NotRequired[TokenMetadata]
27
80
  image_url: NotRequired[str | None]
28
- coingecko_id: NotRequired[str | None]
29
81
 
30
82
 
31
83
  class GasToken(TypedDict):
32
84
  """Gas token response structure"""
33
85
 
34
86
  id: Required[str]
35
- address: Required[str]
36
- symbol: Required[str]
87
+ coingecko_id: NotRequired[str]
88
+ token_id: Required[str]
37
89
  name: Required[str]
90
+ symbol: Required[str]
91
+ address: Required[str]
38
92
  decimals: Required[int]
39
- chain_id: Required[int]
40
- chain_code: Required[str]
41
- price_usd: NotRequired[float]
93
+ chain: NotRequired[ChainInfo]
42
94
 
43
95
 
44
96
  class TokenClient(WayfinderClient):
45
97
  """Adapter for token-related operations"""
46
98
 
47
- def __init__(self, api_key: str | None = None):
48
- super().__init__(api_key=api_key)
49
- self.api_base_url = f"{self.api_base_url}/tokens"
50
- self._auth_client: AuthClient | None = AuthClient(api_key=api_key)
51
-
52
- # ============== Public (No-Auth) Endpoints ==============
99
+ def __init__(self):
100
+ super().__init__()
101
+ self.api_base_url = f"{get_api_base_url()}/v1/blockchain/tokens"
53
102
 
54
103
  async def get_token_details(
55
- self, token_id: str, force_refresh: bool = False
104
+ self, query: str, market_data: bool = False, chain_id: int | None = None
56
105
  ) -> TokenDetails:
57
106
  """
58
- Get token data including price from the token-details endpoint
107
+ Get token data including price from the token-details endpoint.
59
108
 
60
109
  Args:
61
- token_id: Token identifier or address
110
+ query: Token identifier, address, or symbol to query
111
+ market_data: Whether to include market data (default: True)
112
+ chain_id: Optional chain ID
62
113
 
63
114
  Returns:
64
115
  Full token data including price information
65
116
  """
66
- url = f"{get_api_base_url()}/public/tokens/detail/"
117
+ url = f"{self.api_base_url}/detail/"
67
118
  params = {
68
- "query": token_id,
69
- "get_chart": "false",
70
- "force_refresh": str(force_refresh),
119
+ "query": query,
120
+ "market_data": market_data,
71
121
  }
72
- # Public endpoint: do not pass auth headers
73
- response = await self._request("GET", url, params=params, headers={})
122
+ if chain_id is not None:
123
+ params["chain_id"] = chain_id
124
+ response = await self._authed_request("GET", url, params=params)
74
125
  response.raise_for_status()
75
126
  data = response.json()
76
127
  return data.get("data", data)
77
128
 
78
- async def get_gas_token(self, chain_code: str) -> GasToken:
129
+ async def get_gas_token(self, query: str) -> GasToken:
79
130
  """
80
- Fetch the native gas token for a given chain code via public endpoint.
81
- Example: GET /api/v1/public/tokens/gas/?chain_code=base
131
+ Fetch the native gas token for a given chain code or query.
132
+
133
+ Args:
134
+ query: Chain code or query string
135
+
136
+ Returns:
137
+ Gas token information including chain details
82
138
  """
83
- url = f"{get_api_base_url()}/public/tokens/gas/"
84
- params = {"chain_code": chain_code}
85
- # Public endpoint: do not pass auth headers
86
- response = await self._request("GET", url, params=params, headers={})
139
+ url = f"{self.api_base_url}/gas/"
140
+ params = {"query": query}
141
+ response = await self._authed_request("GET", url, params=params)
87
142
  response.raise_for_status()
88
143
  data = response.json()
89
144
  return data.get("data", data)
@@ -7,80 +7,50 @@ from __future__ import annotations
7
7
 
8
8
  from typing import NotRequired, Required, TypedDict
9
9
 
10
- from wayfinder_paths.core.clients.AuthClient import AuthClient
11
10
  from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
12
11
  from wayfinder_paths.core.config import get_api_base_url
13
12
 
14
13
 
15
- class TokenBalance(TypedDict):
16
- """Token balance response structure"""
14
+ class AddressBalance(TypedDict):
15
+ """Balance response structure for address/query lookups."""
17
16
 
18
- token_id: Required[str]
19
- wallet_address: Required[str]
20
- balance: Required[str]
21
- balance_human: NotRequired[float | None]
22
- usd_value: NotRequired[float | None]
23
-
24
-
25
- class PoolBalance(TypedDict):
26
- """Pool balance response structure"""
27
-
28
- pool_address: Required[str]
29
- chain_id: Required[int]
30
- user_address: Required[str]
31
- balance: Required[str]
17
+ balance: Required[int]
32
18
  balance_human: NotRequired[float | None]
33
19
  usd_value: NotRequired[float | None]
20
+ address: NotRequired[str]
21
+ token_id: NotRequired[str | None]
22
+ wallet_address: NotRequired[str]
23
+ chain_id: NotRequired[int]
34
24
 
35
25
 
36
26
  class WalletClient(WayfinderClient):
37
- def __init__(self, api_key: str | None = None):
38
- super().__init__(api_key=api_key)
27
+ def __init__(self):
28
+ super().__init__()
39
29
  self.api_base_url = get_api_base_url()
40
- self._auth_client = AuthClient(api_key=api_key)
41
30
 
42
- async def get_token_balance_for_wallet(
31
+ async def get_token_balance_for_address(
43
32
  self,
44
33
  *,
45
- token_id: str,
46
34
  wallet_address: str,
47
- human_readable: bool = True,
48
- ) -> TokenBalance:
35
+ query: str,
36
+ chain_id: int | None = None,
37
+ ) -> AddressBalance:
49
38
  """
50
- Fetch a single token balance for an explicit wallet address.
39
+ Fetch a balance for a wallet address + chain + query.
51
40
 
52
- Mirrors POST /api/v1/public/balances/token/
41
+ Args:
42
+ wallet_address: Wallet address
43
+ query: Token address, pool address, or equivalent identifier
44
+ chain_id: Chain ID (required)
53
45
  """
54
- url = f"{self.api_base_url}/public/balances/token/"
55
- payload = {
56
- "token_id": token_id,
57
- "wallet_address": wallet_address,
58
- "human_readable": human_readable,
59
- }
60
- response = await self._authed_request("POST", url, json=payload)
61
- data = response.json()
62
- return data.get("data", data)
46
+ if chain_id is None:
47
+ raise ValueError("chain_id is required")
63
48
 
64
- async def get_pool_balance_for_wallet(
65
- self,
66
- *,
67
- pool_address: str,
68
- chain_id: int,
69
- user_address: str,
70
- human_readable: bool = True,
71
- ) -> PoolBalance:
72
- """
73
- Fetch a wallet's LP/share balance for a given pool address and chain.
74
-
75
- Mirrors POST /api/v1/public/balances/pool/
76
- """
77
- url = f"{self.api_base_url}/public/balances/pool/"
78
- payload = {
79
- "pool_address": pool_address,
49
+ url = f"{self.api_base_url}/v1/blockchain/balances/address/"
50
+ params = {
51
+ "wallet_address": wallet_address,
80
52
  "chain_id": chain_id,
81
- "user_address": user_address,
82
- "human_readable": human_readable,
53
+ "query": query,
83
54
  }
84
- response = await self._authed_request("POST", url, json=payload)
85
- data = response.json()
86
- return data.get("data", data)
55
+ response = await self._authed_request("GET", url, params=params)
56
+ return response.json()
@@ -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, api_key: str | None = None):
13
+ def __init__(self):
15
14
  """
16
15
  Initialize WayfinderClient.
17
16
 
18
- Args:
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 Authorization headers (useful for logout or auth mode switch)."""
50
- self.headers.pop("Authorization", None)
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 credentials from config.json.
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
- return {
99
- "username": user.get("username"),
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
- async def _ensure_bearer_token(self) -> bool:
52
+ def _ensure_api_key(self) -> bool:
122
53
  """
123
- Ensure Authorization header is set. Priority: existing header > constructor api_key > config.json api_key > config.json tokens > username/password.
124
- Raises PermissionError if no credentials found.
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("Authorization"):
57
+ if self.headers.get("X-API-KEY"):
127
58
  return True
128
59
 
129
- # Check for API key: constructor > config.json
130
- api_key = self._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["Authorization"] = f"Bearer {api_key}"
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 (via constructor or config.json) for service account auth, "
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 = True,
80
+ retry_on_401: bool = False,
186
81
  **kwargs: Any,
187
82
  ) -> httpx.Response:
188
83
  """
189
- Wrapper around httpx that injects headers and auto-refreshes tokens on 401 once.
190
- Ensures API key or bearer token is set in headers when available (for service account auth and rate limits).
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 or bearer token is set in headers if available and not already set
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("Authorization"):
198
- # Try to get API key from constructor or config
199
- api_key = self._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["Authorization"] = f"Bearer {api_key}"
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 Authorization (via env/config creds) and perform the request.
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
- ok = await self._ensure_bearer_token()
254
- if not ok:
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",