wayfinder-paths 0.1.11__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.

Files changed (66) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +13 -14
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +36 -39
  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 +87 -75
  6. wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
  7. wayfinder_paths/adapters/brap_adapter/test_adapter.py +121 -59
  8. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +22 -23
  9. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +114 -60
  10. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1 -1
  11. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +44 -5
  12. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +104 -0
  13. wayfinder_paths/adapters/moonwell_adapter/adapter.py +0 -3
  14. wayfinder_paths/adapters/pool_adapter/README.md +11 -27
  15. wayfinder_paths/adapters/pool_adapter/adapter.py +11 -37
  16. wayfinder_paths/adapters/pool_adapter/examples.json +6 -7
  17. wayfinder_paths/adapters/pool_adapter/test_adapter.py +8 -8
  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 +5 -3
  22. wayfinder_paths/core/clients/BRAPClient.py +103 -62
  23. wayfinder_paths/core/clients/ClientManager.py +1 -68
  24. wayfinder_paths/core/clients/HyperlendClient.py +127 -66
  25. wayfinder_paths/core/clients/LedgerClient.py +1 -4
  26. wayfinder_paths/core/clients/PoolClient.py +126 -88
  27. wayfinder_paths/core/clients/TokenClient.py +92 -37
  28. wayfinder_paths/core/clients/WalletClient.py +28 -58
  29. wayfinder_paths/core/clients/WayfinderClient.py +33 -166
  30. wayfinder_paths/core/clients/__init__.py +0 -2
  31. wayfinder_paths/core/clients/protocols.py +35 -52
  32. wayfinder_paths/core/clients/sdk_example.py +37 -22
  33. wayfinder_paths/core/config.py +60 -224
  34. wayfinder_paths/core/engine/StrategyJob.py +7 -55
  35. wayfinder_paths/core/services/local_evm_txn.py +28 -10
  36. wayfinder_paths/core/services/local_token_txn.py +1 -1
  37. wayfinder_paths/core/strategies/Strategy.py +3 -5
  38. wayfinder_paths/core/strategies/descriptors.py +7 -0
  39. wayfinder_paths/core/utils/evm_helpers.py +7 -3
  40. wayfinder_paths/core/utils/wallets.py +12 -19
  41. wayfinder_paths/core/wallets/README.md +1 -1
  42. wayfinder_paths/run_strategy.py +8 -17
  43. wayfinder_paths/scripts/create_strategy.py +5 -5
  44. wayfinder_paths/scripts/make_wallets.py +5 -5
  45. wayfinder_paths/scripts/run_strategy.py +3 -3
  46. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -1
  47. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +206 -526
  48. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +228 -11
  49. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
  50. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +41 -25
  51. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
  52. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +1 -1
  53. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +10 -9
  54. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +12 -6
  55. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +3 -3
  56. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +110 -78
  57. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +44 -21
  58. wayfinder_paths/templates/adapter/README.md +1 -1
  59. wayfinder_paths/templates/strategy/README.md +3 -3
  60. wayfinder_paths/templates/strategy/test_strategy.py +3 -2
  61. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/METADATA +21 -59
  62. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/RECORD +64 -65
  63. wayfinder_paths/core/clients/AuthClient.py +0 -83
  64. wayfinder_paths/core/settings.py +0 -61
  65. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/LICENSE +0 -0
  66. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.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
- from wayfinder_paths.core.settings import settings
11
+ from wayfinder_paths.core.config import get_api_base_url
12
+
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]
13
50
 
14
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"{settings.WAYFINDER_API_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"{settings.WAYFINDER_API_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
- from wayfinder_paths.core.settings import settings
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)
39
- self.api_base_url = f"{settings.WAYFINDER_API_URL}"
40
- self._auth_client = AuthClient(api_key=api_key)
27
+ def __init__(self):
28
+ super().__init__()
29
+ self.api_base_url = get_api_base_url()
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,25 +1,22 @@
1
1
  import json
2
- import os
3
2
  import time
4
3
  from typing import Any
5
4
 
6
5
  import httpx
7
6
  from loguru import logger
8
7
 
8
+ from wayfinder_paths.core.config import get_api_base_url
9
9
  from wayfinder_paths.core.constants.base import DEFAULT_HTTP_TIMEOUT
10
- from wayfinder_paths.core.settings import settings
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
- self.api_base_url = f"{settings.WAYFINDER_API_URL}/"
19
+ self.api_base_url = f"{get_api_base_url()}/"
23
20
  timeout = httpx.Timeout(DEFAULT_HTTP_TIMEOUT)
24
21
  self.client = httpx.AsyncClient(timeout=timeout)
25
22
 
@@ -27,154 +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 Django-backed 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"{settings.WAYFINDER_API_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. Path can be overridden via WAYFINDER_CONFIG_PATH.
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
- path = os.getenv("WAYFINDER_CONFIG_PATH", "config.json")
94
39
  try:
95
- with open(path) as f:
40
+ with open("config.json") as f:
96
41
  cfg = json.load(f)
97
- user = cfg.get("user", {}) if isinstance(cfg, dict) else {}
98
42
  system = cfg.get("system", {}) if isinstance(cfg, dict) else {}
99
- return {
100
- "username": user.get("username"),
101
- "password": user.get("password"),
102
- "refresh_token": user.get("refresh_token"),
103
- "api_key": user.get("api_key") or system.get("api_key"),
104
- }
43
+ api_key = system.get("api_key")
44
+ return {"api_key": api_key}
105
45
  except (FileNotFoundError, json.JSONDecodeError, OSError) as e:
106
- logger.debug(f"Could not load config file at {path}: {e}")
107
- return {
108
- "username": None,
109
- "password": None,
110
- "refresh_token": None,
111
- "api_key": None,
112
- }
46
+ logger.debug(f"Could not load config file at config.json: {e}")
47
+ return {"api_key": None}
113
48
  except Exception as e:
114
- logger.warning(f"Unexpected error loading config file at {path}: {e}")
115
- return {
116
- "username": None,
117
- "password": None,
118
- "refresh_token": None,
119
- "api_key": None,
120
- }
49
+ logger.warning(f"Unexpected error loading config file at config.json: {e}")
50
+ return {"api_key": None}
121
51
 
122
- async def _ensure_bearer_token(self) -> bool:
52
+ def _ensure_api_key(self) -> bool:
123
53
  """
124
- Ensure Authorization header is set. Priority: existing header > constructor api_key > config.json api_key > config.json tokens > username/password.
125
- 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.
126
56
  """
127
- if self.headers.get("Authorization"):
57
+ if self.headers.get("X-API-KEY"):
128
58
  return True
129
59
 
130
- # Check for API key: constructor > config.json
131
- api_key = self._api_key
132
- if not api_key:
133
- creds = self._load_config_credentials()
134
- 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")
135
62
 
136
63
  if api_key:
137
64
  api_key = api_key.strip() if isinstance(api_key, str) else api_key
138
65
  if not api_key:
139
66
  raise ValueError("API key cannot be empty")
140
- self.headers["Authorization"] = f"Bearer {api_key}"
67
+ self.headers["X-API-KEY"] = api_key
141
68
  return True
142
69
 
143
- # Fall back to OAuth token-based auth
144
- creds = self._load_config_credentials()
145
- refresh = creds.get("refresh_token")
146
-
147
- if refresh:
148
- self._refresh_token = refresh
149
- refreshed = await self._refresh_access_token()
150
- if refreshed:
151
- return True
152
-
153
- username = creds.get("username")
154
- password = creds.get("password")
155
-
156
- if username and password:
157
- try:
158
- url = f"{settings.WAYFINDER_API_URL}/auth/token/"
159
- payload = {"username": username, "password": password}
160
- response = await self.client.post(
161
- url,
162
- json=payload,
163
- headers={"Content-Type": "application/json"},
164
- )
165
- if response.status_code == 200:
166
- data = response.json()
167
- access = data.get("access") or data.get("access_token")
168
- refresh = data.get("refresh") or data.get("refresh_token")
169
- self.set_tokens(access, refresh)
170
- return bool(access)
171
- except Exception as e:
172
- logger.debug(f"Failed to authenticate with username/password: {e}")
173
- pass
174
-
175
70
  raise PermissionError(
176
- "Not authenticated: provide api_key (via constructor or config.json) for service account auth, "
177
- "or username+password/refresh_token in config.json for personal access"
71
+ "Not authenticated: provide api_key in system.api_key in config.json"
178
72
  )
179
73
 
180
74
  async def _request(
@@ -183,49 +77,32 @@ class WayfinderClient:
183
77
  url: str,
184
78
  *,
185
79
  headers: dict[str, str] | None = None,
186
- retry_on_401: bool = True,
80
+ retry_on_401: bool = False,
187
81
  **kwargs: Any,
188
82
  ) -> httpx.Response:
189
83
  """
190
- Wrapper around httpx that injects headers and auto-refreshes tokens on 401 once.
191
- 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).
192
86
  """
193
87
  logger.debug(f"Making {method} request to {url}")
194
88
  start_time = time.time()
195
89
 
196
- # 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
197
91
  # This ensures API keys are passed to all endpoints (including public ones) for rate limiting
198
- if not self.headers.get("Authorization"):
199
- # Try to get API key from constructor or config
200
- api_key = self._api_key
201
- if not api_key:
202
- creds = self._load_config_credentials()
203
- 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")
204
95
 
205
96
  if api_key:
206
97
  api_key = api_key.strip() if isinstance(api_key, str) else api_key
207
98
  if api_key:
208
- self.headers["Authorization"] = f"Bearer {api_key}"
99
+ self.headers["X-API-KEY"] = api_key
209
100
 
210
101
  merged_headers = dict(self.headers)
211
102
  if headers:
212
103
  merged_headers.update(headers)
213
104
  resp = await self.client.request(method, url, headers=merged_headers, **kwargs)
214
105
 
215
- if resp.status_code == 401 and retry_on_401 and self._refresh_token:
216
- logger.info("Received 401, attempting token refresh and retry")
217
- refreshed = await self._refresh_access_token()
218
- if refreshed:
219
- merged_headers = dict(self.headers)
220
- if headers:
221
- merged_headers.update(headers)
222
- resp = await self.client.request(
223
- method, url, headers=merged_headers, **kwargs
224
- )
225
- logger.info("Retry after token refresh successful")
226
- else:
227
- logger.error("Token refresh failed, request will fail")
228
-
229
106
  elapsed = time.time() - start_time
230
107
  if resp.status_code >= 400:
231
108
  logger.warning(
@@ -248,17 +125,7 @@ class WayfinderClient:
248
125
  **kwargs: Any,
249
126
  ) -> httpx.Response:
250
127
  """
251
- Ensure Authorization (via env/config creds) and perform the request.
252
- 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.
253
129
  """
254
- ok = await self._ensure_bearer_token()
255
- if not ok:
256
- raise PermissionError("Not authenticated: set env tokens or credentials")
257
- try:
258
- return await self._request(method, url, headers=headers, **kwargs)
259
- except httpx.HTTPStatusError as e:
260
- if e.response is not None and e.response.status_code == 401:
261
- # Retry after attempting re-acquire/refresh
262
- await self._ensure_bearer_token()
263
- return await self._request(method, url, headers=headers, **kwargs)
264
- 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",