wayfinder-paths 0.1.1__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 (115) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +394 -0
  2. wayfinder_paths/__init__.py +21 -0
  3. wayfinder_paths/config.example.json +20 -0
  4. wayfinder_paths/conftest.py +31 -0
  5. wayfinder_paths/core/__init__.py +13 -0
  6. wayfinder_paths/core/adapters/BaseAdapter.py +48 -0
  7. wayfinder_paths/core/adapters/__init__.py +5 -0
  8. wayfinder_paths/core/adapters/base.py +5 -0
  9. wayfinder_paths/core/clients/AuthClient.py +83 -0
  10. wayfinder_paths/core/clients/BRAPClient.py +90 -0
  11. wayfinder_paths/core/clients/ClientManager.py +231 -0
  12. wayfinder_paths/core/clients/HyperlendClient.py +151 -0
  13. wayfinder_paths/core/clients/LedgerClient.py +222 -0
  14. wayfinder_paths/core/clients/PoolClient.py +96 -0
  15. wayfinder_paths/core/clients/SimulationClient.py +180 -0
  16. wayfinder_paths/core/clients/TokenClient.py +73 -0
  17. wayfinder_paths/core/clients/TransactionClient.py +47 -0
  18. wayfinder_paths/core/clients/WalletClient.py +90 -0
  19. wayfinder_paths/core/clients/WayfinderClient.py +258 -0
  20. wayfinder_paths/core/clients/__init__.py +48 -0
  21. wayfinder_paths/core/clients/protocols.py +295 -0
  22. wayfinder_paths/core/clients/sdk_example.py +115 -0
  23. wayfinder_paths/core/config.py +369 -0
  24. wayfinder_paths/core/constants/__init__.py +26 -0
  25. wayfinder_paths/core/constants/base.py +25 -0
  26. wayfinder_paths/core/constants/erc20_abi.py +118 -0
  27. wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
  28. wayfinder_paths/core/engine/VaultJob.py +182 -0
  29. wayfinder_paths/core/engine/__init__.py +5 -0
  30. wayfinder_paths/core/engine/manifest.py +97 -0
  31. wayfinder_paths/core/services/__init__.py +0 -0
  32. wayfinder_paths/core/services/base.py +177 -0
  33. wayfinder_paths/core/services/local_evm_txn.py +429 -0
  34. wayfinder_paths/core/services/local_token_txn.py +231 -0
  35. wayfinder_paths/core/services/web3_service.py +45 -0
  36. wayfinder_paths/core/settings.py +61 -0
  37. wayfinder_paths/core/strategies/Strategy.py +183 -0
  38. wayfinder_paths/core/strategies/__init__.py +5 -0
  39. wayfinder_paths/core/strategies/base.py +7 -0
  40. wayfinder_paths/core/utils/__init__.py +1 -0
  41. wayfinder_paths/core/utils/evm_helpers.py +165 -0
  42. wayfinder_paths/core/utils/wallets.py +77 -0
  43. wayfinder_paths/core/wallets/README.md +91 -0
  44. wayfinder_paths/core/wallets/WalletManager.py +56 -0
  45. wayfinder_paths/core/wallets/__init__.py +7 -0
  46. wayfinder_paths/run_strategy.py +409 -0
  47. wayfinder_paths/scripts/__init__.py +0 -0
  48. wayfinder_paths/scripts/create_strategy.py +181 -0
  49. wayfinder_paths/scripts/make_wallets.py +160 -0
  50. wayfinder_paths/scripts/validate_manifests.py +213 -0
  51. wayfinder_paths/tests/__init__.py +0 -0
  52. wayfinder_paths/tests/test_smoke_manifest.py +48 -0
  53. wayfinder_paths/tests/test_test_coverage.py +212 -0
  54. wayfinder_paths/tests/test_utils.py +64 -0
  55. wayfinder_paths/vaults/__init__.py +0 -0
  56. wayfinder_paths/vaults/adapters/__init__.py +0 -0
  57. wayfinder_paths/vaults/adapters/balance_adapter/README.md +104 -0
  58. wayfinder_paths/vaults/adapters/balance_adapter/adapter.py +257 -0
  59. wayfinder_paths/vaults/adapters/balance_adapter/examples.json +6 -0
  60. wayfinder_paths/vaults/adapters/balance_adapter/manifest.yaml +8 -0
  61. wayfinder_paths/vaults/adapters/balance_adapter/test_adapter.py +83 -0
  62. wayfinder_paths/vaults/adapters/brap_adapter/README.md +249 -0
  63. wayfinder_paths/vaults/adapters/brap_adapter/__init__.py +7 -0
  64. wayfinder_paths/vaults/adapters/brap_adapter/adapter.py +717 -0
  65. wayfinder_paths/vaults/adapters/brap_adapter/examples.json +175 -0
  66. wayfinder_paths/vaults/adapters/brap_adapter/manifest.yaml +11 -0
  67. wayfinder_paths/vaults/adapters/brap_adapter/test_adapter.py +288 -0
  68. wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +7 -0
  69. wayfinder_paths/vaults/adapters/hyperlend_adapter/adapter.py +298 -0
  70. wayfinder_paths/vaults/adapters/hyperlend_adapter/manifest.yaml +10 -0
  71. wayfinder_paths/vaults/adapters/hyperlend_adapter/test_adapter.py +267 -0
  72. wayfinder_paths/vaults/adapters/ledger_adapter/README.md +158 -0
  73. wayfinder_paths/vaults/adapters/ledger_adapter/__init__.py +7 -0
  74. wayfinder_paths/vaults/adapters/ledger_adapter/adapter.py +286 -0
  75. wayfinder_paths/vaults/adapters/ledger_adapter/examples.json +131 -0
  76. wayfinder_paths/vaults/adapters/ledger_adapter/manifest.yaml +11 -0
  77. wayfinder_paths/vaults/adapters/ledger_adapter/test_adapter.py +202 -0
  78. wayfinder_paths/vaults/adapters/pool_adapter/README.md +218 -0
  79. wayfinder_paths/vaults/adapters/pool_adapter/__init__.py +7 -0
  80. wayfinder_paths/vaults/adapters/pool_adapter/adapter.py +289 -0
  81. wayfinder_paths/vaults/adapters/pool_adapter/examples.json +143 -0
  82. wayfinder_paths/vaults/adapters/pool_adapter/manifest.yaml +10 -0
  83. wayfinder_paths/vaults/adapters/pool_adapter/test_adapter.py +222 -0
  84. wayfinder_paths/vaults/adapters/token_adapter/README.md +101 -0
  85. wayfinder_paths/vaults/adapters/token_adapter/__init__.py +3 -0
  86. wayfinder_paths/vaults/adapters/token_adapter/adapter.py +92 -0
  87. wayfinder_paths/vaults/adapters/token_adapter/examples.json +26 -0
  88. wayfinder_paths/vaults/adapters/token_adapter/manifest.yaml +6 -0
  89. wayfinder_paths/vaults/adapters/token_adapter/test_adapter.py +135 -0
  90. wayfinder_paths/vaults/strategies/__init__.py +0 -0
  91. wayfinder_paths/vaults/strategies/config.py +85 -0
  92. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/README.md +99 -0
  93. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/examples.json +16 -0
  94. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
  95. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/strategy.py +2328 -0
  96. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/test_strategy.py +319 -0
  97. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/README.md +95 -0
  98. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/examples.json +17 -0
  99. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
  100. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/strategy.py +1684 -0
  101. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/test_strategy.py +350 -0
  102. wayfinder_paths/vaults/templates/adapter/README.md +105 -0
  103. wayfinder_paths/vaults/templates/adapter/adapter.py +26 -0
  104. wayfinder_paths/vaults/templates/adapter/examples.json +8 -0
  105. wayfinder_paths/vaults/templates/adapter/manifest.yaml +6 -0
  106. wayfinder_paths/vaults/templates/adapter/test_adapter.py +49 -0
  107. wayfinder_paths/vaults/templates/strategy/README.md +152 -0
  108. wayfinder_paths/vaults/templates/strategy/examples.json +11 -0
  109. wayfinder_paths/vaults/templates/strategy/manifest.yaml +8 -0
  110. wayfinder_paths/vaults/templates/strategy/strategy.py +57 -0
  111. wayfinder_paths/vaults/templates/strategy/test_strategy.py +197 -0
  112. wayfinder_paths-0.1.1.dist-info/LICENSE +21 -0
  113. wayfinder_paths-0.1.1.dist-info/METADATA +727 -0
  114. wayfinder_paths-0.1.1.dist-info/RECORD +115 -0
  115. wayfinder_paths-0.1.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,258 @@
1
+ import json
2
+ import os
3
+ import time
4
+ from typing import Any
5
+
6
+ import httpx
7
+ from loguru import logger
8
+
9
+ from wayfinder_paths.core.settings import settings
10
+
11
+
12
+ class WayfinderClient:
13
+ def __init__(self, api_key: str | None = None):
14
+ """
15
+ Initialize WayfinderClient.
16
+
17
+ Args:
18
+ api_key: Optional API key for service account authentication.
19
+ If provided, uses API key auth. Otherwise falls back to config.json.
20
+ """
21
+ self.api_base_url = f"{settings.WAYFINDER_API_URL}/"
22
+ timeout = httpx.Timeout(30.0)
23
+ self.client = httpx.AsyncClient(timeout=timeout)
24
+
25
+ self.headers = {
26
+ "Content-Type": "application/json",
27
+ }
28
+
29
+ self._api_key: str | None = api_key
30
+ self._access_token: str | None = None
31
+ self._refresh_token: str | None = None
32
+
33
+ def set_bearer_token(self, token: str) -> None:
34
+ """
35
+ Set runtime OAuth/JWT Bearer token for Django-backed Wayfinder services.
36
+ """
37
+ self.headers["Authorization"] = f"Bearer {token}"
38
+ self._access_token = token
39
+
40
+ def set_tokens(self, access: str | None, refresh: str | None) -> None:
41
+ """Set both access and refresh tokens and configure Authorization header."""
42
+ if access:
43
+ self.set_bearer_token(access)
44
+ if refresh:
45
+ self._refresh_token = refresh
46
+
47
+ def clear_auth(self) -> None:
48
+ """Clear Authorization headers (useful for logout or auth mode switch)."""
49
+ self.headers.pop("Authorization", None)
50
+
51
+ async def _refresh_access_token(self) -> bool:
52
+ """Attempt to refresh access token using stored refresh token."""
53
+ if not self._refresh_token:
54
+ logger.debug("No refresh token available")
55
+ return False
56
+ try:
57
+ logger.info("Attempting to refresh access token")
58
+ start_time = time.time()
59
+ url = f"{settings.WAYFINDER_API_URL}/auth/token/refresh/"
60
+ payload = {"refresh": self._refresh_token}
61
+ response = await self.client.post(
62
+ url, json=payload, headers={"Content-Type": "application/json"}
63
+ )
64
+ if response.status_code != 200:
65
+ logger.warning(
66
+ f"Token refresh failed with status {response.status_code}"
67
+ )
68
+ return False
69
+ data = response.json()
70
+ new_access = data.get("access") or data.get("access_token")
71
+ if not new_access:
72
+ logger.warning("No access token in refresh response")
73
+ return False
74
+ self.set_bearer_token(new_access)
75
+ elapsed = time.time() - start_time
76
+ logger.info(f"Access token refreshed successfully in {elapsed:.2f}s")
77
+ return True
78
+ except Exception as e:
79
+ elapsed = time.time() - start_time
80
+ logger.error(f"Token refresh failed after {elapsed:.2f}s: {e}")
81
+ return False
82
+
83
+ def _load_config_credentials(self) -> dict[str, str | None]:
84
+ """
85
+ Load credentials from config.json. Path can be overridden via WAYFINDER_CONFIG_PATH.
86
+ Expected shape:
87
+ {
88
+ "user": { "username": ..., "password": ..., "refresh_token": ..., "api_key": ... },
89
+ "system": { "api_key": ... }
90
+ }
91
+ """
92
+ path = os.getenv("WAYFINDER_CONFIG_PATH", "config.json")
93
+ try:
94
+ with open(path) as f:
95
+ cfg = json.load(f)
96
+ user = cfg.get("user", {}) if isinstance(cfg, dict) else {}
97
+ 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
+ }
104
+ except Exception:
105
+ return {
106
+ "username": None,
107
+ "password": None,
108
+ "refresh_token": None,
109
+ "api_key": None,
110
+ }
111
+
112
+ async def _ensure_bearer_token(self) -> bool:
113
+ """
114
+ Ensure Authorization header is set. Priority: existing header > constructor api_key > config.json api_key > env api_key > config.json tokens > env tokens > username/password.
115
+ Raises PermissionError if no credentials found.
116
+ """
117
+ if self.headers.get("Authorization"):
118
+ return True
119
+
120
+ # Check for API key: constructor > config.json > environment
121
+ api_key = self._api_key
122
+ if not api_key:
123
+ creds = self._load_config_credentials()
124
+ api_key = creds.get("api_key") or os.getenv("WAYFINDER_API_KEY")
125
+
126
+ if api_key:
127
+ api_key = api_key.strip() if isinstance(api_key, str) else api_key
128
+ if not api_key:
129
+ raise ValueError("API key cannot be empty")
130
+ self.headers["Authorization"] = f"Bearer {api_key}"
131
+ return True
132
+
133
+ # Fall back to OAuth token-based auth
134
+ creds = self._load_config_credentials()
135
+ access = os.getenv("WAYFINDER_ACCESS_TOKEN")
136
+ refresh = creds.get("refresh_token") or os.getenv("WAYFINDER_REFRESH_TOKEN")
137
+
138
+ if access:
139
+ self.set_tokens(access, refresh)
140
+ return True
141
+
142
+ if refresh:
143
+ self._refresh_token = refresh
144
+ refreshed = await self._refresh_access_token()
145
+ if refreshed:
146
+ return True
147
+
148
+ username = creds.get("username") or os.getenv("WAYFINDER_USERNAME")
149
+ password = creds.get("password") or os.getenv("WAYFINDER_PASSWORD")
150
+
151
+ if username and password:
152
+ try:
153
+ url = f"{settings.WAYFINDER_API_URL}/auth/token/"
154
+ payload = {"username": username, "password": password}
155
+ response = await self.client.post(
156
+ url,
157
+ json=payload,
158
+ headers={"Content-Type": "application/json"},
159
+ )
160
+ if response.status_code == 200:
161
+ data = response.json()
162
+ access = data.get("access") or data.get("access_token")
163
+ refresh = data.get("refresh") or data.get("refresh_token")
164
+ self.set_tokens(access, refresh)
165
+ return bool(access)
166
+ except Exception:
167
+ pass
168
+
169
+ raise PermissionError(
170
+ "Not authenticated: provide api_key (via constructor, config.json, or WAYFINDER_API_KEY env var) for service account auth, "
171
+ "or username+password/refresh_token in config.json for personal access"
172
+ )
173
+
174
+ async def _request(
175
+ self,
176
+ method: str,
177
+ url: str,
178
+ *,
179
+ headers: dict[str, str] | None = None,
180
+ retry_on_401: bool = True,
181
+ **kwargs: Any,
182
+ ) -> httpx.Response:
183
+ """
184
+ Wrapper around httpx that injects headers and auto-refreshes tokens on 401 once.
185
+ Ensures API key or bearer token is set in headers when available (for service account auth and rate limits).
186
+ """
187
+ logger.debug(f"Making {method} request to {url}")
188
+ start_time = time.time()
189
+
190
+ # Ensure API key or bearer token is set in headers if available and not already set
191
+ # This ensures API keys are passed to all endpoints (including public ones) for rate limiting
192
+ if not self.headers.get("Authorization"):
193
+ # Try to get API key from constructor, config, or env
194
+ api_key = self._api_key
195
+ if not api_key:
196
+ creds = self._load_config_credentials()
197
+ api_key = creds.get("api_key") or os.getenv("WAYFINDER_API_KEY")
198
+
199
+ if api_key:
200
+ api_key = api_key.strip() if isinstance(api_key, str) else api_key
201
+ if api_key:
202
+ self.headers["Authorization"] = f"Bearer {api_key}"
203
+
204
+ merged_headers = dict(self.headers)
205
+ if headers:
206
+ merged_headers.update(headers)
207
+ resp = await self.client.request(method, url, headers=merged_headers, **kwargs)
208
+
209
+ if resp.status_code == 401 and retry_on_401 and self._refresh_token:
210
+ logger.info("Received 401, attempting token refresh and retry")
211
+ refreshed = await self._refresh_access_token()
212
+ if refreshed:
213
+ merged_headers = dict(self.headers)
214
+ if headers:
215
+ merged_headers.update(headers)
216
+ resp = await self.client.request(
217
+ method, url, headers=merged_headers, **kwargs
218
+ )
219
+ logger.info("Retry after token refresh successful")
220
+ else:
221
+ logger.error("Token refresh failed, request will fail")
222
+
223
+ elapsed = time.time() - start_time
224
+ if resp.status_code >= 400:
225
+ logger.warning(
226
+ f"HTTP {resp.status_code} response for {method} {url} after {elapsed:.2f}s"
227
+ )
228
+ else:
229
+ logger.debug(
230
+ f"HTTP {resp.status_code} response for {method} {url} after {elapsed:.2f}s"
231
+ )
232
+
233
+ resp.raise_for_status()
234
+ return resp
235
+
236
+ async def _authed_request(
237
+ self,
238
+ method: str,
239
+ url: str,
240
+ *,
241
+ headers: dict[str, str] | None = None,
242
+ **kwargs: Any,
243
+ ) -> httpx.Response:
244
+ """
245
+ Ensure Authorization (via env/config creds) and perform the request.
246
+ Retries once on 401 by re-acquiring tokens.
247
+ """
248
+ ok = await self._ensure_bearer_token()
249
+ if not ok:
250
+ raise PermissionError("Not authenticated: set env tokens or credentials")
251
+ try:
252
+ return await self._request(method, url, headers=headers, **kwargs)
253
+ except httpx.HTTPStatusError as e:
254
+ if e.response is not None and e.response.status_code == 401:
255
+ # Retry after attempting re-acquire/refresh
256
+ await self._ensure_bearer_token()
257
+ return await self._request(method, url, headers=headers, **kwargs)
258
+ raise
@@ -0,0 +1,48 @@
1
+ """
2
+ Core client modules for API communication
3
+ """
4
+
5
+ from wayfinder_paths.core.clients.AuthClient import AuthClient
6
+ from wayfinder_paths.core.clients.BRAPClient import BRAPClient
7
+ from wayfinder_paths.core.clients.ClientManager import ClientManager
8
+ from wayfinder_paths.core.clients.HyperlendClient import HyperlendClient
9
+ from wayfinder_paths.core.clients.LedgerClient import LedgerClient
10
+ from wayfinder_paths.core.clients.PoolClient import PoolClient
11
+ from wayfinder_paths.core.clients.protocols import (
12
+ BRAPClientProtocol,
13
+ HyperlendClientProtocol,
14
+ LedgerClientProtocol,
15
+ PoolClientProtocol,
16
+ SimulationClientProtocol,
17
+ TokenClientProtocol,
18
+ TransactionClientProtocol,
19
+ WalletClientProtocol,
20
+ )
21
+ from wayfinder_paths.core.clients.SimulationClient import SimulationClient
22
+ from wayfinder_paths.core.clients.TokenClient import TokenClient
23
+ from wayfinder_paths.core.clients.TransactionClient import TransactionClient
24
+ from wayfinder_paths.core.clients.WalletClient import WalletClient
25
+ from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
26
+
27
+ __all__ = [
28
+ "WayfinderClient",
29
+ "ClientManager",
30
+ "AuthClient",
31
+ "TokenClient",
32
+ "WalletClient",
33
+ "TransactionClient",
34
+ "LedgerClient",
35
+ "PoolClient",
36
+ "BRAPClient",
37
+ "SimulationClient",
38
+ "HyperlendClient",
39
+ # Protocols for SDK usage
40
+ "TokenClientProtocol",
41
+ "HyperlendClientProtocol",
42
+ "LedgerClientProtocol",
43
+ "WalletClientProtocol",
44
+ "TransactionClientProtocol",
45
+ "PoolClientProtocol",
46
+ "BRAPClientProtocol",
47
+ "SimulationClientProtocol",
48
+ ]
@@ -0,0 +1,295 @@
1
+ """
2
+ Protocol definitions for API clients.
3
+
4
+ These protocols define the interface that all client implementations must satisfy.
5
+ When used as an SDK, users can provide custom implementations that match these protocols.
6
+
7
+ Note: AuthClient is excluded as SDK users handle their own authentication.
8
+ """
9
+
10
+ from typing import Any, Protocol
11
+
12
+
13
+ class TokenClientProtocol(Protocol):
14
+ """Protocol for token-related operations"""
15
+
16
+ async def get_token_details(
17
+ self, token_id: str, force_refresh: bool = False
18
+ ) -> dict[str, Any]:
19
+ """Get token data including price from the token-details endpoint"""
20
+ ...
21
+
22
+ async def get_gas_token(self, chain_code: str) -> dict[str, Any]:
23
+ """Fetch the native gas token for a given chain code"""
24
+ ...
25
+
26
+ async def is_native_token(
27
+ self, token_address: str, chain_id: int
28
+ ) -> dict[str, Any]:
29
+ """Determine if a token address corresponds to the native gas token on a chain"""
30
+ ...
31
+
32
+
33
+ class HyperlendClientProtocol(Protocol):
34
+ """Protocol for Hyperlend-related operations"""
35
+
36
+ async def get_stable_markets(
37
+ self,
38
+ *,
39
+ chain_id: int,
40
+ required_underlying_tokens: float | None = None,
41
+ buffer_bps: int | None = None,
42
+ min_buffer_tokens: float | None = None,
43
+ is_stable_symbol: bool | None = None,
44
+ ) -> dict[str, Any]:
45
+ """Fetch stable markets from Hyperlend"""
46
+ ...
47
+
48
+ async def get_assets_view(
49
+ self,
50
+ *,
51
+ chain_id: int,
52
+ user_address: str,
53
+ ) -> dict[str, Any]:
54
+ """Fetch assets view for a user address from Hyperlend"""
55
+ ...
56
+
57
+ async def get_market_entry(
58
+ self,
59
+ *,
60
+ chain_id: int,
61
+ token_address: str,
62
+ ) -> dict[str, Any]:
63
+ """Fetch market entry from Hyperlend"""
64
+ ...
65
+
66
+ async def get_lend_rate_history(
67
+ self,
68
+ *,
69
+ chain_id: int,
70
+ token_address: str,
71
+ lookback_hours: int,
72
+ ) -> dict[str, Any]:
73
+ """Fetch lend rate history from Hyperlend"""
74
+ ...
75
+
76
+
77
+ class LedgerClientProtocol(Protocol):
78
+ """Protocol for vault transaction history and bookkeeping operations"""
79
+
80
+ async def get_vault_transactions(
81
+ self,
82
+ *,
83
+ wallet_address: str,
84
+ limit: int = 50,
85
+ offset: int = 0,
86
+ ) -> dict[str, Any]:
87
+ """Fetch a paginated list of transactions for a given vault wallet"""
88
+ ...
89
+
90
+ async def get_vault_net_deposit(self, *, wallet_address: str) -> dict[str, Any]:
91
+ """Fetch the net deposit (deposits - withdrawals) for a vault"""
92
+ ...
93
+
94
+ async def get_vault_latest_transactions(
95
+ self, *, wallet_address: str
96
+ ) -> dict[str, Any]:
97
+ """Fetch the latest transactions for a vault"""
98
+ ...
99
+
100
+ async def add_vault_deposit(
101
+ self,
102
+ *,
103
+ wallet_address: str,
104
+ chain_id: int,
105
+ token_address: str,
106
+ token_amount: str | float,
107
+ usd_value: str | float,
108
+ data: dict[str, Any] | None = None,
109
+ strategy_name: str | None = None,
110
+ ) -> dict[str, Any]:
111
+ """Record a deposit for a vault"""
112
+ ...
113
+
114
+ async def add_vault_withdraw(
115
+ self,
116
+ *,
117
+ wallet_address: str,
118
+ chain_id: int,
119
+ token_address: str,
120
+ token_amount: str | float,
121
+ usd_value: str | float,
122
+ data: dict[str, Any] | None = None,
123
+ strategy_name: str | None = None,
124
+ ) -> dict[str, Any]:
125
+ """Record a withdrawal for a vault"""
126
+ ...
127
+
128
+ async def add_vault_operation(
129
+ self,
130
+ *,
131
+ wallet_address: str,
132
+ operation_data: dict[str, Any],
133
+ usd_value: str | float,
134
+ strategy_name: str | None = None,
135
+ ) -> dict[str, Any]:
136
+ """Record a vault operation (e.g., swaps, rebalances)"""
137
+ ...
138
+
139
+ async def add_vault_cashflow(
140
+ self,
141
+ *,
142
+ wallet_address: str,
143
+ block_timestamp: int,
144
+ token_addr: str,
145
+ amount: str | int | float,
146
+ description: str,
147
+ strategy_name: str | None = None,
148
+ ) -> dict[str, Any]:
149
+ """Record a cashflow for a vault (interest, funding, reward, or fee)"""
150
+ ...
151
+
152
+
153
+ class WalletClientProtocol(Protocol):
154
+ """Protocol for wallet-related operations"""
155
+
156
+ async def get_token_balance_for_wallet(
157
+ self,
158
+ *,
159
+ token_id: str,
160
+ wallet_address: str,
161
+ human_readable: bool = True,
162
+ ) -> dict[str, Any]:
163
+ """Fetch a single token balance for an explicit wallet address"""
164
+ ...
165
+
166
+ async def get_pool_balance_for_wallet(
167
+ self,
168
+ *,
169
+ pool_address: str,
170
+ chain_id: int,
171
+ user_address: str,
172
+ human_readable: bool = True,
173
+ ) -> dict[str, Any]:
174
+ """Fetch a wallet's LP/share balance for a given pool address and chain"""
175
+ ...
176
+
177
+ async def get_all_enriched_token_balances_for_wallet(
178
+ self,
179
+ *,
180
+ wallet_address: str,
181
+ enrich: bool = True,
182
+ from_cache: bool = False,
183
+ add_llama: bool = True,
184
+ ) -> dict[str, Any]:
185
+ """Fetch all token balances for a wallet with enrichment"""
186
+ ...
187
+
188
+
189
+ class TransactionClientProtocol(Protocol):
190
+ """Protocol for transaction operations"""
191
+
192
+ async def build_send(
193
+ self,
194
+ from_address: str,
195
+ to_address: str,
196
+ token_address: str,
197
+ amount: float,
198
+ chain_id: int,
199
+ ) -> dict[str, Any]:
200
+ """Build a send transaction payload for EVM tokens/native transfers"""
201
+ ...
202
+
203
+
204
+ class PoolClientProtocol(Protocol):
205
+ """Protocol for pool-related read operations"""
206
+
207
+ async def get_pools_by_ids(
208
+ self,
209
+ *,
210
+ pool_ids: str,
211
+ merge_external: bool | None = None,
212
+ ) -> dict[str, Any]:
213
+ """Fetch pools by comma-separated pool ids"""
214
+ ...
215
+
216
+ async def get_all_pools(
217
+ self, *, merge_external: bool | None = None
218
+ ) -> dict[str, Any]:
219
+ """Fetch all pools"""
220
+ ...
221
+
222
+ async def get_combined_pool_reports(self) -> dict[str, Any]:
223
+ """Fetch combined pool reports"""
224
+ ...
225
+
226
+ async def get_llama_matches(self) -> dict[str, Any]:
227
+ """Fetch Llama matches for pools"""
228
+ ...
229
+
230
+ async def get_llama_reports(self, *, identifiers: str) -> dict[str, Any]:
231
+ """Fetch Llama reports using identifiers"""
232
+ ...
233
+
234
+
235
+ class BRAPClientProtocol(Protocol):
236
+ """Protocol for BRAP (Bridge/Router/Adapter Protocol) quote operations"""
237
+
238
+ async def get_quote(
239
+ self,
240
+ *,
241
+ from_token_address: str,
242
+ to_token_address: str,
243
+ from_chain_id: int,
244
+ to_chain_id: int,
245
+ from_address: str,
246
+ to_address: str,
247
+ amount1: str,
248
+ slippage: float | None = None,
249
+ wayfinder_fee: float | None = None,
250
+ ) -> dict[str, Any]:
251
+ """Get a quote for a bridge/swap operation"""
252
+ ...
253
+
254
+
255
+ class SimulationClientProtocol(Protocol):
256
+ """Protocol for blockchain transaction simulations"""
257
+
258
+ async def simulate_send(
259
+ self,
260
+ from_address: str,
261
+ to_address: str,
262
+ token_address: str,
263
+ amount: str,
264
+ chain_id: int,
265
+ initial_balances: dict[str, str],
266
+ ) -> dict[str, Any]:
267
+ """Simulate sending native ETH or ERC20 tokens"""
268
+ ...
269
+
270
+ async def simulate_approve(
271
+ self,
272
+ from_address: str,
273
+ to_address: str,
274
+ token_address: str,
275
+ amount: str,
276
+ chain_id: int,
277
+ initial_balances: dict[str, str],
278
+ clear_approval_first: bool = False,
279
+ ) -> dict[str, Any]:
280
+ """Simulate ERC20 token approval"""
281
+ ...
282
+
283
+ async def simulate_swap(
284
+ self,
285
+ from_token_address: str,
286
+ to_token_address: str,
287
+ from_chain_id: int,
288
+ to_chain_id: int,
289
+ amount: str,
290
+ from_address: str,
291
+ slippage: float,
292
+ initial_balances: dict[str, str],
293
+ ) -> dict[str, Any]:
294
+ """Simulate token swap operation"""
295
+ ...