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,192 @@
1
+ """
2
+ Simulation Client
3
+ Handles blockchain transaction simulations via Gorlami/Tenderly
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import time
9
+ from typing import NotRequired, Required, TypedDict
10
+
11
+ from loguru import logger
12
+
13
+ from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
14
+
15
+
16
+ class SimulationResult(TypedDict):
17
+ """Simulation result structure"""
18
+
19
+ success: Required[bool]
20
+ gas_used: NotRequired[str | None]
21
+ gas_price: NotRequired[str | None]
22
+ balances: NotRequired[dict[str, str]]
23
+ error: NotRequired[str | None]
24
+
25
+
26
+ class SimulationClient(WayfinderClient):
27
+ """Client for blockchain transaction simulations"""
28
+
29
+ def __init__(self, api_key: str | None = None):
30
+ super().__init__(api_key=api_key)
31
+
32
+ async def simulate_send(
33
+ self,
34
+ from_address: str,
35
+ to_address: str,
36
+ token_address: str,
37
+ amount: str,
38
+ chain_id: int,
39
+ initial_balances: dict[str, str],
40
+ ) -> SimulationResult:
41
+ """
42
+ Simulate sending native ETH or ERC20 tokens.
43
+
44
+ Args:
45
+ from_address: Source wallet address
46
+ to_address: Destination wallet address
47
+ token_address: Token contract address (use 0x0 for native ETH)
48
+ amount: Amount to send
49
+ chain_id: Blockchain chain ID
50
+ initial_balances: Initial token balances for simulation
51
+
52
+ Returns:
53
+ Simulation result data
54
+ """
55
+ logger.info(
56
+ f"Simulating send: {amount} from {from_address} to {to_address} (chain {chain_id})"
57
+ )
58
+ start_time = time.time()
59
+
60
+ url = f"{self.api_base_url}public/simulate/send/"
61
+
62
+ payload = {
63
+ "from_address": from_address,
64
+ "to_address": to_address,
65
+ "token_address": token_address,
66
+ "amount": amount,
67
+ "chain_id": chain_id,
68
+ "initial_balances": initial_balances,
69
+ }
70
+
71
+ try:
72
+ response = await self._request("POST", url, json=payload, headers={})
73
+ response.raise_for_status()
74
+ data = response.json()
75
+ elapsed = time.time() - start_time
76
+ logger.info(f"Simulation request completed successfully in {elapsed:.2f}s")
77
+ return data.get("data", data)
78
+ except Exception as e:
79
+ elapsed = time.time() - start_time
80
+ logger.error(f"Simulation request failed after {elapsed:.2f}s: {e}")
81
+ raise
82
+
83
+ async def simulate_approve(
84
+ self,
85
+ from_address: str,
86
+ to_address: str,
87
+ token_address: str,
88
+ amount: str,
89
+ chain_id: int,
90
+ initial_balances: dict[str, str],
91
+ clear_approval_first: bool = False,
92
+ ) -> SimulationResult:
93
+ """
94
+ Simulate ERC20 token approval.
95
+
96
+ Args:
97
+ from_address: Address approving the tokens
98
+ to_address: Address being approved to spend tokens
99
+ token_address: ERC20 token contract address
100
+ amount: Amount to approve
101
+ chain_id: Blockchain chain ID
102
+ initial_balances: Initial token balances for simulation
103
+ clear_approval_first: Whether to clear existing approval before setting new one
104
+
105
+ Returns:
106
+ Simulation result data
107
+ """
108
+ logger.info(
109
+ f"Simulating approval: {amount} from {from_address} to {to_address} (chain {chain_id})"
110
+ )
111
+ start_time = time.time()
112
+
113
+ url = f"{self.api_base_url}public/simulate/approve/"
114
+
115
+ payload = {
116
+ "from_address": from_address,
117
+ "to_address": to_address,
118
+ "token_address": token_address,
119
+ "amount": amount,
120
+ "chain_id": chain_id,
121
+ "initial_balances": initial_balances,
122
+ "clear_approval_first": clear_approval_first,
123
+ }
124
+
125
+ try:
126
+ response = await self._request("POST", url, json=payload, headers={})
127
+ response.raise_for_status()
128
+ data = response.json()
129
+ elapsed = time.time() - start_time
130
+ logger.info(f"Simulation request completed successfully in {elapsed:.2f}s")
131
+ return data.get("data", data)
132
+ except Exception as e:
133
+ elapsed = time.time() - start_time
134
+ logger.error(f"Simulation request failed after {elapsed:.2f}s: {e}")
135
+ raise
136
+
137
+ async def simulate_swap(
138
+ self,
139
+ from_token_address: str,
140
+ to_token_address: str,
141
+ from_chain_id: int,
142
+ to_chain_id: int,
143
+ amount: str,
144
+ from_address: str,
145
+ slippage: float,
146
+ initial_balances: dict[str, str],
147
+ ) -> SimulationResult:
148
+ """
149
+ Simulate token swap operation.
150
+
151
+ Args:
152
+ from_token_address: Source token contract address
153
+ to_token_address: Destination token contract address
154
+ from_chain_id: Source chain ID
155
+ to_chain_id: Destination chain ID
156
+ amount: Amount to swap
157
+ from_address: Wallet address initiating swap
158
+ slippage: Maximum slippage tolerance
159
+ initial_balances: Initial token balances for simulation
160
+
161
+ Returns:
162
+ Simulation result data
163
+ """
164
+ logger.info(
165
+ f"Simulating swap: {from_token_address} -> {to_token_address} (chain {from_chain_id} -> {to_chain_id})"
166
+ )
167
+ start_time = time.time()
168
+
169
+ url = f"{self.api_base_url}public/simulate/swap/"
170
+
171
+ payload = {
172
+ "from_token_address": from_token_address,
173
+ "to_token_address": to_token_address,
174
+ "from_chain_id": from_chain_id,
175
+ "to_chain_id": to_chain_id,
176
+ "amount": amount,
177
+ "from_address": from_address,
178
+ "slippage": slippage,
179
+ "initial_balances": initial_balances,
180
+ }
181
+
182
+ try:
183
+ response = await self._request("POST", url, json=payload, headers={})
184
+ response.raise_for_status()
185
+ data = response.json()
186
+ elapsed = time.time() - start_time
187
+ logger.info(f"Simulation request completed successfully in {elapsed:.2f}s")
188
+ return data.get("data", data)
189
+ except Exception as e:
190
+ elapsed = time.time() - start_time
191
+ logger.error(f"Simulation request failed after {elapsed:.2f}s: {e}")
192
+ raise
@@ -0,0 +1,89 @@
1
+ """
2
+ Token Adapter
3
+ Handles token information, prices, and parsing
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import NotRequired, Required, TypedDict
9
+
10
+ from wayfinder_paths.core.clients.AuthClient import AuthClient
11
+ from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
12
+ from wayfinder_paths.core.settings import settings
13
+
14
+
15
+ class TokenDetails(TypedDict):
16
+ """Token details response structure"""
17
+
18
+ id: Required[str]
19
+ address: Required[str]
20
+ symbol: Required[str]
21
+ name: Required[str]
22
+ decimals: Required[int]
23
+ chain_id: Required[int]
24
+ chain_code: Required[str]
25
+ price_usd: NotRequired[float]
26
+ price: NotRequired[float]
27
+ image_url: NotRequired[str | None]
28
+ coingecko_id: NotRequired[str | None]
29
+
30
+
31
+ class GasToken(TypedDict):
32
+ """Gas token response structure"""
33
+
34
+ id: Required[str]
35
+ address: Required[str]
36
+ symbol: Required[str]
37
+ name: Required[str]
38
+ decimals: Required[int]
39
+ chain_id: Required[int]
40
+ chain_code: Required[str]
41
+ price_usd: NotRequired[float]
42
+
43
+
44
+ class TokenClient(WayfinderClient):
45
+ """Adapter for token-related operations"""
46
+
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 ==============
53
+
54
+ async def get_token_details(
55
+ self, token_id: str, force_refresh: bool = False
56
+ ) -> TokenDetails:
57
+ """
58
+ Get token data including price from the token-details endpoint
59
+
60
+ Args:
61
+ token_id: Token identifier or address
62
+
63
+ Returns:
64
+ Full token data including price information
65
+ """
66
+ url = f"{settings.WAYFINDER_API_URL}/public/tokens/detail/"
67
+ params = {
68
+ "query": token_id,
69
+ "get_chart": "false",
70
+ "force_refresh": str(force_refresh),
71
+ }
72
+ # Public endpoint: do not pass auth headers
73
+ response = await self._request("GET", url, params=params, headers={})
74
+ response.raise_for_status()
75
+ data = response.json()
76
+ return data.get("data", data)
77
+
78
+ async def get_gas_token(self, chain_code: str) -> GasToken:
79
+ """
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
82
+ """
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={})
87
+ response.raise_for_status()
88
+ data = response.json()
89
+ return data.get("data", data)
@@ -0,0 +1,63 @@
1
+ """
2
+ Transaction Client
3
+ Handles transaction building, gas estimation, and monitoring
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import NotRequired, Required, TypedDict
9
+
10
+ from wayfinder_paths.core.clients.AuthClient import AuthClient
11
+ from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
12
+ from wayfinder_paths.core.settings import settings
13
+
14
+
15
+ class TransactionPayload(TypedDict):
16
+ """Transaction payload structure"""
17
+
18
+ from_address: Required[str]
19
+ to_address: Required[str]
20
+ token_address: Required[str]
21
+ amount: Required[str]
22
+ chain_id: Required[int]
23
+ data: NotRequired[str | None]
24
+ value: NotRequired[str | None]
25
+ gas_limit: NotRequired[str | None]
26
+ gas_price: NotRequired[str | None]
27
+
28
+
29
+ class TransactionClient(WayfinderClient):
30
+ """Client for transaction operations"""
31
+
32
+ def __init__(self, api_key: str | None = None):
33
+ super().__init__(api_key=api_key)
34
+ self.api_base_url = f"{self.api_base_url}/transactions"
35
+ self._auth_client: AuthClient | None = AuthClient(api_key=api_key)
36
+
37
+ # ============== Protected (Auth Required) Endpoints ==============
38
+
39
+ async def build_send(
40
+ self,
41
+ from_address: str,
42
+ to_address: str,
43
+ token_address: str,
44
+ amount: float,
45
+ chain_id: int,
46
+ ) -> TransactionPayload:
47
+ """
48
+ Build a send transaction payload for EVM tokens/native transfers.
49
+
50
+ GET /api/v1/public/transactions/build-send/?from_address=...&to_address=...&token_address=...&amount=...&chain_id=...
51
+ """
52
+ url = f"{settings.WAYFINDER_API_URL}/public/transactions/build-send/"
53
+ params = {
54
+ "from_address": from_address,
55
+ "to_address": to_address,
56
+ "token_address": token_address,
57
+ "amount": str(amount),
58
+ "chain_id": str(chain_id),
59
+ }
60
+ response = await self._authed_request("GET", url, params=params)
61
+ response.raise_for_status()
62
+ data = response.json()
63
+ return data.get("data", data)
@@ -0,0 +1,94 @@
1
+ """
2
+ Wallet Client
3
+ Fetches wallet-related data such as aggregated balances for the authenticated user.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ from typing import NotRequired, Required, TypedDict
9
+
10
+ from wayfinder_paths.core.clients.AuthClient import AuthClient
11
+ from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
12
+ from wayfinder_paths.core.settings import settings
13
+
14
+
15
+ class TokenBalance(TypedDict):
16
+ """Token balance response structure"""
17
+
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]
32
+ balance_human: NotRequired[float | None]
33
+ usd_value: NotRequired[float | None]
34
+
35
+
36
+ class EnrichedBalances(TypedDict):
37
+ """Enriched token balances response structure"""
38
+
39
+ wallet_address: Required[str]
40
+ balances: Required[list[TokenBalance]]
41
+ total_usd_value: NotRequired[float | None]
42
+
43
+
44
+ class WalletClient(WayfinderClient):
45
+ def __init__(self, api_key: str | None = None):
46
+ super().__init__(api_key=api_key)
47
+ self.api_base_url = f"{settings.WAYFINDER_API_URL}"
48
+ self._auth_client = AuthClient(api_key=api_key)
49
+
50
+ async def get_token_balance_for_wallet(
51
+ self,
52
+ *,
53
+ token_id: str,
54
+ wallet_address: str,
55
+ human_readable: bool = True,
56
+ ) -> TokenBalance:
57
+ """
58
+ Fetch a single token balance for an explicit wallet address.
59
+
60
+ Mirrors POST /api/v1/public/balances/token/
61
+ """
62
+ url = f"{self.api_base_url}/public/balances/token/"
63
+ payload = {
64
+ "token_id": token_id,
65
+ "wallet_address": wallet_address,
66
+ "human_readable": human_readable,
67
+ }
68
+ response = await self._authed_request("POST", url, json=payload)
69
+ data = response.json()
70
+ return data.get("data", data)
71
+
72
+ async def get_pool_balance_for_wallet(
73
+ self,
74
+ *,
75
+ pool_address: str,
76
+ chain_id: int,
77
+ user_address: str,
78
+ human_readable: bool = True,
79
+ ) -> PoolBalance:
80
+ """
81
+ Fetch a wallet's LP/share balance for a given pool address and chain.
82
+
83
+ Mirrors POST /api/v1/public/balances/pool/
84
+ """
85
+ url = f"{self.api_base_url}/public/balances/pool/"
86
+ payload = {
87
+ "pool_address": pool_address,
88
+ "chain_id": chain_id,
89
+ "user_address": user_address,
90
+ "human_readable": human_readable,
91
+ }
92
+ response = await self._authed_request("POST", url, json=payload)
93
+ data = response.json()
94
+ return data.get("data", data)