wayfinder-paths 0.1.23__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.
- wayfinder_paths/adapters/balance_adapter/adapter.py +250 -0
- wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -11
- wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
- wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
- wayfinder_paths/adapters/boros_adapter/client.py +476 -0
- wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
- wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
- wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
- wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
- wayfinder_paths/adapters/boros_adapter/types.py +70 -0
- wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
- wayfinder_paths/adapters/brap_adapter/adapter.py +1 -1
- wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +161 -26
- wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +77 -13
- wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +585 -61
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
- wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
- wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +592 -400
- wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +126 -219
- wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
- wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
- wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
- wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
- wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
- wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
- wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
- wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
- wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
- wayfinder_paths/adapters/token_adapter/examples.json +0 -4
- wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
- wayfinder_paths/conftest.py +24 -17
- wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
- wayfinder_paths/core/adapters/models.py +17 -7
- wayfinder_paths/core/clients/BRAPClient.py +1 -1
- wayfinder_paths/core/clients/TokenClient.py +47 -1
- wayfinder_paths/core/clients/WayfinderClient.py +1 -2
- wayfinder_paths/core/clients/protocols.py +21 -22
- wayfinder_paths/core/clients/test_ledger_client.py +448 -0
- wayfinder_paths/core/config.py +12 -0
- wayfinder_paths/core/constants/__init__.py +15 -0
- wayfinder_paths/core/constants/base.py +6 -1
- wayfinder_paths/core/constants/contracts.py +39 -26
- wayfinder_paths/core/constants/erc20_abi.py +0 -1
- wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
- wayfinder_paths/core/constants/hyperliquid.py +16 -0
- wayfinder_paths/core/constants/moonwell_abi.py +0 -15
- wayfinder_paths/core/engine/manifest.py +66 -0
- wayfinder_paths/core/strategies/Strategy.py +0 -61
- wayfinder_paths/core/strategies/__init__.py +10 -1
- wayfinder_paths/core/strategies/opa_loop.py +167 -0
- wayfinder_paths/core/utils/test_transaction.py +289 -0
- wayfinder_paths/core/utils/transaction.py +44 -1
- wayfinder_paths/core/utils/web3.py +3 -0
- wayfinder_paths/mcp/__init__.py +5 -0
- wayfinder_paths/mcp/preview.py +185 -0
- wayfinder_paths/mcp/scripting.py +84 -0
- wayfinder_paths/mcp/server.py +52 -0
- wayfinder_paths/mcp/state/profile_store.py +195 -0
- wayfinder_paths/mcp/state/store.py +89 -0
- wayfinder_paths/mcp/test_scripting.py +267 -0
- wayfinder_paths/mcp/tools/__init__.py +0 -0
- wayfinder_paths/mcp/tools/balances.py +290 -0
- wayfinder_paths/mcp/tools/discovery.py +158 -0
- wayfinder_paths/mcp/tools/execute.py +770 -0
- wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
- wayfinder_paths/mcp/tools/quotes.py +288 -0
- wayfinder_paths/mcp/tools/run_script.py +286 -0
- wayfinder_paths/mcp/tools/strategies.py +188 -0
- wayfinder_paths/mcp/tools/tokens.py +46 -0
- wayfinder_paths/mcp/tools/wallets.py +354 -0
- wayfinder_paths/mcp/utils.py +129 -0
- wayfinder_paths/policies/hyperliquid.py +1 -1
- wayfinder_paths/policies/lifi.py +18 -0
- wayfinder_paths/policies/util.py +8 -2
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +28 -119
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
- wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
- wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
- wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
- wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
- wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
- wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
- wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
- wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
- wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
- wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
- wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
- wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
- wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +202 -0
- wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
- wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +3 -12
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +7 -29
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +63 -40
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -34
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +11 -34
- wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
- wayfinder_paths/tests/test_test_coverage.py +1 -4
- wayfinder_paths-0.1.24.dist-info/METADATA +378 -0
- wayfinder_paths-0.1.24.dist-info/RECORD +185 -0
- {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.24.dist-info}/WHEEL +1 -1
- wayfinder_paths/scripts/create_strategy.py +0 -139
- wayfinder_paths/scripts/make_wallets.py +0 -142
- wayfinder_paths-0.1.23.dist-info/METADATA +0 -354
- wayfinder_paths-0.1.23.dist-info/RECORD +0 -120
- /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
- {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.24.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Boros-specific utilities (tick math, parsing helpers, conversions)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import math
|
|
6
|
+
from decimal import Decimal
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
BOROS_TICK_BASE = 1.0001
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def tick_from_rate(
|
|
13
|
+
rate: float, tick_step: int, *, round_down: bool, base: float = BOROS_TICK_BASE
|
|
14
|
+
) -> int:
|
|
15
|
+
"""Convert APR rate to Boros limitTick."""
|
|
16
|
+
if tick_step <= 0:
|
|
17
|
+
tick_step = 1
|
|
18
|
+
ln_base = math.log(base)
|
|
19
|
+
if rate >= 0:
|
|
20
|
+
if rate == 0:
|
|
21
|
+
return 0
|
|
22
|
+
raw = math.log1p(rate) / (tick_step * ln_base)
|
|
23
|
+
return int(math.floor(raw) if round_down else math.ceil(raw))
|
|
24
|
+
# Negative rate
|
|
25
|
+
raw = math.log1p(-rate) / (tick_step * ln_base)
|
|
26
|
+
return -int(math.floor(raw) if round_down else math.ceil(raw))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def rate_from_tick(tick: int, tick_step: int, base: float = BOROS_TICK_BASE) -> float:
|
|
30
|
+
"""Convert Boros limitTick to APR rate."""
|
|
31
|
+
if tick_step <= 0:
|
|
32
|
+
tick_step = 1
|
|
33
|
+
p = base ** (abs(int(tick)) * int(tick_step))
|
|
34
|
+
r = p - 1
|
|
35
|
+
return r if tick >= 0 else -r
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def normalize_apr(value: Any) -> float | None:
|
|
39
|
+
"""Normalize various APR encodings to decimal.
|
|
40
|
+
|
|
41
|
+
Handles: decimal (0.1115), percent (11.15), bps (1115), 1e18-scaled.
|
|
42
|
+
"""
|
|
43
|
+
if value is None:
|
|
44
|
+
return None
|
|
45
|
+
try:
|
|
46
|
+
x = float(value)
|
|
47
|
+
except (TypeError, ValueError):
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
if x == 0:
|
|
51
|
+
return None
|
|
52
|
+
# 1e18-scaled decimal
|
|
53
|
+
if x > 1e9:
|
|
54
|
+
return x / 1e18
|
|
55
|
+
# bps (1115 = 11.15%)
|
|
56
|
+
if x > 1000:
|
|
57
|
+
return x / 10_000.0
|
|
58
|
+
# percent (11.15 = 11.15%)
|
|
59
|
+
if x > 1:
|
|
60
|
+
return x / 100.0
|
|
61
|
+
# already decimal
|
|
62
|
+
return x
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def cash_wei_to_float(value_wei: Any) -> float:
|
|
66
|
+
"""Convert Boros cash units (1e18) to float."""
|
|
67
|
+
if value_wei is None:
|
|
68
|
+
return 0.0
|
|
69
|
+
try:
|
|
70
|
+
return float(Decimal(str(value_wei)) / Decimal(1e18))
|
|
71
|
+
except Exception:
|
|
72
|
+
return 0.0
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def market_id_from_market_acc(market_acc: str) -> int | None:
|
|
76
|
+
"""Parse a Boros `marketAcc` into a market_id (last 3 bytes)."""
|
|
77
|
+
if not market_acc or len(market_acc) < 8:
|
|
78
|
+
return None
|
|
79
|
+
try:
|
|
80
|
+
market_id = int(market_acc[-6:], 16)
|
|
81
|
+
except ValueError:
|
|
82
|
+
return None
|
|
83
|
+
if market_id == 0xFFFFFF:
|
|
84
|
+
return None
|
|
85
|
+
return market_id
|
|
@@ -61,7 +61,7 @@ class BRAPAdapter(BaseAdapter):
|
|
|
61
61
|
owner_checksum = Web3.to_checksum_address(owner_address)
|
|
62
62
|
spender_checksum = Web3.to_checksum_address(spender_address)
|
|
63
63
|
|
|
64
|
-
if (chain_id, token_checksum
|
|
64
|
+
if (chain_id, token_checksum) in TOKENS_REQUIRING_APPROVAL_RESET:
|
|
65
65
|
allowance = await get_token_allowance(
|
|
66
66
|
token_checksum,
|
|
67
67
|
chain_id,
|
|
@@ -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,
|
|
@@ -36,28 +39,19 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
36
39
|
) -> None:
|
|
37
40
|
super().__init__("hyperlend_adapter", config)
|
|
38
41
|
config = config or {}
|
|
39
|
-
adapter_cfg = config.get("hyperlend_adapter") or {}
|
|
40
42
|
|
|
41
43
|
self.strategy_wallet_signing_callback = strategy_wallet_signing_callback
|
|
42
44
|
self.hyperlend_client = HyperlendClient()
|
|
43
45
|
|
|
46
|
+
self.ledger_adapter = LedgerAdapter()
|
|
47
|
+
self.token_adapter = TokenAdapter()
|
|
44
48
|
strategy_wallet = config.get("strategy_wallet") or {}
|
|
45
49
|
strategy_addr = strategy_wallet.get("address")
|
|
46
|
-
|
|
47
|
-
raise ValueError("strategy_wallet.address is required")
|
|
50
|
+
|
|
48
51
|
self.strategy_wallet_address = to_checksum_address(strategy_addr)
|
|
49
|
-
self.pool_address =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
self.gateway_address = to_checksum_address(
|
|
53
|
-
adapter_cfg.get("wrapped_token_gateway") or HYPERLEND_WRAPPED_TOKEN_GATEWAY
|
|
54
|
-
)
|
|
55
|
-
self.wrapped_native = to_checksum_address(
|
|
56
|
-
adapter_cfg.get("wrapped_native_underlying") or HYPEREVM_WHYPE
|
|
57
|
-
)
|
|
58
|
-
self.gateway_deposit_takes_pool = adapter_cfg.get(
|
|
59
|
-
"gateway_deposit_takes_pool", True
|
|
60
|
-
)
|
|
52
|
+
self.pool_address = HYPERLEND_POOL
|
|
53
|
+
self.gateway_address = HYPERLEND_WRAPPED_TOKEN_GATEWAY
|
|
54
|
+
self.wrapped_native = HYPEREVM_WHYPE
|
|
61
55
|
|
|
62
56
|
async def get_stable_markets(
|
|
63
57
|
self,
|
|
@@ -89,6 +83,46 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
89
83
|
except Exception as exc:
|
|
90
84
|
return False, str(exc)
|
|
91
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
|
+
|
|
92
126
|
async def get_market_entry(
|
|
93
127
|
self,
|
|
94
128
|
*,
|
|
@@ -124,6 +158,7 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
124
158
|
qty: int,
|
|
125
159
|
chain_id: int,
|
|
126
160
|
native: bool = False,
|
|
161
|
+
strategy_name: str | None = None,
|
|
127
162
|
) -> tuple[bool, Any]:
|
|
128
163
|
strategy = self.strategy_wallet_address
|
|
129
164
|
qty = int(qty)
|
|
@@ -132,11 +167,12 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
132
167
|
chain_id = int(chain_id)
|
|
133
168
|
|
|
134
169
|
if native:
|
|
135
|
-
|
|
170
|
+
token_addr = self.wrapped_native
|
|
171
|
+
transaction = await self._encode_call(
|
|
136
172
|
target=self.gateway_address,
|
|
137
173
|
abi=WRAPPED_TOKEN_GATEWAY_ABI,
|
|
138
174
|
fn_name="depositETH",
|
|
139
|
-
args=[self.
|
|
175
|
+
args=[self.pool_address, strategy, 0],
|
|
140
176
|
from_address=strategy,
|
|
141
177
|
chain_id=chain_id,
|
|
142
178
|
value=qty,
|
|
@@ -153,7 +189,7 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
153
189
|
)
|
|
154
190
|
if not approved[0]:
|
|
155
191
|
return approved
|
|
156
|
-
|
|
192
|
+
transaction = await self._encode_call(
|
|
157
193
|
target=self.pool_address,
|
|
158
194
|
abi=POOL_ABI,
|
|
159
195
|
fn_name="supply",
|
|
@@ -161,10 +197,22 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
161
197
|
from_address=strategy,
|
|
162
198
|
chain_id=chain_id,
|
|
163
199
|
)
|
|
200
|
+
|
|
164
201
|
txn_hash = await send_transaction(
|
|
165
|
-
|
|
202
|
+
transaction, self.strategy_wallet_signing_callback
|
|
166
203
|
)
|
|
167
|
-
|
|
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)
|
|
168
216
|
|
|
169
217
|
async def unlend(
|
|
170
218
|
self,
|
|
@@ -173,6 +221,7 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
173
221
|
qty: int,
|
|
174
222
|
chain_id: int,
|
|
175
223
|
native: bool = False,
|
|
224
|
+
strategy_name: str | None = None,
|
|
176
225
|
) -> tuple[bool, Any]:
|
|
177
226
|
strategy = self.strategy_wallet_address
|
|
178
227
|
qty = int(qty)
|
|
@@ -181,11 +230,12 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
181
230
|
chain_id = int(chain_id)
|
|
182
231
|
|
|
183
232
|
if native:
|
|
233
|
+
token_addr = self.wrapped_native
|
|
184
234
|
transaction = await self._encode_call(
|
|
185
235
|
target=self.gateway_address,
|
|
186
236
|
abi=WRAPPED_TOKEN_GATEWAY_ABI,
|
|
187
237
|
fn_name="withdrawETH",
|
|
188
|
-
args=[self.
|
|
238
|
+
args=[self.pool_address, qty, strategy],
|
|
189
239
|
from_address=strategy,
|
|
190
240
|
chain_id=chain_id,
|
|
191
241
|
)
|
|
@@ -199,9 +249,20 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
199
249
|
from_address=strategy,
|
|
200
250
|
chain_id=chain_id,
|
|
201
251
|
)
|
|
252
|
+
|
|
202
253
|
txn_hash = await send_transaction(
|
|
203
254
|
transaction, self.strategy_wallet_signing_callback
|
|
204
255
|
)
|
|
256
|
+
await self._record_pool_op(
|
|
257
|
+
token_address=token_addr,
|
|
258
|
+
amount=qty,
|
|
259
|
+
chain_id=chain_id,
|
|
260
|
+
wallet_address=strategy,
|
|
261
|
+
txn_hash=txn_hash,
|
|
262
|
+
strategy_name=strategy_name,
|
|
263
|
+
op_type="unlend",
|
|
264
|
+
)
|
|
265
|
+
|
|
205
266
|
return True, txn_hash
|
|
206
267
|
|
|
207
268
|
async def _encode_call(
|
|
@@ -235,7 +296,81 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
235
296
|
}
|
|
236
297
|
return transaction
|
|
237
298
|
|
|
238
|
-
def
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
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}"
|
|
350
|
+
)
|
|
351
|
+
return None
|
|
352
|
+
|
|
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)
|
|
359
|
+
|
|
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
|
+
)
|
|
@@ -151,19 +151,6 @@ class TestHyperlendAdapter:
|
|
|
151
151
|
def test_adapter_type(self, adapter):
|
|
152
152
|
assert adapter.adapter_type == "HYPERLEND"
|
|
153
153
|
|
|
154
|
-
@pytest.mark.asyncio
|
|
155
|
-
async def test_health_check(self, adapter):
|
|
156
|
-
health = await adapter.health_check()
|
|
157
|
-
assert isinstance(health, dict)
|
|
158
|
-
assert health.get("status") in {"healthy", "unhealthy", "error"}
|
|
159
|
-
assert health.get("adapter") == "HYPERLEND"
|
|
160
|
-
|
|
161
|
-
@pytest.mark.asyncio
|
|
162
|
-
async def test_connect(self, adapter):
|
|
163
|
-
ok = await adapter.connect()
|
|
164
|
-
assert isinstance(ok, bool)
|
|
165
|
-
assert ok is True
|
|
166
|
-
|
|
167
154
|
@pytest.mark.asyncio
|
|
168
155
|
async def test_get_stable_markets_with_is_stable_symbol(
|
|
169
156
|
self, adapter, mock_hyperlend_client
|
|
@@ -271,6 +258,83 @@ class TestHyperlendAdapter:
|
|
|
271
258
|
assert success is False
|
|
272
259
|
assert "API Error: Invalid address" in data
|
|
273
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
|
+
|
|
274
338
|
@pytest.mark.asyncio
|
|
275
339
|
async def test_get_assets_view_empty_response(self, adapter, mock_hyperlend_client):
|
|
276
340
|
mock_response = {
|
|
@@ -1,18 +1,11 @@
|
|
|
1
|
-
from .adapter import
|
|
2
|
-
|
|
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
|
]
|