wayfinder-paths 0.1.22__py3-none-any.whl → 0.1.24__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 (156) hide show
  1. wayfinder_paths/__init__.py +0 -4
  2. wayfinder_paths/adapters/balance_adapter/README.md +0 -1
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +313 -167
  4. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  5. wayfinder_paths/adapters/balance_adapter/test_adapter.py +41 -124
  6. wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
  7. wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
  8. wayfinder_paths/adapters/boros_adapter/client.py +476 -0
  9. wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
  10. wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
  11. wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
  12. wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
  13. wayfinder_paths/adapters/boros_adapter/types.py +70 -0
  14. wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
  15. wayfinder_paths/adapters/brap_adapter/README.md +22 -75
  16. wayfinder_paths/adapters/brap_adapter/adapter.py +187 -576
  17. wayfinder_paths/adapters/brap_adapter/examples.json +21 -140
  18. wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
  19. wayfinder_paths/adapters/brap_adapter/test_adapter.py +6 -234
  20. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +180 -92
  21. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
  22. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +82 -14
  23. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
  24. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +586 -61
  25. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
  26. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
  27. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
  28. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
  29. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
  30. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
  31. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
  32. wayfinder_paths/adapters/ledger_adapter/README.md +4 -1
  33. wayfinder_paths/adapters/ledger_adapter/adapter.py +3 -3
  34. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
  35. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
  36. wayfinder_paths/adapters/moonwell_adapter/adapter.py +649 -547
  37. wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
  38. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +160 -239
  39. wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
  40. wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
  41. wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
  42. wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
  43. wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
  44. wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
  45. wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
  46. wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
  47. wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
  48. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
  49. wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
  50. wayfinder_paths/adapters/token_adapter/adapter.py +14 -0
  51. wayfinder_paths/adapters/token_adapter/examples.json +0 -4
  52. wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
  53. wayfinder_paths/conftest.py +24 -17
  54. wayfinder_paths/core/__init__.py +0 -3
  55. wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
  56. wayfinder_paths/core/adapters/models.py +17 -7
  57. wayfinder_paths/core/clients/BRAPClient.py +4 -1
  58. wayfinder_paths/core/clients/ClientManager.py +0 -7
  59. wayfinder_paths/core/clients/LedgerClient.py +196 -172
  60. wayfinder_paths/core/clients/TokenClient.py +47 -1
  61. wayfinder_paths/core/clients/WayfinderClient.py +1 -3
  62. wayfinder_paths/core/clients/__init__.py +0 -5
  63. wayfinder_paths/core/clients/protocols.py +21 -35
  64. wayfinder_paths/core/clients/test_ledger_client.py +448 -0
  65. wayfinder_paths/core/config.py +10 -162
  66. wayfinder_paths/core/constants/__init__.py +73 -2
  67. wayfinder_paths/core/constants/base.py +8 -17
  68. wayfinder_paths/core/constants/chains.py +36 -0
  69. wayfinder_paths/core/constants/contracts.py +52 -0
  70. wayfinder_paths/core/constants/erc20_abi.py +0 -1
  71. wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
  72. wayfinder_paths/core/constants/hyperliquid.py +16 -0
  73. wayfinder_paths/core/constants/moonwell_abi.py +0 -15
  74. wayfinder_paths/core/constants/tokens.py +9 -0
  75. wayfinder_paths/core/engine/manifest.py +66 -0
  76. wayfinder_paths/core/strategies/Strategy.py +0 -71
  77. wayfinder_paths/core/strategies/__init__.py +10 -1
  78. wayfinder_paths/core/strategies/opa_loop.py +167 -0
  79. wayfinder_paths/core/utils/evm_helpers.py +5 -15
  80. wayfinder_paths/core/utils/test_transaction.py +289 -0
  81. wayfinder_paths/core/utils/tokens.py +28 -0
  82. wayfinder_paths/core/utils/transaction.py +57 -8
  83. wayfinder_paths/core/utils/web3.py +8 -3
  84. wayfinder_paths/mcp/__init__.py +5 -0
  85. wayfinder_paths/mcp/preview.py +185 -0
  86. wayfinder_paths/mcp/scripting.py +84 -0
  87. wayfinder_paths/mcp/server.py +52 -0
  88. wayfinder_paths/mcp/state/profile_store.py +195 -0
  89. wayfinder_paths/mcp/state/store.py +89 -0
  90. wayfinder_paths/mcp/test_scripting.py +267 -0
  91. wayfinder_paths/mcp/tools/__init__.py +0 -0
  92. wayfinder_paths/mcp/tools/balances.py +290 -0
  93. wayfinder_paths/mcp/tools/discovery.py +158 -0
  94. wayfinder_paths/mcp/tools/execute.py +770 -0
  95. wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
  96. wayfinder_paths/mcp/tools/quotes.py +288 -0
  97. wayfinder_paths/mcp/tools/run_script.py +286 -0
  98. wayfinder_paths/mcp/tools/strategies.py +188 -0
  99. wayfinder_paths/mcp/tools/tokens.py +46 -0
  100. wayfinder_paths/mcp/tools/wallets.py +354 -0
  101. wayfinder_paths/mcp/utils.py +129 -0
  102. wayfinder_paths/policies/enso.py +1 -2
  103. wayfinder_paths/policies/hyper_evm.py +6 -3
  104. wayfinder_paths/policies/hyperlend.py +1 -2
  105. wayfinder_paths/policies/hyperliquid.py +1 -1
  106. wayfinder_paths/policies/lifi.py +18 -0
  107. wayfinder_paths/policies/moonwell.py +12 -7
  108. wayfinder_paths/policies/prjx.py +1 -3
  109. wayfinder_paths/policies/util.py +8 -2
  110. wayfinder_paths/run_strategy.py +97 -300
  111. wayfinder_paths/strategies/basis_trading_strategy/constants.py +3 -1
  112. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +47 -133
  113. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
  114. wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
  115. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
  116. wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
  117. wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
  118. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
  119. wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
  120. wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
  121. wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
  122. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
  123. wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
  124. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
  125. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
  126. wayfinder_paths/{templates/strategy → strategies/boros_hype_strategy}/test_strategy.py +99 -63
  127. wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
  128. wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
  129. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +15 -23
  130. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +27 -62
  131. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +84 -58
  132. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
  133. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +69 -164
  134. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +43 -76
  135. wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
  136. wayfinder_paths/tests/test_test_coverage.py +1 -4
  137. wayfinder_paths-0.1.24.dist-info/METADATA +378 -0
  138. wayfinder_paths-0.1.24.dist-info/RECORD +185 -0
  139. {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.24.dist-info}/WHEEL +1 -1
  140. wayfinder_paths/core/clients/WalletClient.py +0 -41
  141. wayfinder_paths/core/engine/StrategyJob.py +0 -110
  142. wayfinder_paths/core/services/test_local_evm_txn.py +0 -145
  143. wayfinder_paths/scripts/create_strategy.py +0 -139
  144. wayfinder_paths/scripts/make_wallets.py +0 -142
  145. wayfinder_paths/templates/adapter/README.md +0 -150
  146. wayfinder_paths/templates/adapter/adapter.py +0 -16
  147. wayfinder_paths/templates/adapter/examples.json +0 -8
  148. wayfinder_paths/templates/adapter/test_adapter.py +0 -30
  149. wayfinder_paths/templates/strategy/README.md +0 -186
  150. wayfinder_paths/templates/strategy/examples.json +0 -11
  151. wayfinder_paths/templates/strategy/strategy.py +0 -35
  152. wayfinder_paths/tests/test_smoke_manifest.py +0 -63
  153. wayfinder_paths-0.1.22.dist-info/METADATA +0 -355
  154. wayfinder_paths-0.1.22.dist-info/RECORD +0 -129
  155. /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
  156. {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.24.dist-info}/LICENSE +0 -0
@@ -1,10 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Any
3
+ from typing import Any, Literal
4
4
 
5
5
  from eth_utils import to_checksum_address
6
6
 
7
+ from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
8
+ from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
7
9
  from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
10
+ from wayfinder_paths.core.adapters.models import LEND, UNLEND
8
11
  from wayfinder_paths.core.clients.HyperlendClient import (
9
12
  AssetsView,
10
13
  HyperlendClient,
@@ -12,23 +15,19 @@ from wayfinder_paths.core.clients.HyperlendClient import (
12
15
  MarketEntry,
13
16
  StableMarketsHeadroomResponse,
14
17
  )
18
+ from wayfinder_paths.core.constants.contracts import (
19
+ HYPEREVM_WHYPE,
20
+ HYPERLEND_POOL,
21
+ HYPERLEND_WRAPPED_TOKEN_GATEWAY,
22
+ )
15
23
  from wayfinder_paths.core.constants.hyperlend_abi import (
16
24
  POOL_ABI,
17
25
  WRAPPED_TOKEN_GATEWAY_ABI,
18
26
  )
19
- from wayfinder_paths.core.utils.tokens import (
20
- build_approve_transaction,
21
- get_token_allowance,
22
- )
27
+ from wayfinder_paths.core.utils.tokens import ensure_allowance
23
28
  from wayfinder_paths.core.utils.transaction import send_transaction
24
29
  from wayfinder_paths.core.utils.web3 import web3_from_chain_id
25
30
 
26
- HYPERLEND_DEFAULTS = {
27
- "pool": "0x00A89d7a5A02160f20150EbEA7a2b5E4879A1A8b",
28
- "wrapped_token_gateway": "0x49558c794ea2aC8974C9F27886DDfAa951E99171",
29
- "wrapped_native_underlying": "0x5555555555555555555555555555555555555555",
30
- }
31
-
32
31
 
33
32
  class HyperlendAdapter(BaseAdapter):
34
33
  adapter_type = "HYPERLEND"
@@ -39,31 +38,20 @@ class HyperlendAdapter(BaseAdapter):
39
38
  strategy_wallet_signing_callback=None,
40
39
  ) -> None:
41
40
  super().__init__("hyperlend_adapter", config)
42
- cfg = config or {}
43
- adapter_cfg = cfg.get("hyperlend_adapter") or {}
41
+ config = config or {}
44
42
 
45
43
  self.strategy_wallet_signing_callback = strategy_wallet_signing_callback
46
44
  self.hyperlend_client = HyperlendClient()
47
45
 
48
- self.strategy_wallet = cfg.get("strategy_wallet") or {}
49
- self.pool_address = self._checksum(
50
- adapter_cfg.get("pool_address") or HYPERLEND_DEFAULTS["pool"]
51
- )
52
- self.gateway_address = self._checksum(
53
- adapter_cfg.get("wrapped_token_gateway")
54
- or HYPERLEND_DEFAULTS["wrapped_token_gateway"]
55
- )
56
- self.wrapped_native = self._checksum(
57
- adapter_cfg.get("wrapped_native_underlying")
58
- or HYPERLEND_DEFAULTS["wrapped_native_underlying"]
59
- )
60
- self.gateway_deposit_takes_pool = adapter_cfg.get(
61
- "gateway_deposit_takes_pool", True
62
- )
46
+ self.ledger_adapter = LedgerAdapter()
47
+ self.token_adapter = TokenAdapter()
48
+ strategy_wallet = config.get("strategy_wallet") or {}
49
+ strategy_addr = strategy_wallet.get("address")
63
50
 
64
- # ------------------------------------------------------------------ #
65
- # Public API #
66
- # ------------------------------------------------------------------ #
51
+ self.strategy_wallet_address = to_checksum_address(strategy_addr)
52
+ self.pool_address = HYPERLEND_POOL
53
+ self.gateway_address = HYPERLEND_WRAPPED_TOKEN_GATEWAY
54
+ self.wrapped_native = HYPEREVM_WHYPE
67
55
 
68
56
  async def get_stable_markets(
69
57
  self,
@@ -95,6 +83,46 @@ class HyperlendAdapter(BaseAdapter):
95
83
  except Exception as exc:
96
84
  return False, str(exc)
97
85
 
86
+ async def get_full_user_state(
87
+ self,
88
+ *,
89
+ account: str,
90
+ include_zero_positions: bool = False,
91
+ ) -> tuple[bool, dict[str, Any] | str]:
92
+ """
93
+ Full Hyperlend user state.
94
+
95
+ Backed by the Wayfinder API `hyperlend/assets/` response which already includes
96
+ per-asset supply/borrow balances and account health factor.
97
+ """
98
+ ok, view = await self.get_assets_view(user_address=account)
99
+ if not ok:
100
+ return False, str(view)
101
+
102
+ assets = view.get("assets", []) if isinstance(view, dict) else []
103
+ if include_zero_positions:
104
+ positions = assets
105
+ else:
106
+ positions = [
107
+ a
108
+ for a in assets
109
+ if float(a.get("supply", 0) or 0) > 0
110
+ or float(a.get("variable_borrow", 0) or 0) > 0
111
+ ]
112
+
113
+ return (
114
+ True,
115
+ {
116
+ "protocol": "hyperlend",
117
+ "account": account,
118
+ "positions": positions,
119
+ "accountData": view.get("account_data")
120
+ if isinstance(view, dict)
121
+ else {},
122
+ "assetsView": view,
123
+ },
124
+ )
125
+
98
126
  async def get_market_entry(
99
127
  self,
100
128
  *,
@@ -130,35 +158,38 @@ class HyperlendAdapter(BaseAdapter):
130
158
  qty: int,
131
159
  chain_id: int,
132
160
  native: bool = False,
161
+ strategy_name: str | None = None,
133
162
  ) -> tuple[bool, Any]:
134
- strategy = self._strategy_address()
163
+ strategy = self.strategy_wallet_address
135
164
  qty = int(qty)
136
165
  if qty <= 0:
137
166
  return False, "qty must be positive"
138
167
  chain_id = int(chain_id)
139
168
 
140
169
  if native:
141
- tx = await self._encode_call(
170
+ token_addr = self.wrapped_native
171
+ transaction = await self._encode_call(
142
172
  target=self.gateway_address,
143
173
  abi=WRAPPED_TOKEN_GATEWAY_ABI,
144
174
  fn_name="depositETH",
145
- args=[self._gateway_first_arg(underlying_token), strategy, 0],
175
+ args=[self.pool_address, strategy, 0],
146
176
  from_address=strategy,
147
177
  chain_id=chain_id,
148
178
  value=qty,
149
179
  )
150
180
  else:
151
- token_addr = self._checksum(underlying_token)
152
- approved = await self._ensure_allowance(
181
+ token_addr = to_checksum_address(underlying_token)
182
+ approved = await ensure_allowance(
153
183
  token_address=token_addr,
154
184
  owner=strategy,
155
185
  spender=self.pool_address,
156
186
  amount=qty,
157
187
  chain_id=chain_id,
188
+ signing_callback=self.strategy_wallet_signing_callback,
158
189
  )
159
190
  if not approved[0]:
160
191
  return approved
161
- tx = await self._encode_call(
192
+ transaction = await self._encode_call(
162
193
  target=self.pool_address,
163
194
  abi=POOL_ABI,
164
195
  fn_name="supply",
@@ -166,7 +197,22 @@ class HyperlendAdapter(BaseAdapter):
166
197
  from_address=strategy,
167
198
  chain_id=chain_id,
168
199
  )
169
- return await self._send_tx(tx)
200
+
201
+ txn_hash = await send_transaction(
202
+ transaction, self.strategy_wallet_signing_callback
203
+ )
204
+
205
+ await self._record_pool_op(
206
+ token_address=token_addr,
207
+ amount=qty,
208
+ chain_id=chain_id,
209
+ wallet_address=strategy,
210
+ txn_hash=txn_hash,
211
+ strategy_name=strategy_name,
212
+ op_type="lend",
213
+ )
214
+
215
+ return (True, txn_hash)
170
216
 
171
217
  async def unlend(
172
218
  self,
@@ -175,25 +221,27 @@ class HyperlendAdapter(BaseAdapter):
175
221
  qty: int,
176
222
  chain_id: int,
177
223
  native: bool = False,
224
+ strategy_name: str | None = None,
178
225
  ) -> tuple[bool, Any]:
179
- strategy = self._strategy_address()
226
+ strategy = self.strategy_wallet_address
180
227
  qty = int(qty)
181
228
  if qty <= 0:
182
229
  return False, "qty must be positive"
183
230
  chain_id = int(chain_id)
184
231
 
185
232
  if native:
186
- tx = await self._encode_call(
233
+ token_addr = self.wrapped_native
234
+ transaction = await self._encode_call(
187
235
  target=self.gateway_address,
188
236
  abi=WRAPPED_TOKEN_GATEWAY_ABI,
189
237
  fn_name="withdrawETH",
190
- args=[self._gateway_first_arg(underlying_token), qty, strategy],
238
+ args=[self.pool_address, qty, strategy],
191
239
  from_address=strategy,
192
240
  chain_id=chain_id,
193
241
  )
194
242
  else:
195
- token_addr = self._checksum(underlying_token)
196
- tx = await self._encode_call(
243
+ token_addr = to_checksum_address(underlying_token)
244
+ transaction = await self._encode_call(
197
245
  target=self.pool_address,
198
246
  abi=POOL_ABI,
199
247
  fn_name="withdraw",
@@ -201,36 +249,21 @@ class HyperlendAdapter(BaseAdapter):
201
249
  from_address=strategy,
202
250
  chain_id=chain_id,
203
251
  )
204
- return await self._send_tx(tx)
205
-
206
- # ------------------------------------------------------------------ #
207
- # Helpers #
208
- # ------------------------------------------------------------------ #
209
-
210
- async def _send_tx(self, tx: dict[str, Any]) -> tuple[bool, Any]:
211
- txn_hash = await send_transaction(tx, self.strategy_wallet_signing_callback)
212
- return True, txn_hash
213
252
 
214
- async def _ensure_allowance(
215
- self,
216
- *,
217
- token_address: str,
218
- owner: str,
219
- spender: str,
220
- amount: int,
221
- chain_id: int,
222
- ) -> tuple[bool, Any]:
223
- allowance = await get_token_allowance(token_address, chain_id, owner, spender)
224
- if allowance >= amount:
225
- return True, {}
226
- approve_tx = await build_approve_transaction(
227
- from_address=owner,
253
+ txn_hash = await send_transaction(
254
+ transaction, self.strategy_wallet_signing_callback
255
+ )
256
+ await self._record_pool_op(
257
+ token_address=token_addr,
258
+ amount=qty,
228
259
  chain_id=chain_id,
229
- token_address=token_address,
230
- spender_address=spender,
231
- amount=amount,
260
+ wallet_address=strategy,
261
+ txn_hash=txn_hash,
262
+ strategy_name=strategy_name,
263
+ op_type="unlend",
232
264
  )
233
- return await self._send_tx(approve_tx)
265
+
266
+ return True, txn_hash
234
267
 
235
268
  async def _encode_call(
236
269
  self,
@@ -254,35 +287,90 @@ class HyperlendAdapter(BaseAdapter):
254
287
  except ValueError as exc:
255
288
  raise ValueError(f"Failed to encode {fn_name}: {exc}") from exc
256
289
 
257
- tx: dict[str, Any] = {
290
+ transaction: dict[str, Any] = {
258
291
  "chainId": int(chain_id),
259
292
  "from": to_checksum_address(from_address),
260
293
  "to": to_checksum_address(target),
261
294
  "data": data,
262
295
  "value": int(value),
263
296
  }
264
- return tx
297
+ return transaction
265
298
 
266
- def _strategy_address(self) -> str:
267
- addr = None
268
- if isinstance(self.strategy_wallet, dict):
269
- addr = self.strategy_wallet.get("address") or (
270
- (self.strategy_wallet.get("evm") or {}).get("address")
271
- )
272
- elif isinstance(self.strategy_wallet, str):
273
- addr = self.strategy_wallet
274
- if not addr:
275
- raise ValueError(
276
- "strategy_wallet address is required for HyperLend operations"
299
+ async def _record_pool_op(
300
+ self,
301
+ token_address: str,
302
+ amount: int,
303
+ chain_id: int,
304
+ wallet_address: str,
305
+ txn_hash: str,
306
+ op_type: Literal["lend", "unlend"],
307
+ strategy_name: str | None = None,
308
+ ):
309
+ amount_usd = await self._calculate_amount_usd(
310
+ token_address=token_address,
311
+ amount=amount,
312
+ chain_id=chain_id,
313
+ )
314
+
315
+ model = {"lend": LEND, "unlend": UNLEND}[op_type]
316
+
317
+ operation_data = model(
318
+ adapter=self.adapter_type,
319
+ token_address=token_address,
320
+ pool_address=self.pool_address,
321
+ amount=str(amount),
322
+ amount_usd=amount_usd or 0,
323
+ transaction_hash=txn_hash,
324
+ transaction_chain_id=chain_id,
325
+ )
326
+
327
+ success, ledger_response = await self.ledger_adapter.record_operation(
328
+ wallet_address=wallet_address,
329
+ operation_data=operation_data,
330
+ usd_value=amount_usd or 0,
331
+ strategy_name=strategy_name,
332
+ )
333
+ if not success:
334
+ self.logger.warning("Ledger record failed", error=ledger_response)
335
+
336
+ async def _calculate_amount_usd(
337
+ self,
338
+ token_address: str,
339
+ amount: int,
340
+ chain_id: int,
341
+ ) -> float | None:
342
+ # Get token details with market data using the address as query
343
+ success, token_data = await self.token_adapter.get_token(
344
+ query=token_address,
345
+ chain_id=chain_id,
346
+ )
347
+ if not success or not token_data:
348
+ self.logger.warning(
349
+ f"Could not get token info for {token_address} on chain {chain_id}"
277
350
  )
278
- return to_checksum_address(addr)
351
+ return None
279
352
 
280
- def _gateway_first_arg(self, underlying_token: str) -> str:
281
- if self.gateway_deposit_takes_pool:
282
- return self.pool_address
283
- return self._checksum(underlying_token) or self.wrapped_native
353
+ # Get price data
354
+ decimals, current_price = (
355
+ token_data["decimals"],
356
+ token_data["current_price"],
357
+ )
358
+ return current_price * float(amount) / 10 ** int(decimals)
284
359
 
285
- def _checksum(self, address: str | None) -> str:
286
- if not address:
287
- raise ValueError("Missing required contract address in HyperLend config")
288
- return to_checksum_address(address)
360
+ async def _ensure_allowance(
361
+ self,
362
+ *,
363
+ token_address: str,
364
+ owner: str,
365
+ spender: str,
366
+ amount: int,
367
+ chain_id: int,
368
+ ) -> tuple[bool, Any]:
369
+ return await ensure_allowance(
370
+ token_address=token_address,
371
+ owner=owner,
372
+ spender=spender,
373
+ amount=amount,
374
+ chain_id=chain_id,
375
+ signing_callback=self.strategy_wallet_signing_callback,
376
+ )
@@ -0,0 +1,9 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "adapters.hyperlend_adapter.adapter.HyperlendAdapter"
3
+ capabilities:
4
+ - "market.stable_markets"
5
+ - "market.assets_view"
6
+ - "market.rate_history"
7
+ - "lending.lend"
8
+ - "lending.unlend"
9
+ dependencies: []
@@ -14,7 +14,11 @@ class TestHyperlendAdapter:
14
14
  @pytest.fixture
15
15
  def adapter(self, mock_hyperlend_client):
16
16
  adapter = HyperlendAdapter(
17
- config={},
17
+ config={
18
+ "strategy_wallet": {
19
+ "address": "0x1234567890123456789012345678901234567890"
20
+ }
21
+ },
18
22
  )
19
23
  adapter.hyperlend_client = mock_hyperlend_client
20
24
  return adapter
@@ -147,19 +151,6 @@ class TestHyperlendAdapter:
147
151
  def test_adapter_type(self, adapter):
148
152
  assert adapter.adapter_type == "HYPERLEND"
149
153
 
150
- @pytest.mark.asyncio
151
- async def test_health_check(self, adapter):
152
- health = await adapter.health_check()
153
- assert isinstance(health, dict)
154
- assert health.get("status") in {"healthy", "unhealthy", "error"}
155
- assert health.get("adapter") == "HYPERLEND"
156
-
157
- @pytest.mark.asyncio
158
- async def test_connect(self, adapter):
159
- ok = await adapter.connect()
160
- assert isinstance(ok, bool)
161
- assert ok is True
162
-
163
154
  @pytest.mark.asyncio
164
155
  async def test_get_stable_markets_with_is_stable_symbol(
165
156
  self, adapter, mock_hyperlend_client
@@ -267,6 +258,83 @@ class TestHyperlendAdapter:
267
258
  assert success is False
268
259
  assert "API Error: Invalid address" in data
269
260
 
261
+ @pytest.mark.asyncio
262
+ async def test_get_full_user_state_filters_zero_positions(
263
+ self, adapter, mock_hyperlend_client
264
+ ):
265
+ mock_response = {
266
+ "block_number": 12345,
267
+ "user": "0xabc",
268
+ "native_balance_wei": 0,
269
+ "native_balance": 0.0,
270
+ "assets": [
271
+ {
272
+ "underlying": "0x1",
273
+ "symbol": "USDC",
274
+ "symbol_canonical": "usdc",
275
+ "symbol_display": "USDC",
276
+ "decimals": 6,
277
+ "a_token": "0xa",
278
+ "variable_debt_token": "0xd",
279
+ "usage_as_collateral_enabled": True,
280
+ "borrowing_enabled": True,
281
+ "is_active": True,
282
+ "is_frozen": False,
283
+ "is_paused": False,
284
+ "is_siloed_borrowing": False,
285
+ "is_stablecoin": True,
286
+ "underlying_wallet_balance": 0.0,
287
+ "underlying_wallet_balance_wei": 0,
288
+ "price_usd": 1.0,
289
+ "supply": 0.0,
290
+ "variable_borrow": 0.0,
291
+ "supply_usd": 0.0,
292
+ "variable_borrow_usd": 0.0,
293
+ "supply_apr": 0.0,
294
+ "supply_apy": 0.0,
295
+ "variable_borrow_apr": 0.0,
296
+ "variable_borrow_apy": 0.0,
297
+ },
298
+ {
299
+ "underlying": "0x2",
300
+ "symbol": "USDT",
301
+ "symbol_canonical": "usdt",
302
+ "symbol_display": "USDT",
303
+ "decimals": 6,
304
+ "a_token": "0xa2",
305
+ "variable_debt_token": "0xd2",
306
+ "usage_as_collateral_enabled": True,
307
+ "borrowing_enabled": True,
308
+ "is_active": True,
309
+ "is_frozen": False,
310
+ "is_paused": False,
311
+ "is_siloed_borrowing": False,
312
+ "is_stablecoin": True,
313
+ "underlying_wallet_balance": 0.0,
314
+ "underlying_wallet_balance_wei": 0,
315
+ "price_usd": 1.0,
316
+ "supply": 123.0,
317
+ "variable_borrow": 0.0,
318
+ "supply_usd": 123.0,
319
+ "variable_borrow_usd": 0.0,
320
+ "supply_apr": 0.05,
321
+ "supply_apy": 0.05,
322
+ "variable_borrow_apr": 0.07,
323
+ "variable_borrow_apy": 0.07,
324
+ },
325
+ ],
326
+ "account_data": {"health_factor": 2.0},
327
+ "base_currency_info": {},
328
+ }
329
+ mock_hyperlend_client.get_assets_view = AsyncMock(return_value=mock_response)
330
+
331
+ ok, state = await adapter.get_full_user_state(account="0xabc")
332
+ assert ok is True
333
+ assert state["protocol"] == "hyperlend"
334
+ assert state["account"] == "0xabc"
335
+ assert len(state["positions"]) == 1
336
+ assert state["positions"][0]["symbol"] == "USDT"
337
+
270
338
  @pytest.mark.asyncio
271
339
  async def test_get_assets_view_empty_response(self, adapter, mock_hyperlend_client):
272
340
  mock_response = {
@@ -1,18 +1,11 @@
1
- from .adapter import (
2
- ARBITRUM_USDC_ADDRESS,
3
- HYPERLIQUID_BRIDGE_ADDRESS,
4
- HyperliquidAdapter,
5
- )
6
- from .executor import HyperliquidExecutor, LocalHyperliquidExecutor
1
+ from .adapter import HyperliquidAdapter
2
+ from .executor import LocalHyperliquidExecutor
7
3
  from .paired_filler import FillConfig, FillConfirmCfg, PairedFiller
8
4
 
9
5
  __all__ = [
10
6
  "HyperliquidAdapter",
11
- "HyperliquidExecutor",
12
7
  "LocalHyperliquidExecutor",
13
8
  "PairedFiller",
14
9
  "FillConfig",
15
10
  "FillConfirmCfg",
16
- "HYPERLIQUID_BRIDGE_ADDRESS",
17
- "ARBITRUM_USDC_ADDRESS",
18
11
  ]