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.
- wayfinder_paths/__init__.py +0 -4
- wayfinder_paths/adapters/balance_adapter/README.md +0 -1
- wayfinder_paths/adapters/balance_adapter/adapter.py +313 -167
- wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +41 -124
- 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/README.md +22 -75
- wayfinder_paths/adapters/brap_adapter/adapter.py +187 -576
- wayfinder_paths/adapters/brap_adapter/examples.json +21 -140
- wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +6 -234
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +180 -92
- wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +82 -14
- wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +586 -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/README.md +4 -1
- wayfinder_paths/adapters/ledger_adapter/adapter.py +3 -3
- 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 +649 -547
- wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +160 -239
- 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/adapter.py +14 -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/__init__.py +0 -3
- wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
- wayfinder_paths/core/adapters/models.py +17 -7
- wayfinder_paths/core/clients/BRAPClient.py +4 -1
- wayfinder_paths/core/clients/ClientManager.py +0 -7
- wayfinder_paths/core/clients/LedgerClient.py +196 -172
- wayfinder_paths/core/clients/TokenClient.py +47 -1
- wayfinder_paths/core/clients/WayfinderClient.py +1 -3
- wayfinder_paths/core/clients/__init__.py +0 -5
- wayfinder_paths/core/clients/protocols.py +21 -35
- wayfinder_paths/core/clients/test_ledger_client.py +448 -0
- wayfinder_paths/core/config.py +10 -162
- wayfinder_paths/core/constants/__init__.py +73 -2
- wayfinder_paths/core/constants/base.py +8 -17
- wayfinder_paths/core/constants/chains.py +36 -0
- wayfinder_paths/core/constants/contracts.py +52 -0
- 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/constants/tokens.py +9 -0
- wayfinder_paths/core/engine/manifest.py +66 -0
- wayfinder_paths/core/strategies/Strategy.py +0 -71
- wayfinder_paths/core/strategies/__init__.py +10 -1
- wayfinder_paths/core/strategies/opa_loop.py +167 -0
- wayfinder_paths/core/utils/evm_helpers.py +5 -15
- wayfinder_paths/core/utils/test_transaction.py +289 -0
- wayfinder_paths/core/utils/tokens.py +28 -0
- wayfinder_paths/core/utils/transaction.py +57 -8
- wayfinder_paths/core/utils/web3.py +8 -3
- 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/enso.py +1 -2
- wayfinder_paths/policies/hyper_evm.py +6 -3
- wayfinder_paths/policies/hyperlend.py +1 -2
- wayfinder_paths/policies/hyperliquid.py +1 -1
- wayfinder_paths/policies/lifi.py +18 -0
- wayfinder_paths/policies/moonwell.py +12 -7
- wayfinder_paths/policies/prjx.py +1 -3
- wayfinder_paths/policies/util.py +8 -2
- wayfinder_paths/run_strategy.py +97 -300
- wayfinder_paths/strategies/basis_trading_strategy/constants.py +3 -1
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +47 -133
- 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/{templates/strategy → strategies/boros_hype_strategy}/test_strategy.py +99 -63
- 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 +15 -23
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +27 -62
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +84 -58
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +69 -164
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +43 -76
- 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.22.dist-info → wayfinder_paths-0.1.24.dist-info}/WHEEL +1 -1
- wayfinder_paths/core/clients/WalletClient.py +0 -41
- wayfinder_paths/core/engine/StrategyJob.py +0 -110
- wayfinder_paths/core/services/test_local_evm_txn.py +0 -145
- wayfinder_paths/scripts/create_strategy.py +0 -139
- wayfinder_paths/scripts/make_wallets.py +0 -142
- wayfinder_paths/templates/adapter/README.md +0 -150
- wayfinder_paths/templates/adapter/adapter.py +0 -16
- wayfinder_paths/templates/adapter/examples.json +0 -8
- wayfinder_paths/templates/adapter/test_adapter.py +0 -30
- wayfinder_paths/templates/strategy/README.md +0 -186
- wayfinder_paths/templates/strategy/examples.json +0 -11
- wayfinder_paths/templates/strategy/strategy.py +0 -35
- wayfinder_paths/tests/test_smoke_manifest.py +0 -63
- wayfinder_paths-0.1.22.dist-info/METADATA +0 -355
- wayfinder_paths-0.1.22.dist-info/RECORD +0 -129
- /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
- {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
|
-
|
|
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.
|
|
49
|
-
self.
|
|
50
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
152
|
-
approved = await
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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 =
|
|
196
|
-
|
|
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
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
|
|
230
|
-
|
|
231
|
-
|
|
260
|
+
wallet_address=strategy,
|
|
261
|
+
txn_hash=txn_hash,
|
|
262
|
+
strategy_name=strategy_name,
|
|
263
|
+
op_type="unlend",
|
|
232
264
|
)
|
|
233
|
-
|
|
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
|
-
|
|
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
|
|
297
|
+
return transaction
|
|
265
298
|
|
|
266
|
-
def
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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
|
-
|
|
351
|
+
return None
|
|
279
352
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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
|
+
)
|
|
@@ -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
|
-
|
|
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
|
]
|