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.
- wayfinder_paths/CONFIG_GUIDE.md +399 -0
- wayfinder_paths/__init__.py +22 -0
- wayfinder_paths/abis/generic/erc20.json +383 -0
- wayfinder_paths/adapters/__init__.py +0 -0
- wayfinder_paths/adapters/balance_adapter/README.md +94 -0
- wayfinder_paths/adapters/balance_adapter/adapter.py +238 -0
- wayfinder_paths/adapters/balance_adapter/examples.json +6 -0
- wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +59 -0
- wayfinder_paths/adapters/brap_adapter/README.md +249 -0
- wayfinder_paths/adapters/brap_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/brap_adapter/adapter.py +726 -0
- wayfinder_paths/adapters/brap_adapter/examples.json +175 -0
- wayfinder_paths/adapters/brap_adapter/manifest.yaml +11 -0
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +286 -0
- wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +305 -0
- wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +10 -0
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +274 -0
- wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +18 -0
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1093 -0
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +549 -0
- wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +8 -0
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +1050 -0
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +126 -0
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +219 -0
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +220 -0
- wayfinder_paths/adapters/hyperliquid_adapter/utils.py +134 -0
- wayfinder_paths/adapters/ledger_adapter/README.md +145 -0
- wayfinder_paths/adapters/ledger_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/ledger_adapter/adapter.py +289 -0
- wayfinder_paths/adapters/ledger_adapter/examples.json +137 -0
- wayfinder_paths/adapters/ledger_adapter/manifest.yaml +11 -0
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +205 -0
- wayfinder_paths/adapters/pool_adapter/README.md +206 -0
- wayfinder_paths/adapters/pool_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/pool_adapter/adapter.py +282 -0
- wayfinder_paths/adapters/pool_adapter/examples.json +143 -0
- wayfinder_paths/adapters/pool_adapter/manifest.yaml +10 -0
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +220 -0
- wayfinder_paths/adapters/token_adapter/README.md +101 -0
- wayfinder_paths/adapters/token_adapter/__init__.py +3 -0
- wayfinder_paths/adapters/token_adapter/adapter.py +96 -0
- wayfinder_paths/adapters/token_adapter/examples.json +26 -0
- wayfinder_paths/adapters/token_adapter/manifest.yaml +6 -0
- wayfinder_paths/adapters/token_adapter/test_adapter.py +125 -0
- wayfinder_paths/config.example.json +22 -0
- wayfinder_paths/conftest.py +31 -0
- wayfinder_paths/core/__init__.py +18 -0
- wayfinder_paths/core/adapters/BaseAdapter.py +65 -0
- wayfinder_paths/core/adapters/__init__.py +5 -0
- wayfinder_paths/core/adapters/base.py +5 -0
- wayfinder_paths/core/adapters/models.py +46 -0
- wayfinder_paths/core/analytics/__init__.py +11 -0
- wayfinder_paths/core/analytics/bootstrap.py +57 -0
- wayfinder_paths/core/analytics/stats.py +48 -0
- wayfinder_paths/core/analytics/test_analytics.py +170 -0
- wayfinder_paths/core/clients/AuthClient.py +83 -0
- wayfinder_paths/core/clients/BRAPClient.py +109 -0
- wayfinder_paths/core/clients/ClientManager.py +210 -0
- wayfinder_paths/core/clients/HyperlendClient.py +192 -0
- wayfinder_paths/core/clients/LedgerClient.py +443 -0
- wayfinder_paths/core/clients/PoolClient.py +128 -0
- wayfinder_paths/core/clients/SimulationClient.py +192 -0
- wayfinder_paths/core/clients/TokenClient.py +89 -0
- wayfinder_paths/core/clients/TransactionClient.py +63 -0
- wayfinder_paths/core/clients/WalletClient.py +94 -0
- wayfinder_paths/core/clients/WayfinderClient.py +269 -0
- wayfinder_paths/core/clients/__init__.py +48 -0
- wayfinder_paths/core/clients/protocols.py +392 -0
- wayfinder_paths/core/clients/sdk_example.py +110 -0
- wayfinder_paths/core/config.py +458 -0
- wayfinder_paths/core/constants/__init__.py +26 -0
- wayfinder_paths/core/constants/base.py +42 -0
- wayfinder_paths/core/constants/erc20_abi.py +118 -0
- wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
- wayfinder_paths/core/engine/StrategyJob.py +188 -0
- wayfinder_paths/core/engine/__init__.py +5 -0
- wayfinder_paths/core/engine/manifest.py +97 -0
- wayfinder_paths/core/services/__init__.py +0 -0
- wayfinder_paths/core/services/base.py +179 -0
- wayfinder_paths/core/services/local_evm_txn.py +430 -0
- wayfinder_paths/core/services/local_token_txn.py +231 -0
- wayfinder_paths/core/services/web3_service.py +45 -0
- wayfinder_paths/core/settings.py +61 -0
- wayfinder_paths/core/strategies/Strategy.py +280 -0
- wayfinder_paths/core/strategies/__init__.py +5 -0
- wayfinder_paths/core/strategies/base.py +7 -0
- wayfinder_paths/core/strategies/descriptors.py +81 -0
- wayfinder_paths/core/utils/__init__.py +1 -0
- wayfinder_paths/core/utils/evm_helpers.py +206 -0
- wayfinder_paths/core/utils/wallets.py +77 -0
- wayfinder_paths/core/wallets/README.md +91 -0
- wayfinder_paths/core/wallets/WalletManager.py +56 -0
- wayfinder_paths/core/wallets/__init__.py +7 -0
- wayfinder_paths/policies/enso.py +17 -0
- wayfinder_paths/policies/erc20.py +34 -0
- wayfinder_paths/policies/evm.py +21 -0
- wayfinder_paths/policies/hyper_evm.py +19 -0
- wayfinder_paths/policies/hyperlend.py +12 -0
- wayfinder_paths/policies/hyperliquid.py +30 -0
- wayfinder_paths/policies/moonwell.py +54 -0
- wayfinder_paths/policies/prjx.py +30 -0
- wayfinder_paths/policies/util.py +27 -0
- wayfinder_paths/run_strategy.py +411 -0
- wayfinder_paths/scripts/__init__.py +0 -0
- wayfinder_paths/scripts/create_strategy.py +181 -0
- wayfinder_paths/scripts/make_wallets.py +169 -0
- wayfinder_paths/scripts/run_strategy.py +124 -0
- wayfinder_paths/scripts/validate_manifests.py +213 -0
- wayfinder_paths/strategies/__init__.py +0 -0
- wayfinder_paths/strategies/basis_trading_strategy/README.md +213 -0
- wayfinder_paths/strategies/basis_trading_strategy/__init__.py +3 -0
- wayfinder_paths/strategies/basis_trading_strategy/constants.py +1 -0
- wayfinder_paths/strategies/basis_trading_strategy/examples.json +16 -0
- wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +23 -0
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1011 -0
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +4522 -0
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +727 -0
- wayfinder_paths/strategies/basis_trading_strategy/types.py +39 -0
- wayfinder_paths/strategies/config.py +85 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +100 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +8 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +2270 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +352 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +96 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/examples.json +17 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1810 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +520 -0
- wayfinder_paths/templates/adapter/README.md +105 -0
- wayfinder_paths/templates/adapter/adapter.py +26 -0
- wayfinder_paths/templates/adapter/examples.json +8 -0
- wayfinder_paths/templates/adapter/manifest.yaml +6 -0
- wayfinder_paths/templates/adapter/test_adapter.py +49 -0
- wayfinder_paths/templates/strategy/README.md +153 -0
- wayfinder_paths/templates/strategy/examples.json +11 -0
- wayfinder_paths/templates/strategy/manifest.yaml +8 -0
- wayfinder_paths/templates/strategy/strategy.py +57 -0
- wayfinder_paths/templates/strategy/test_strategy.py +197 -0
- wayfinder_paths/tests/__init__.py +0 -0
- wayfinder_paths/tests/test_smoke_manifest.py +48 -0
- wayfinder_paths/tests/test_test_coverage.py +212 -0
- wayfinder_paths/tests/test_utils.py +64 -0
- wayfinder_paths-0.1.7.dist-info/LICENSE +21 -0
- wayfinder_paths-0.1.7.dist-info/METADATA +777 -0
- wayfinder_paths-0.1.7.dist-info/RECORD +149 -0
- 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)
|