wayfinder-paths 0.1.7__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 (149) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +399 -0
  2. wayfinder_paths/__init__.py +22 -0
  3. wayfinder_paths/abis/generic/erc20.json +383 -0
  4. wayfinder_paths/adapters/__init__.py +0 -0
  5. wayfinder_paths/adapters/balance_adapter/README.md +94 -0
  6. wayfinder_paths/adapters/balance_adapter/adapter.py +238 -0
  7. wayfinder_paths/adapters/balance_adapter/examples.json +6 -0
  8. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  9. wayfinder_paths/adapters/balance_adapter/test_adapter.py +59 -0
  10. wayfinder_paths/adapters/brap_adapter/README.md +249 -0
  11. wayfinder_paths/adapters/brap_adapter/__init__.py +7 -0
  12. wayfinder_paths/adapters/brap_adapter/adapter.py +726 -0
  13. wayfinder_paths/adapters/brap_adapter/examples.json +175 -0
  14. wayfinder_paths/adapters/brap_adapter/manifest.yaml +11 -0
  15. wayfinder_paths/adapters/brap_adapter/test_adapter.py +286 -0
  16. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
  17. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +305 -0
  18. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +10 -0
  19. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +274 -0
  20. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +18 -0
  21. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1093 -0
  22. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +549 -0
  23. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +8 -0
  24. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +1050 -0
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +126 -0
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +219 -0
  27. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +220 -0
  28. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +134 -0
  29. wayfinder_paths/adapters/ledger_adapter/README.md +145 -0
  30. wayfinder_paths/adapters/ledger_adapter/__init__.py +7 -0
  31. wayfinder_paths/adapters/ledger_adapter/adapter.py +289 -0
  32. wayfinder_paths/adapters/ledger_adapter/examples.json +137 -0
  33. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +11 -0
  34. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +205 -0
  35. wayfinder_paths/adapters/pool_adapter/README.md +206 -0
  36. wayfinder_paths/adapters/pool_adapter/__init__.py +7 -0
  37. wayfinder_paths/adapters/pool_adapter/adapter.py +282 -0
  38. wayfinder_paths/adapters/pool_adapter/examples.json +143 -0
  39. wayfinder_paths/adapters/pool_adapter/manifest.yaml +10 -0
  40. wayfinder_paths/adapters/pool_adapter/test_adapter.py +220 -0
  41. wayfinder_paths/adapters/token_adapter/README.md +101 -0
  42. wayfinder_paths/adapters/token_adapter/__init__.py +3 -0
  43. wayfinder_paths/adapters/token_adapter/adapter.py +96 -0
  44. wayfinder_paths/adapters/token_adapter/examples.json +26 -0
  45. wayfinder_paths/adapters/token_adapter/manifest.yaml +6 -0
  46. wayfinder_paths/adapters/token_adapter/test_adapter.py +125 -0
  47. wayfinder_paths/config.example.json +22 -0
  48. wayfinder_paths/conftest.py +31 -0
  49. wayfinder_paths/core/__init__.py +18 -0
  50. wayfinder_paths/core/adapters/BaseAdapter.py +65 -0
  51. wayfinder_paths/core/adapters/__init__.py +5 -0
  52. wayfinder_paths/core/adapters/base.py +5 -0
  53. wayfinder_paths/core/adapters/models.py +46 -0
  54. wayfinder_paths/core/analytics/__init__.py +11 -0
  55. wayfinder_paths/core/analytics/bootstrap.py +57 -0
  56. wayfinder_paths/core/analytics/stats.py +48 -0
  57. wayfinder_paths/core/analytics/test_analytics.py +170 -0
  58. wayfinder_paths/core/clients/AuthClient.py +83 -0
  59. wayfinder_paths/core/clients/BRAPClient.py +109 -0
  60. wayfinder_paths/core/clients/ClientManager.py +210 -0
  61. wayfinder_paths/core/clients/HyperlendClient.py +192 -0
  62. wayfinder_paths/core/clients/LedgerClient.py +443 -0
  63. wayfinder_paths/core/clients/PoolClient.py +128 -0
  64. wayfinder_paths/core/clients/SimulationClient.py +192 -0
  65. wayfinder_paths/core/clients/TokenClient.py +89 -0
  66. wayfinder_paths/core/clients/TransactionClient.py +63 -0
  67. wayfinder_paths/core/clients/WalletClient.py +94 -0
  68. wayfinder_paths/core/clients/WayfinderClient.py +269 -0
  69. wayfinder_paths/core/clients/__init__.py +48 -0
  70. wayfinder_paths/core/clients/protocols.py +392 -0
  71. wayfinder_paths/core/clients/sdk_example.py +110 -0
  72. wayfinder_paths/core/config.py +458 -0
  73. wayfinder_paths/core/constants/__init__.py +26 -0
  74. wayfinder_paths/core/constants/base.py +42 -0
  75. wayfinder_paths/core/constants/erc20_abi.py +118 -0
  76. wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
  77. wayfinder_paths/core/engine/StrategyJob.py +188 -0
  78. wayfinder_paths/core/engine/__init__.py +5 -0
  79. wayfinder_paths/core/engine/manifest.py +97 -0
  80. wayfinder_paths/core/services/__init__.py +0 -0
  81. wayfinder_paths/core/services/base.py +179 -0
  82. wayfinder_paths/core/services/local_evm_txn.py +430 -0
  83. wayfinder_paths/core/services/local_token_txn.py +231 -0
  84. wayfinder_paths/core/services/web3_service.py +45 -0
  85. wayfinder_paths/core/settings.py +61 -0
  86. wayfinder_paths/core/strategies/Strategy.py +280 -0
  87. wayfinder_paths/core/strategies/__init__.py +5 -0
  88. wayfinder_paths/core/strategies/base.py +7 -0
  89. wayfinder_paths/core/strategies/descriptors.py +81 -0
  90. wayfinder_paths/core/utils/__init__.py +1 -0
  91. wayfinder_paths/core/utils/evm_helpers.py +206 -0
  92. wayfinder_paths/core/utils/wallets.py +77 -0
  93. wayfinder_paths/core/wallets/README.md +91 -0
  94. wayfinder_paths/core/wallets/WalletManager.py +56 -0
  95. wayfinder_paths/core/wallets/__init__.py +7 -0
  96. wayfinder_paths/policies/enso.py +17 -0
  97. wayfinder_paths/policies/erc20.py +34 -0
  98. wayfinder_paths/policies/evm.py +21 -0
  99. wayfinder_paths/policies/hyper_evm.py +19 -0
  100. wayfinder_paths/policies/hyperlend.py +12 -0
  101. wayfinder_paths/policies/hyperliquid.py +30 -0
  102. wayfinder_paths/policies/moonwell.py +54 -0
  103. wayfinder_paths/policies/prjx.py +30 -0
  104. wayfinder_paths/policies/util.py +27 -0
  105. wayfinder_paths/run_strategy.py +411 -0
  106. wayfinder_paths/scripts/__init__.py +0 -0
  107. wayfinder_paths/scripts/create_strategy.py +181 -0
  108. wayfinder_paths/scripts/make_wallets.py +169 -0
  109. wayfinder_paths/scripts/run_strategy.py +124 -0
  110. wayfinder_paths/scripts/validate_manifests.py +213 -0
  111. wayfinder_paths/strategies/__init__.py +0 -0
  112. wayfinder_paths/strategies/basis_trading_strategy/README.md +213 -0
  113. wayfinder_paths/strategies/basis_trading_strategy/__init__.py +3 -0
  114. wayfinder_paths/strategies/basis_trading_strategy/constants.py +1 -0
  115. wayfinder_paths/strategies/basis_trading_strategy/examples.json +16 -0
  116. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +23 -0
  117. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1011 -0
  118. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +4522 -0
  119. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +727 -0
  120. wayfinder_paths/strategies/basis_trading_strategy/types.py +39 -0
  121. wayfinder_paths/strategies/config.py +85 -0
  122. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +100 -0
  123. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +8 -0
  124. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
  125. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +2270 -0
  126. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +352 -0
  127. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +96 -0
  128. wayfinder_paths/strategies/stablecoin_yield_strategy/examples.json +17 -0
  129. wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
  130. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1810 -0
  131. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +520 -0
  132. wayfinder_paths/templates/adapter/README.md +105 -0
  133. wayfinder_paths/templates/adapter/adapter.py +26 -0
  134. wayfinder_paths/templates/adapter/examples.json +8 -0
  135. wayfinder_paths/templates/adapter/manifest.yaml +6 -0
  136. wayfinder_paths/templates/adapter/test_adapter.py +49 -0
  137. wayfinder_paths/templates/strategy/README.md +153 -0
  138. wayfinder_paths/templates/strategy/examples.json +11 -0
  139. wayfinder_paths/templates/strategy/manifest.yaml +8 -0
  140. wayfinder_paths/templates/strategy/strategy.py +57 -0
  141. wayfinder_paths/templates/strategy/test_strategy.py +197 -0
  142. wayfinder_paths/tests/__init__.py +0 -0
  143. wayfinder_paths/tests/test_smoke_manifest.py +48 -0
  144. wayfinder_paths/tests/test_test_coverage.py +212 -0
  145. wayfinder_paths/tests/test_utils.py +64 -0
  146. wayfinder_paths-0.1.7.dist-info/LICENSE +21 -0
  147. wayfinder_paths-0.1.7.dist-info/METADATA +777 -0
  148. wayfinder_paths-0.1.7.dist-info/RECORD +149 -0
  149. wayfinder_paths-0.1.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,83 @@
1
+ import os
2
+ from typing import Any
3
+
4
+ from loguru import logger
5
+
6
+ from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
7
+ from wayfinder_paths.core.settings import settings
8
+
9
+
10
+ class AuthClient(WayfinderClient):
11
+ def __init__(self, api_key: str | None = None):
12
+ """
13
+ Initialize AuthClient.
14
+
15
+ Args:
16
+ api_key: Optional API key for service account authentication.
17
+ If provided, uses API key auth. Otherwise falls back to config.json.
18
+ """
19
+ super().__init__(api_key=api_key)
20
+
21
+ self.api_base_url = f"{settings.WAYFINDER_API_URL}"
22
+ self.logger = logger.bind(client="AuthClient")
23
+
24
+ def _is_using_api_key(self) -> bool:
25
+ """Check if API key authentication is being used."""
26
+ if self._api_key:
27
+ return True
28
+
29
+ try:
30
+ creds = self._load_config_credentials()
31
+ if creds.get("api_key"):
32
+ return True
33
+ if os.getenv("WAYFINDER_API_KEY"):
34
+ return True
35
+ except Exception:
36
+ pass
37
+
38
+ return False
39
+
40
+ async def authenticate(
41
+ self,
42
+ username: str | None = None,
43
+ password: str | None = None,
44
+ *,
45
+ refresh_token: str | None = None,
46
+ ) -> dict[str, Any]:
47
+ """
48
+ Obtain an access token via username/password or refresh token.
49
+
50
+ Expected endpoints:
51
+ - POST {api_base_url}/token/ (username, password) -> { access, refresh }
52
+ - POST {api_base_url}/token/refresh/ (refresh) -> { access }
53
+ """
54
+ if refresh_token:
55
+ self.logger.debug(
56
+ "AuthClient.authenticate -> POST /token/refresh (refresh provided)"
57
+ )
58
+ url = f"{self.api_base_url}/auth/token/refresh/"
59
+ payload = {"refresh": refresh_token}
60
+ elif username and password:
61
+ self.logger.debug(
62
+ f"AuthClient.authenticate -> POST /token (username provided={bool(username)})"
63
+ )
64
+ url = f"{self.api_base_url}/auth/token/"
65
+ payload = {"username": username, "password": password}
66
+ else:
67
+ raise ValueError(
68
+ "Credentials required: provide username+password or refresh_token"
69
+ )
70
+
71
+ response = await self._request("POST", url, json=payload)
72
+ response.raise_for_status()
73
+ data = response.json()
74
+
75
+ access = data.get("access") or data.get("access_token")
76
+ refresh = data.get("refresh") or data.get("refresh_token")
77
+ if access or refresh:
78
+ self.set_tokens(access, refresh)
79
+ self.logger.debug(
80
+ f"AuthClient.authenticate <- success (access={'yes' if access else 'no'}, refresh={'yes' if refresh else 'no'})"
81
+ )
82
+
83
+ return data
@@ -0,0 +1,109 @@
1
+ """
2
+ BRAP (Bridge/Router/Adapter Protocol) Client
3
+ Provides access to quote operations via the public quote endpoint.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import time
9
+ from typing import Any, NotRequired, Required, TypedDict
10
+
11
+ from loguru import logger
12
+
13
+ from wayfinder_paths.core.clients.AuthClient import AuthClient
14
+ from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
15
+ from wayfinder_paths.core.settings import settings
16
+
17
+
18
+ class BRAPQuote(TypedDict):
19
+ """BRAP quote response structure"""
20
+
21
+ from_token_address: Required[str]
22
+ to_token_address: Required[str]
23
+ from_chain_id: Required[int]
24
+ to_chain_id: Required[int]
25
+ from_address: Required[str]
26
+ to_address: Required[str]
27
+ amount1: Required[str]
28
+ amount2: NotRequired[str]
29
+ routes: NotRequired[list[dict[str, Any]]]
30
+ fees: NotRequired[dict[str, Any] | None]
31
+ slippage: NotRequired[float | None]
32
+ wayfinder_fee: NotRequired[float | None]
33
+
34
+
35
+ class BRAPClient(WayfinderClient):
36
+ """Client for BRAP quote operations"""
37
+
38
+ def __init__(self, api_key: str | None = None):
39
+ super().__init__(api_key=api_key)
40
+ self.api_base_url = f"{settings.WAYFINDER_API_URL}"
41
+ self._auth_client: AuthClient | None = AuthClient(api_key=api_key)
42
+
43
+ async def get_quote(
44
+ self,
45
+ *,
46
+ from_token_address: str,
47
+ to_token_address: str,
48
+ from_chain_id: int,
49
+ to_chain_id: int,
50
+ from_address: str,
51
+ to_address: str,
52
+ amount1: str,
53
+ slippage: float | None = None,
54
+ wayfinder_fee: float | None = None,
55
+ ) -> BRAPQuote:
56
+ """
57
+ Get a quote for a bridge/swap operation.
58
+
59
+ Args:
60
+ from_token_address: Source token contract address
61
+ to_token_address: Destination token contract address
62
+ from_chain_id: Source chain ID
63
+ to_chain_id: Destination chain ID
64
+ from_address: Source wallet address
65
+ to_address: Destination wallet address
66
+ amount1: Amount to swap (in smallest units)
67
+ slippage: Maximum slippage tolerance (optional)
68
+ wayfinder_fee: Wayfinder fee (optional)
69
+
70
+ Returns:
71
+ Quote data including routes, amounts, fees, etc.
72
+ """
73
+ logger.info(
74
+ f"Getting BRAP quote: {from_token_address} -> {to_token_address} (chain {from_chain_id} -> {to_chain_id})"
75
+ )
76
+ logger.debug(
77
+ f"Quote params: amount={amount1}, slippage={slippage}, wayfinder_fee={wayfinder_fee}"
78
+ )
79
+ start_time = time.time()
80
+
81
+ url = f"{self.api_base_url}/public/quotes/"
82
+
83
+ payload = {
84
+ "from_token_address": from_token_address,
85
+ "to_token_address": to_token_address,
86
+ "from_chain_id": from_chain_id,
87
+ "to_chain_id": to_chain_id,
88
+ "from_address": from_address,
89
+ "to_address": to_address,
90
+ "amount1": amount1,
91
+ }
92
+
93
+ # Only add optional parameters if they're provided
94
+ if slippage is not None:
95
+ payload["slippage"] = slippage
96
+ if wayfinder_fee is not None:
97
+ payload["wayfinder_fee"] = wayfinder_fee
98
+
99
+ try:
100
+ response = await self._request("POST", url, json=payload, headers={})
101
+ response.raise_for_status()
102
+ data = response.json()
103
+ elapsed = time.time() - start_time
104
+ logger.info(f"BRAP quote request completed successfully in {elapsed:.2f}s")
105
+ return data.get("data", data)
106
+ except Exception as e:
107
+ elapsed = time.time() - start_time
108
+ logger.error(f"BRAP quote request failed after {elapsed:.2f}s: {e}")
109
+ raise
@@ -0,0 +1,210 @@
1
+ """
2
+ Client Manager
3
+ Consolidated client management for all API interactions
4
+ """
5
+
6
+ from typing import Any
7
+
8
+ from wayfinder_paths.core.clients.AuthClient import AuthClient
9
+ from wayfinder_paths.core.clients.BRAPClient import BRAPClient
10
+ from wayfinder_paths.core.clients.HyperlendClient import HyperlendClient
11
+ from wayfinder_paths.core.clients.LedgerClient import LedgerClient
12
+ from wayfinder_paths.core.clients.PoolClient import PoolClient
13
+ from wayfinder_paths.core.clients.protocols import (
14
+ BRAPClientProtocol,
15
+ HyperlendClientProtocol,
16
+ LedgerClientProtocol,
17
+ PoolClientProtocol,
18
+ SimulationClientProtocol,
19
+ TokenClientProtocol,
20
+ TransactionClientProtocol,
21
+ WalletClientProtocol,
22
+ )
23
+ from wayfinder_paths.core.clients.SimulationClient import SimulationClient
24
+ from wayfinder_paths.core.clients.TokenClient import TokenClient
25
+ from wayfinder_paths.core.clients.TransactionClient import TransactionClient
26
+ from wayfinder_paths.core.clients.WalletClient import WalletClient
27
+
28
+
29
+ class ClientManager:
30
+ """
31
+ Manages all API client instances for a strategy job.
32
+
33
+ Args:
34
+ clients: Optional dict of pre-instantiated clients to inject directly.
35
+ Keys: 'token', 'hyperlend', 'ledger', 'wallet', 'transaction', 'pool', 'brap', 'simulation'.
36
+ If not provided, defaults to HTTP-based clients.
37
+ skip_auth: If True, skips authentication (for SDK usage).
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ clients: dict[str, Any] | None = None,
43
+ skip_auth: bool = False,
44
+ api_key: str | None = None,
45
+ ):
46
+ """
47
+ Initialize ClientManager.
48
+
49
+ Args:
50
+ clients: Optional dict of pre-instantiated clients to inject directly.
51
+ skip_auth: If True, skips authentication (for SDK usage).
52
+ api_key: Optional API key for service account authentication.
53
+ """
54
+ self._injected_clients = clients or {}
55
+ self._skip_auth = skip_auth
56
+ self._api_key = api_key
57
+ self._access_token: str | None = None
58
+
59
+ self._auth_client: AuthClient | None = None
60
+ self._token_client: TokenClientProtocol | None = None
61
+ self._wallet_client: WalletClientProtocol | None = None
62
+ self._transaction_client: TransactionClientProtocol | None = None
63
+ self._ledger_client: LedgerClientProtocol | None = None
64
+ self._pool_client: PoolClientProtocol | None = None
65
+ self._hyperlend_client: HyperlendClientProtocol | None = None
66
+ self._brap_client: BRAPClientProtocol | None = None
67
+ self._simulation_client: SimulationClientProtocol | None = None
68
+
69
+ def _get_or_create_client(
70
+ self,
71
+ client_attr: str,
72
+ injected_key: str,
73
+ client_class: type[Any],
74
+ ) -> Any:
75
+ """
76
+ Helper method to get or create a client instance.
77
+
78
+ Args:
79
+ client_attr: Name of the private attribute storing the client (e.g., "_token_client").
80
+ injected_key: Key to look up in _injected_clients dict.
81
+ client_class: Client class to instantiate if not injected.
82
+
83
+ Returns:
84
+ Client instance.
85
+ """
86
+ client = getattr(self, client_attr)
87
+ if not client:
88
+ client = self._injected_clients.get(injected_key) or client_class(
89
+ api_key=self._api_key
90
+ )
91
+ setattr(self, client_attr, client)
92
+ if self._access_token and hasattr(client, "set_bearer_token"):
93
+ client.set_bearer_token(self._access_token)
94
+ return client
95
+
96
+ @property
97
+ def auth(self) -> AuthClient | None:
98
+ """Get or create auth client. Returns None if skip_auth=True."""
99
+ if self._skip_auth:
100
+ return None
101
+ if not self._auth_client:
102
+ self._auth_client = AuthClient(api_key=self._api_key)
103
+ if self._access_token:
104
+ self._auth_client.set_bearer_token(self._access_token)
105
+ return self._auth_client
106
+
107
+ @property
108
+ def token(self) -> TokenClientProtocol:
109
+ """Get or create token client"""
110
+ return self._get_or_create_client("_token_client", "token", TokenClient)
111
+
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
+ @property
120
+ def ledger(self) -> LedgerClientProtocol:
121
+ """Get or create ledger client"""
122
+ return self._get_or_create_client("_ledger_client", "ledger", LedgerClient)
123
+
124
+ @property
125
+ def pool(self) -> PoolClientProtocol:
126
+ """Get or create pool client"""
127
+ return self._get_or_create_client("_pool_client", "pool", PoolClient)
128
+
129
+ @property
130
+ def hyperlend(self) -> HyperlendClientProtocol:
131
+ """Get or create hyperlend client"""
132
+ return self._get_or_create_client(
133
+ "_hyperlend_client", "hyperlend", HyperlendClient
134
+ )
135
+
136
+ @property
137
+ def wallet(self) -> WalletClientProtocol:
138
+ """Get or create wallet client"""
139
+ return self._get_or_create_client("_wallet_client", "wallet", WalletClient)
140
+
141
+ @property
142
+ def brap(self) -> BRAPClientProtocol:
143
+ """Get or create BRAP client"""
144
+ return self._get_or_create_client("_brap_client", "brap", BRAPClient)
145
+
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
+ async def authenticate(
154
+ self,
155
+ username: str | None = None,
156
+ password: str | None = None,
157
+ *,
158
+ refresh_token: str | None = None,
159
+ ) -> dict[str, Any]:
160
+ """Authenticate with the API. Raises ValueError if skip_auth=True."""
161
+ if self._skip_auth:
162
+ raise ValueError(
163
+ "Authentication is disabled in SDK mode. SDK users handle their own authentication."
164
+ )
165
+ auth_client = self.auth
166
+ if auth_client is None:
167
+ raise ValueError("Auth client is not available")
168
+ data = await auth_client.authenticate(
169
+ username, password, refresh_token=refresh_token
170
+ )
171
+ access = data.get("access") or data.get("access_token")
172
+ if access:
173
+ self.set_access_token(access)
174
+ return data
175
+
176
+ def set_access_token(self, access_token: str) -> None:
177
+ """Set and propagate access token to all initialized clients."""
178
+ self._access_token = access_token
179
+ if self._auth_client:
180
+ self._auth_client.set_bearer_token(access_token)
181
+ if self._token_client and hasattr(self._token_client, "set_bearer_token"):
182
+ self._token_client.set_bearer_token(access_token)
183
+ if self._transaction_client and hasattr(
184
+ self._transaction_client, "set_bearer_token"
185
+ ):
186
+ self._transaction_client.set_bearer_token(access_token)
187
+ if self._ledger_client and hasattr(self._ledger_client, "set_bearer_token"):
188
+ self._ledger_client.set_bearer_token(access_token)
189
+ if self._pool_client and hasattr(self._pool_client, "set_bearer_token"):
190
+ self._pool_client.set_bearer_token(access_token)
191
+ if self._hyperlend_client and hasattr(
192
+ self._hyperlend_client, "set_bearer_token"
193
+ ):
194
+ self._hyperlend_client.set_bearer_token(access_token)
195
+ if self._wallet_client and hasattr(self._wallet_client, "set_bearer_token"):
196
+ self._wallet_client.set_bearer_token(access_token)
197
+
198
+ def get_all_clients(self) -> dict[str, Any]:
199
+ """Get all initialized clients for direct access"""
200
+ return {
201
+ "auth": self._auth_client,
202
+ "token": self._token_client,
203
+ "transaction": self._transaction_client,
204
+ "ledger": self._ledger_client,
205
+ "pool": self._pool_client,
206
+ "wallet": self._wallet_client,
207
+ "hyperlend": self._hyperlend_client,
208
+ "brap": self._brap_client,
209
+ "simulation": self._simulation_client,
210
+ }
@@ -0,0 +1,192 @@
1
+ """
2
+ Hyperlend Client
3
+ Provides access to Hyperlend stable markets data via public endpoints.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import Any, NotRequired, Required, TypedDict
9
+
10
+ from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
11
+ from wayfinder_paths.core.settings import settings
12
+
13
+
14
+ class StableMarket(TypedDict):
15
+ """Stable market data structure"""
16
+
17
+ chain_id: Required[int]
18
+ token_address: Required[str]
19
+ symbol: Required[str]
20
+ name: Required[str]
21
+ underlying_tokens: Required[float]
22
+ buffer_bps: Required[int]
23
+ min_buffer_tokens: Required[float]
24
+ is_stable_symbol: Required[bool]
25
+
26
+
27
+ class AssetsView(TypedDict):
28
+ """Assets view response structure"""
29
+
30
+ chain_id: Required[int]
31
+ user_address: Required[str]
32
+ assets: Required[list[dict[str, Any]]]
33
+ total_value: NotRequired[float | None]
34
+
35
+
36
+ class MarketEntry(TypedDict):
37
+ """Market entry data structure"""
38
+
39
+ chain_id: Required[int]
40
+ token_address: Required[str]
41
+ market_data: Required[dict[str, Any]]
42
+
43
+
44
+ class LendRateHistory(TypedDict):
45
+ """Lend rate history response structure"""
46
+
47
+ chain_id: Required[int]
48
+ token_address: Required[str]
49
+ lookback_hours: Required[int]
50
+ rates: Required[list[dict[str, Any]]]
51
+
52
+
53
+ class HyperlendClient(WayfinderClient):
54
+ """Client for Hyperlend-related operations"""
55
+
56
+ def __init__(self, api_key: str | None = None):
57
+ super().__init__(api_key=api_key)
58
+ self.api_base_url = f"{settings.WAYFINDER_API_URL}"
59
+
60
+ async def get_stable_markets(
61
+ self,
62
+ *,
63
+ chain_id: int,
64
+ required_underlying_tokens: float | None = None,
65
+ buffer_bps: int | None = None,
66
+ min_buffer_tokens: float | None = None,
67
+ is_stable_symbol: bool | None = None,
68
+ ) -> list[StableMarket]:
69
+ """
70
+ Fetch stable markets from Hyperlend.
71
+
72
+ Args:
73
+ chain_id: Chain ID to query markets for
74
+ required_underlying_tokens: Required underlying tokens amount
75
+ buffer_bps: Buffer in basis points
76
+ min_buffer_tokens: Minimum buffer in tokens
77
+ is_stable_symbol: Filter by stable symbol (optional)
78
+
79
+ Example:
80
+ GET /api/v1/public/hyperlend/stable-markets/?chain_id=999&required_underlying_tokens=1000.0&buffer_bps=100&min_buffer_tokens=100.0&is_stable_symbol=true
81
+
82
+ Returns:
83
+ Dictionary containing stable markets data
84
+ """
85
+ url = f"{self.api_base_url}/public/hyperlend/stable-markets/"
86
+ params: dict[str, Any] = {"chain_id": chain_id}
87
+ if required_underlying_tokens is not None:
88
+ params["required_underlying_tokens"] = required_underlying_tokens
89
+ if buffer_bps is not None:
90
+ params["buffer_bps"] = buffer_bps
91
+ if min_buffer_tokens is not None:
92
+ params["min_buffer_tokens"] = min_buffer_tokens
93
+ if is_stable_symbol is not None:
94
+ params["is_stable_symbol"] = "true" if is_stable_symbol else "false"
95
+
96
+ response = await self._authed_request("GET", url, params=params, headers={})
97
+ response.raise_for_status()
98
+ data = response.json()
99
+ return data.get("data", data)
100
+
101
+ async def get_assets_view(
102
+ self,
103
+ *,
104
+ chain_id: int,
105
+ user_address: str,
106
+ ) -> AssetsView:
107
+ """
108
+ Fetch assets view for a user address from Hyperlend.
109
+
110
+ Args:
111
+ chain_id: Chain ID to query assets for
112
+ user_address: User wallet address to query assets for
113
+
114
+ Example:
115
+ GET /api/v1/public/hyperlend/assets-view/?chain_id=999&user_address=0x0c737cB5934afCb5B01965141F865F795B324080
116
+
117
+ Returns:
118
+ Dictionary containing assets view data
119
+ """
120
+ url = f"{self.api_base_url}/public/hyperlend/assets-view/"
121
+ params: dict[str, Any] = {
122
+ "chain_id": chain_id,
123
+ "user_address": user_address,
124
+ }
125
+
126
+ response = await self._authed_request("GET", url, params=params, headers={})
127
+ response.raise_for_status()
128
+ data = response.json()
129
+ return data.get("data", data)
130
+
131
+ async def get_market_entry(
132
+ self,
133
+ *,
134
+ chain_id: int,
135
+ token_address: str,
136
+ ) -> MarketEntry:
137
+ """
138
+ Fetch market entry from Hyperlend.
139
+
140
+ Args:
141
+ chain_id: Chain ID to query market for
142
+ token_address: Token address to query market for
143
+
144
+ Example:
145
+ GET /api/v1/public/hyperlend/market-entry/?chain_id=999&token_address=0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
146
+
147
+ Returns:
148
+ Dictionary containing market entry data
149
+ """
150
+ url = f"{self.api_base_url}/public/hyperlend/market-entry/"
151
+ params: dict[str, Any] = {
152
+ "chain_id": chain_id,
153
+ "token_address": token_address,
154
+ }
155
+
156
+ response = await self._authed_request("GET", url, params=params, headers={})
157
+ response.raise_for_status()
158
+ data = response.json()
159
+ return data.get("data", data)
160
+
161
+ async def get_lend_rate_history(
162
+ self,
163
+ *,
164
+ chain_id: int,
165
+ token_address: str,
166
+ lookback_hours: int,
167
+ ) -> LendRateHistory:
168
+ """
169
+ Fetch lend rate history from Hyperlend.
170
+
171
+ Args:
172
+ chain_id: Chain ID to query rate history for
173
+ token_address: Token address to query rate history for
174
+ lookback_hours: Number of hours to look back for rate history
175
+
176
+ Example:
177
+ GET /api/v1/public/hyperlend/lend-rate-history/?chain_id=999&token_address=0x5555555555555555555555555555555555555555&lookback_hours=24
178
+
179
+ Returns:
180
+ Dictionary containing lend rate history data
181
+ """
182
+ url = f"{self.api_base_url}/public/hyperlend/lend-rate-history/"
183
+ params: dict[str, Any] = {
184
+ "chain_id": chain_id,
185
+ "token_address": token_address,
186
+ "lookback_hours": lookback_hours,
187
+ }
188
+
189
+ response = await self._authed_request("GET", url, params=params, headers={})
190
+ response.raise_for_status()
191
+ data = response.json()
192
+ return data.get("data", data)