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
|
@@ -18,6 +18,7 @@ from wayfinder_paths.adapters.hyperlend_adapter.adapter import HyperlendAdapter
|
|
|
18
18
|
from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
|
|
19
19
|
from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
|
|
20
20
|
from wayfinder_paths.core.constants.base import DEFAULT_SLIPPAGE
|
|
21
|
+
from wayfinder_paths.core.constants.contracts import HYPEREVM_WHYPE
|
|
21
22
|
from wayfinder_paths.core.strategies.descriptors import (
|
|
22
23
|
Complexity,
|
|
23
24
|
Directionality,
|
|
@@ -41,6 +42,7 @@ from wayfinder_paths.policies.hyperliquid import (
|
|
|
41
42
|
any_hyperliquid_l1_payload,
|
|
42
43
|
any_hyperliquid_user_payload,
|
|
43
44
|
)
|
|
45
|
+
from wayfinder_paths.policies.lifi import LIFI_ROUTERS, lifi_swap
|
|
44
46
|
from wayfinder_paths.policies.prjx import PRJX_ROUTER, prjx_swap
|
|
45
47
|
|
|
46
48
|
SYMBOL_TRANSLATION_TABLE = str.maketrans(
|
|
@@ -50,7 +52,7 @@ SYMBOL_TRANSLATION_TABLE = str.maketrans(
|
|
|
50
52
|
"Ξ": "X",
|
|
51
53
|
}
|
|
52
54
|
)
|
|
53
|
-
WRAPPED_HYPE_ADDRESS =
|
|
55
|
+
WRAPPED_HYPE_ADDRESS = HYPEREVM_WHYPE
|
|
54
56
|
|
|
55
57
|
|
|
56
58
|
class HyperlendStableYieldStrategy(Strategy):
|
|
@@ -245,15 +247,6 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
245
247
|
strategy_wallet_signing_callback=self.strategy_wallet_signing_callback,
|
|
246
248
|
)
|
|
247
249
|
|
|
248
|
-
self.register_adapters(
|
|
249
|
-
[
|
|
250
|
-
balance,
|
|
251
|
-
token_adapter,
|
|
252
|
-
ledger_adapter,
|
|
253
|
-
brap_adapter,
|
|
254
|
-
hyperlend_adapter,
|
|
255
|
-
]
|
|
256
|
-
)
|
|
257
250
|
self.balance_adapter = balance
|
|
258
251
|
self.token_adapter = token_adapter
|
|
259
252
|
self.ledger_adapter = ledger_adapter
|
|
@@ -338,7 +331,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
338
331
|
success,
|
|
339
332
|
main_usdt0_balance,
|
|
340
333
|
) = await self.balance_adapter.get_balance(
|
|
341
|
-
|
|
334
|
+
token_id=self.usdt_token_info.get("token_id"),
|
|
342
335
|
wallet_address=self._get_main_wallet_address(),
|
|
343
336
|
)
|
|
344
337
|
if not success:
|
|
@@ -351,7 +344,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
351
344
|
success,
|
|
352
345
|
main_hype_balance,
|
|
353
346
|
) = await self.balance_adapter.get_balance(
|
|
354
|
-
|
|
347
|
+
token_id=self.hype_token_info.get("token_id"),
|
|
355
348
|
wallet_address=self._get_main_wallet_address(),
|
|
356
349
|
)
|
|
357
350
|
if not success:
|
|
@@ -839,7 +832,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
839
832
|
total_usdt = 0.0
|
|
840
833
|
try:
|
|
841
834
|
_, total_usdt_wei = await self.balance_adapter.get_balance(
|
|
842
|
-
|
|
835
|
+
token_id=self.usdt_token_info.get("token_id"),
|
|
843
836
|
wallet_address=self._get_strategy_wallet_address(),
|
|
844
837
|
)
|
|
845
838
|
if total_usdt_wei and total_usdt_wei > 0:
|
|
@@ -852,7 +845,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
852
845
|
total_hype = 0.0
|
|
853
846
|
try:
|
|
854
847
|
_, total_hype_wei = await self.balance_adapter.get_balance(
|
|
855
|
-
|
|
848
|
+
token_id=self.hype_token_info.get("token_id"),
|
|
856
849
|
wallet_address=self._get_strategy_wallet_address(),
|
|
857
850
|
)
|
|
858
851
|
if total_hype_wei and total_hype_wei > 0:
|
|
@@ -865,7 +858,6 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
865
858
|
if sweep_actions:
|
|
866
859
|
messages.append(f"Residual sweeps: {'; '.join(sweep_actions)}.")
|
|
867
860
|
|
|
868
|
-
# Report balances in strategy wallet
|
|
869
861
|
balance_parts = []
|
|
870
862
|
if total_usdt > 0:
|
|
871
863
|
balance_parts.append(
|
|
@@ -902,13 +894,12 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
902
894
|
|
|
903
895
|
transferred_items = []
|
|
904
896
|
|
|
905
|
-
# Transfer USDT0 to main wallet
|
|
906
897
|
usdt_ok, usdt_raw = await self.balance_adapter.get_balance(
|
|
907
898
|
token_id="usdt0-hyperevm",
|
|
908
899
|
wallet_address=strategy_address,
|
|
909
900
|
)
|
|
910
901
|
if usdt_ok and usdt_raw:
|
|
911
|
-
usdt_balance = float(usdt_raw
|
|
902
|
+
usdt_balance = float(usdt_raw) / 1e6 # USDT has 6 decimals
|
|
912
903
|
if usdt_balance > 1.0:
|
|
913
904
|
self.logger.info(
|
|
914
905
|
f"Transferring {usdt_balance:.2f} USDT0 to main wallet"
|
|
@@ -917,7 +908,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
917
908
|
success,
|
|
918
909
|
msg,
|
|
919
910
|
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
920
|
-
|
|
911
|
+
token_id="usdt0-hyperevm",
|
|
921
912
|
amount=usdt_balance,
|
|
922
913
|
strategy_name=self.name,
|
|
923
914
|
skip_ledger=False,
|
|
@@ -927,13 +918,12 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
927
918
|
else:
|
|
928
919
|
self.logger.warning(f"USDT0 transfer failed: {msg}")
|
|
929
920
|
|
|
930
|
-
# Transfer HYPE (minus reserve for tx fees) to main wallet
|
|
931
921
|
hype_ok, hype_raw = await self.balance_adapter.get_balance(
|
|
932
922
|
token_id="hyperliquid-hyperevm",
|
|
933
923
|
wallet_address=strategy_address,
|
|
934
924
|
)
|
|
935
925
|
if hype_ok and hype_raw:
|
|
936
|
-
hype_balance = float(hype_raw
|
|
926
|
+
hype_balance = float(hype_raw) / 1e18 # HYPE has 18 decimals
|
|
937
927
|
tx_fee_reserve = 0.1
|
|
938
928
|
transferable_hype = hype_balance - tx_fee_reserve
|
|
939
929
|
if transferable_hype > 0.01:
|
|
@@ -944,7 +934,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
944
934
|
success,
|
|
945
935
|
msg,
|
|
946
936
|
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
947
|
-
|
|
937
|
+
token_id="hyperliquid-hyperevm",
|
|
948
938
|
amount=transferable_hype,
|
|
949
939
|
strategy_name=self.name,
|
|
950
940
|
skip_ledger=False,
|
|
@@ -1014,7 +1004,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
1014
1004
|
success,
|
|
1015
1005
|
message,
|
|
1016
1006
|
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
1017
|
-
|
|
1007
|
+
token_id=token_info.get("token_id"),
|
|
1018
1008
|
amount=amount_tokens,
|
|
1019
1009
|
strategy_name=self.name,
|
|
1020
1010
|
)
|
|
@@ -2275,7 +2265,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
2275
2265
|
success,
|
|
2276
2266
|
strategy_hype_balance_wei,
|
|
2277
2267
|
) = await self.balance_adapter.get_balance(
|
|
2278
|
-
|
|
2268
|
+
token_id=self.hype_token_info.get("token_id"),
|
|
2279
2269
|
wallet_address=self._get_strategy_wallet_address(),
|
|
2280
2270
|
)
|
|
2281
2271
|
hype_price = asset_map.get(WRAPPED_HYPE_ADDRESS, {}).get("price_usd") or 0.0
|
|
@@ -2381,4 +2371,6 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
2381
2371
|
await enso_swap(),
|
|
2382
2372
|
erc20_spender_for_any_token(PRJX_ROUTER),
|
|
2383
2373
|
await prjx_swap(),
|
|
2374
|
+
erc20_spender_for_any_token(LIFI_ROUTERS[999]),
|
|
2375
|
+
await lifi_swap(999),
|
|
2384
2376
|
]
|
|
@@ -1,36 +1,15 @@
|
|
|
1
|
-
import sys
|
|
2
1
|
from pathlib import Path
|
|
3
2
|
from unittest.mock import AsyncMock
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
_wayfinder_path_str = str(_wayfinder_path_dir)
|
|
9
|
-
if _wayfinder_path_str not in sys.path:
|
|
10
|
-
sys.path.insert(0, _wayfinder_path_str)
|
|
11
|
-
elif sys.path.index(_wayfinder_path_str) > 0:
|
|
12
|
-
# Move to front to take precedence
|
|
13
|
-
sys.path.remove(_wayfinder_path_str)
|
|
14
|
-
sys.path.insert(0, _wayfinder_path_str)
|
|
15
|
-
|
|
16
|
-
import pytest # noqa: E402
|
|
17
|
-
|
|
18
|
-
try:
|
|
19
|
-
from tests.test_utils import get_canonical_examples, load_strategy_examples
|
|
20
|
-
except ImportError:
|
|
21
|
-
# Fallback if path setup didn't work
|
|
22
|
-
import importlib.util
|
|
23
|
-
|
|
24
|
-
test_utils_path = Path(_wayfinder_path_dir) / "tests" / "test_utils.py"
|
|
25
|
-
spec = importlib.util.spec_from_file_location("tests.test_utils", test_utils_path)
|
|
26
|
-
test_utils = importlib.util.module_from_spec(spec)
|
|
27
|
-
spec.loader.exec_module(test_utils)
|
|
28
|
-
get_canonical_examples = test_utils.get_canonical_examples
|
|
29
|
-
load_strategy_examples = test_utils.load_strategy_examples
|
|
30
|
-
|
|
31
|
-
from wayfinder_paths.strategies.hyperlend_stable_yield_strategy.strategy import ( # noqa: E402
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from wayfinder_paths.strategies.hyperlend_stable_yield_strategy.strategy import (
|
|
32
7
|
HyperlendStableYieldStrategy,
|
|
33
8
|
)
|
|
9
|
+
from wayfinder_paths.tests.test_utils import (
|
|
10
|
+
get_canonical_examples,
|
|
11
|
+
load_strategy_examples,
|
|
12
|
+
)
|
|
34
13
|
|
|
35
14
|
|
|
36
15
|
@pytest.fixture
|
|
@@ -48,10 +27,9 @@ def strategy():
|
|
|
48
27
|
|
|
49
28
|
if hasattr(s, "balance_adapter") and s.balance_adapter:
|
|
50
29
|
# Mock balances: 1000 USDT0 (with 6 decimals) and 2 HYPE (with 18 decimals)
|
|
51
|
-
def get_balance_side_effect(
|
|
52
|
-
token_id =
|
|
53
|
-
|
|
54
|
-
)
|
|
30
|
+
def get_balance_side_effect(
|
|
31
|
+
*, wallet_address, token_id=None, token_address=None, chain_id=None
|
|
32
|
+
):
|
|
55
33
|
token_id_str = str(token_id).lower() if token_id else ""
|
|
56
34
|
if "usdt0" in token_id_str or token_id_str == "usdt0":
|
|
57
35
|
# 1000 USDT0 with 6 decimals = 1000 * 10^6 = 1000000000
|
|
@@ -102,7 +80,6 @@ def strategy():
|
|
|
102
80
|
)
|
|
103
81
|
|
|
104
82
|
if hasattr(s, "balance_adapter") and s.balance_adapter:
|
|
105
|
-
# Mock the main methods first
|
|
106
83
|
s.balance_adapter.move_from_main_wallet_to_strategy_wallet = AsyncMock(
|
|
107
84
|
return_value=(True, "Transfer successful (simulated)")
|
|
108
85
|
)
|
|
@@ -145,9 +122,7 @@ def strategy():
|
|
|
145
122
|
)
|
|
146
123
|
|
|
147
124
|
if hasattr(s, "ledger_adapter") and s.ledger_adapter:
|
|
148
|
-
s.ledger_adapter.get_strategy_net_deposit = AsyncMock(
|
|
149
|
-
return_value=(True, {"net_deposit": 0})
|
|
150
|
-
)
|
|
125
|
+
s.ledger_adapter.get_strategy_net_deposit = AsyncMock(return_value=(True, 0.0))
|
|
151
126
|
s.ledger_adapter.get_strategy_transactions = AsyncMock(
|
|
152
127
|
return_value=(True, {"transactions": []})
|
|
153
128
|
)
|
|
@@ -155,46 +130,36 @@ def strategy():
|
|
|
155
130
|
if hasattr(s, "brap_adapter") and s.brap_adapter:
|
|
156
131
|
usdt0_address = "0x1234567890123456789012345678901234567890"
|
|
157
132
|
|
|
158
|
-
def
|
|
133
|
+
def best_quote_side_effect(*args, **kwargs):
|
|
159
134
|
to_token_address = kwargs.get("to_token_address", "")
|
|
160
135
|
if to_token_address == usdt0_address:
|
|
161
|
-
return (
|
|
162
|
-
True,
|
|
163
|
-
{
|
|
164
|
-
"quotes": {
|
|
165
|
-
"best_quote": {
|
|
166
|
-
"output_amount": "99900000",
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
)
|
|
136
|
+
return (True, {"output_amount": "99900000"})
|
|
171
137
|
return (
|
|
172
138
|
True,
|
|
173
139
|
{
|
|
174
|
-
"
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
"fromToken": {"symbol": "USDT0"},
|
|
182
|
-
"toToken": {"symbol": "HYPE"},
|
|
183
|
-
}
|
|
184
|
-
}
|
|
140
|
+
"output_amount": "105000000",
|
|
141
|
+
"input_amount": "50000000000000",
|
|
142
|
+
"toAmount": "105000000",
|
|
143
|
+
"estimatedGas": "1000000000",
|
|
144
|
+
"fromAmount": "100000000",
|
|
145
|
+
"fromToken": {"symbol": "USDT0"},
|
|
146
|
+
"toToken": {"symbol": "HYPE"},
|
|
185
147
|
},
|
|
186
148
|
)
|
|
187
149
|
|
|
188
|
-
s.brap_adapter.
|
|
189
|
-
side_effect=get_swap_quote_side_effect
|
|
190
|
-
)
|
|
150
|
+
s.brap_adapter.best_quote = AsyncMock(side_effect=best_quote_side_effect)
|
|
191
151
|
|
|
192
152
|
if (
|
|
193
153
|
hasattr(s, "brap_adapter")
|
|
194
154
|
and s.brap_adapter
|
|
195
155
|
and hasattr(s.brap_adapter, "swap_from_quote")
|
|
196
156
|
):
|
|
197
|
-
s.brap_adapter.swap_from_quote = AsyncMock(
|
|
157
|
+
s.brap_adapter.swap_from_quote = AsyncMock(
|
|
158
|
+
return_value=(
|
|
159
|
+
True,
|
|
160
|
+
{"tx_hash": "0xmockhash", "from_amount": "100", "to_amount": "99"},
|
|
161
|
+
)
|
|
162
|
+
)
|
|
198
163
|
|
|
199
164
|
if hasattr(s, "hyperlend_adapter") and s.hyperlend_adapter:
|
|
200
165
|
s.hyperlend_adapter.get_assets_view = AsyncMock(
|
|
@@ -13,7 +13,17 @@ from wayfinder_paths.adapters.brap_adapter.adapter import BRAPAdapter
|
|
|
13
13
|
from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
|
|
14
14
|
from wayfinder_paths.adapters.moonwell_adapter.adapter import MoonwellAdapter
|
|
15
15
|
from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
|
|
16
|
+
from wayfinder_paths.core.constants.chains import CHAIN_ID_BASE
|
|
17
|
+
from wayfinder_paths.core.constants.contracts import BASE_USDC, BASE_WSTETH
|
|
16
18
|
from wayfinder_paths.core.constants.erc20_abi import ERC20_ABI
|
|
19
|
+
from wayfinder_paths.core.constants.tokens import (
|
|
20
|
+
TOKEN_ID_ETH_BASE,
|
|
21
|
+
TOKEN_ID_STETH,
|
|
22
|
+
TOKEN_ID_USDC_BASE,
|
|
23
|
+
TOKEN_ID_WELL_BASE,
|
|
24
|
+
TOKEN_ID_WETH_BASE,
|
|
25
|
+
TOKEN_ID_WSTETH_BASE,
|
|
26
|
+
)
|
|
17
27
|
from wayfinder_paths.core.strategies.descriptors import (
|
|
18
28
|
Complexity,
|
|
19
29
|
Directionality,
|
|
@@ -38,28 +48,22 @@ from wayfinder_paths.policies.moonwell import (
|
|
|
38
48
|
weth_deposit,
|
|
39
49
|
)
|
|
40
50
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
WSTETH = "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452"
|
|
44
|
-
|
|
45
|
-
# Token IDs
|
|
46
|
-
USDC_TOKEN_ID = "usd-coin-base"
|
|
47
|
-
WETH_TOKEN_ID = "l2-standard-bridged-weth-base-base"
|
|
48
|
-
WSTETH_TOKEN_ID = "superbridge-bridged-wsteth-base-base"
|
|
49
|
-
ETH_TOKEN_ID = "ethereum-base"
|
|
50
|
-
WELL_TOKEN_ID = "moonwell-artemis-base"
|
|
51
|
-
STETH_TOKEN_ID = "staked-ether-ethereum"
|
|
51
|
+
USDC = BASE_USDC
|
|
52
|
+
WSTETH = BASE_WSTETH
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
USDC_TOKEN_ID = TOKEN_ID_USDC_BASE
|
|
55
|
+
WETH_TOKEN_ID = TOKEN_ID_WETH_BASE
|
|
56
|
+
WSTETH_TOKEN_ID = TOKEN_ID_WSTETH_BASE
|
|
57
|
+
ETH_TOKEN_ID = TOKEN_ID_ETH_BASE
|
|
58
|
+
WELL_TOKEN_ID = TOKEN_ID_WELL_BASE
|
|
59
|
+
STETH_TOKEN_ID = TOKEN_ID_STETH
|
|
55
60
|
|
|
56
|
-
|
|
57
|
-
# 0.98 = 2% safety margin when borrowing to avoid hitting exact liquidation threshold
|
|
61
|
+
BASE_CHAIN_ID = CHAIN_ID_BASE
|
|
58
62
|
COLLATERAL_SAFETY_FACTOR = 0.98
|
|
59
63
|
|
|
60
64
|
|
|
61
65
|
class SwapOutcomeUnknownError(RuntimeError):
|
|
62
|
-
|
|
66
|
+
pass
|
|
63
67
|
|
|
64
68
|
|
|
65
69
|
class MoonwellWstethLoopStrategy(Strategy):
|
|
@@ -192,14 +196,12 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
192
196
|
|
|
193
197
|
self.config = merged_config
|
|
194
198
|
|
|
195
|
-
# Adapter references
|
|
196
199
|
self.balance_adapter: BalanceAdapter | None = None
|
|
197
200
|
self.moonwell_adapter: MoonwellAdapter | None = None
|
|
198
201
|
self.brap_adapter: BRAPAdapter | None = None
|
|
199
202
|
self.token_adapter: TokenAdapter | None = None
|
|
200
203
|
self.ledger_adapter: LedgerAdapter | None = None
|
|
201
204
|
|
|
202
|
-
# Token info cache
|
|
203
205
|
self._token_info_cache: dict[str, dict] = {}
|
|
204
206
|
self._token_price_cache: dict[str, float] = {}
|
|
205
207
|
self._token_price_timestamps: dict[str, float] = {}
|
|
@@ -235,16 +237,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
235
237
|
strategy_wallet_signing_callback=self.strategy_wallet_signing_callback,
|
|
236
238
|
)
|
|
237
239
|
|
|
238
|
-
self.register_adapters(
|
|
239
|
-
[
|
|
240
|
-
balance,
|
|
241
|
-
token_adapter,
|
|
242
|
-
ledger_adapter,
|
|
243
|
-
brap_adapter,
|
|
244
|
-
moonwell_adapter,
|
|
245
|
-
]
|
|
246
|
-
)
|
|
247
|
-
|
|
248
240
|
self.balance_adapter = balance
|
|
249
241
|
self.token_adapter = token_adapter
|
|
250
242
|
self.ledger_adapter = ledger_adapter
|
|
@@ -373,11 +365,9 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
373
365
|
int((weth_pos or {}).get("borrow_balance", 0) or 0) if weth_pos_ok else 0
|
|
374
366
|
)
|
|
375
367
|
|
|
376
|
-
# Gas reserve
|
|
377
368
|
gas_keep_wei = int(self._gas_keep_wei())
|
|
378
369
|
eth_usable_wei = max(0, int(wallet_eth) - int(gas_keep_wei))
|
|
379
370
|
|
|
380
|
-
# USD conversions
|
|
381
371
|
def _usd(raw: int, price: float, dec: int) -> float:
|
|
382
372
|
if raw <= 0 or not price or price <= 0:
|
|
383
373
|
return 0.0
|
|
@@ -1554,7 +1544,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
1554
1544
|
if self.token_adapter is None:
|
|
1555
1545
|
raise RuntimeError("Token adapter not initialized.")
|
|
1556
1546
|
|
|
1557
|
-
# Pre-fetch token info
|
|
1558
1547
|
for token_id in [USDC_TOKEN_ID, WETH_TOKEN_ID, WSTETH_TOKEN_ID, ETH_TOKEN_ID]:
|
|
1559
1548
|
try:
|
|
1560
1549
|
success, info = await self.token_adapter.get_token(token_id)
|
|
@@ -1718,7 +1707,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
1718
1707
|
logger.info(
|
|
1719
1708
|
f"Swap succeeded on attempt {i + 1} with slippage {slippage * 100:.1f}%"
|
|
1720
1709
|
)
|
|
1721
|
-
# Ensure result is a dict with to_amount
|
|
1722
1710
|
if isinstance(result, dict):
|
|
1723
1711
|
return result
|
|
1724
1712
|
return {"to_amount": result if isinstance(result, int) else 0}
|
|
@@ -1744,7 +1732,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
1744
1732
|
f"failed with slippage {slippage * 100:.1f}%: {e}"
|
|
1745
1733
|
)
|
|
1746
1734
|
if i < max_retries - 1:
|
|
1747
|
-
# Exponential backoff: 1s, 2s, 4s
|
|
1748
1735
|
await asyncio.sleep(2**i)
|
|
1749
1736
|
|
|
1750
1737
|
logger.error(
|
|
@@ -1834,7 +1821,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
1834
1821
|
if ("429" in err or "Too Many Requests" in err) and attempt < (
|
|
1835
1822
|
max_retries - 1
|
|
1836
1823
|
):
|
|
1837
|
-
# Backoff: 1s, 2s
|
|
1838
1824
|
await asyncio.sleep(2**attempt)
|
|
1839
1825
|
continue
|
|
1840
1826
|
logger.warning(
|
|
@@ -1940,6 +1926,34 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
1940
1926
|
token_id=USDC_TOKEN_ID, wallet_address=self._get_strategy_wallet_address()
|
|
1941
1927
|
)
|
|
1942
1928
|
|
|
1929
|
+
async def _get_wallet_balances_usd(self) -> dict[str, dict]:
|
|
1930
|
+
strategy_addr = self._get_strategy_wallet_address()
|
|
1931
|
+
|
|
1932
|
+
weth_raw, usdc_raw, wsteth_raw = await asyncio.gather(
|
|
1933
|
+
self._get_balance_raw(token_id=WETH_TOKEN_ID, wallet_address=strategy_addr),
|
|
1934
|
+
self._get_balance_raw(token_id=USDC_TOKEN_ID, wallet_address=strategy_addr),
|
|
1935
|
+
self._get_balance_raw(
|
|
1936
|
+
token_id=WSTETH_TOKEN_ID, wallet_address=strategy_addr
|
|
1937
|
+
),
|
|
1938
|
+
)
|
|
1939
|
+
|
|
1940
|
+
# Get token data (prices cached from _aggregate_positions)
|
|
1941
|
+
token_data = await asyncio.gather(
|
|
1942
|
+
self._get_token_data(WETH_TOKEN_ID),
|
|
1943
|
+
self._get_token_data(USDC_TOKEN_ID),
|
|
1944
|
+
self._get_token_data(WSTETH_TOKEN_ID),
|
|
1945
|
+
)
|
|
1946
|
+
|
|
1947
|
+
def calc(raw: int, decimals: int, price: float) -> dict:
|
|
1948
|
+
tokens = raw / 10**decimals if raw > 0 else 0.0
|
|
1949
|
+
return {"tokens": tokens, "usd": tokens * price}
|
|
1950
|
+
|
|
1951
|
+
return {
|
|
1952
|
+
"WETH": calc(weth_raw, token_data[0][1], token_data[0][0]),
|
|
1953
|
+
"USDC": calc(usdc_raw, token_data[1][1], token_data[1][0]),
|
|
1954
|
+
"wstETH": calc(wsteth_raw, token_data[2][1], token_data[2][0]),
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1943
1957
|
async def _validate_gas_balance(self) -> tuple[bool, str]:
|
|
1944
1958
|
gas_balance = await self._get_gas_balance()
|
|
1945
1959
|
main_gas = await self._get_balance_raw(
|
|
@@ -1978,7 +1992,7 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
1978
1992
|
return (True, "USDC deposit amount validated", usdc_amount)
|
|
1979
1993
|
|
|
1980
1994
|
async def _check_quote_profitability(self) -> tuple[bool, str]:
|
|
1981
|
-
quote = await self.
|
|
1995
|
+
quote = await self._quote()
|
|
1982
1996
|
if quote.get("apy", 0) < 0:
|
|
1983
1997
|
return (
|
|
1984
1998
|
False,
|
|
@@ -2023,7 +2037,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
2023
2037
|
if exclude is None:
|
|
2024
2038
|
exclude = set()
|
|
2025
2039
|
|
|
2026
|
-
# Always exclude gas token and target
|
|
2027
2040
|
exclude.add(ETH_TOKEN_ID)
|
|
2028
2041
|
exclude.add(target_token_id)
|
|
2029
2042
|
|
|
@@ -2078,7 +2091,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
2078
2091
|
return (True, f"Swept {swept_count} tokens totaling ${total_swept_usd:.2f}")
|
|
2079
2092
|
|
|
2080
2093
|
async def _claim_and_reinvest_rewards(self) -> tuple[bool, str]:
|
|
2081
|
-
# Claim rewards if above threshold
|
|
2082
2094
|
claimed_ok, claimed = await self.moonwell_adapter.claim_rewards(
|
|
2083
2095
|
min_rewards_usd=self.MIN_REWARD_CLAIM_USD
|
|
2084
2096
|
)
|
|
@@ -2105,7 +2117,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
2105
2117
|
)
|
|
2106
2118
|
return (True, f"WELL value ${well_value_usd:.2f} below threshold")
|
|
2107
2119
|
|
|
2108
|
-
# Swap WELL → USDC
|
|
2109
2120
|
logger.info(
|
|
2110
2121
|
f"Swapping {well_balance / 10**well_decimals:.4f} WELL "
|
|
2111
2122
|
f"(${well_value_usd:.2f}) to USDC"
|
|
@@ -2173,7 +2184,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
2173
2184
|
if not success:
|
|
2174
2185
|
return (False, message)
|
|
2175
2186
|
|
|
2176
|
-
# Transfer USDC to vault wallet
|
|
2177
2187
|
success, message = await self._transfer_usdc_to_vault(usdc_amount)
|
|
2178
2188
|
if not success:
|
|
2179
2189
|
return (False, message)
|
|
@@ -2228,7 +2238,7 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
2228
2238
|
logger.warning(f"Failed to fetch stETH APY: {e}")
|
|
2229
2239
|
return None
|
|
2230
2240
|
|
|
2231
|
-
async def
|
|
2241
|
+
async def _quote(self) -> dict:
|
|
2232
2242
|
(
|
|
2233
2243
|
usdc_apy_result,
|
|
2234
2244
|
weth_apy_result,
|
|
@@ -2350,7 +2360,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
2350
2360
|
weth_bal = int(weth_after)
|
|
2351
2361
|
|
|
2352
2362
|
if eth_delta > 0 and usable_eth > 0:
|
|
2353
|
-
# Borrow arrived as native ETH - wrap it first
|
|
2354
2363
|
wrap_amt = min(int(safe_borrow_amt), int(usable_eth))
|
|
2355
2364
|
logger.info(
|
|
2356
2365
|
f"Borrow arrived as native ETH, wrapping {wrap_amt / 10**18:.6f} ETH to WETH"
|
|
@@ -2478,7 +2487,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
2478
2487
|
min(to_amount_wei, wsteth_bal) if wsteth_bal > 0 else to_amount_wei
|
|
2479
2488
|
)
|
|
2480
2489
|
|
|
2481
|
-
# If swap produced 0 wstETH, rollback the borrow
|
|
2482
2490
|
if lend_amt_wei <= 0:
|
|
2483
2491
|
logger.warning("Swap resulted in 0 wstETH. Rolling back borrow...")
|
|
2484
2492
|
try:
|
|
@@ -2818,7 +2826,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
2818
2826
|
borrow_bal = weth_pos[1].get("borrow_balance", 0)
|
|
2819
2827
|
current_borrowed_value = (borrow_bal / 10**18) * weth_price
|
|
2820
2828
|
|
|
2821
|
-
# Lend USDC and enable as collateral
|
|
2822
2829
|
success, msg = await self.moonwell_adapter.lend(
|
|
2823
2830
|
mtoken=M_USDC,
|
|
2824
2831
|
underlying_token=USDC,
|
|
@@ -2894,7 +2901,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
2894
2901
|
)
|
|
2895
2902
|
logger.info("Entered M_WETH market to enable borrowing")
|
|
2896
2903
|
|
|
2897
|
-
# Use provided collateral factors or fetch them
|
|
2898
2904
|
if collateral_factors is not None:
|
|
2899
2905
|
cf_u, cf_w = collateral_factors
|
|
2900
2906
|
else:
|
|
@@ -2902,7 +2908,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
2902
2908
|
|
|
2903
2909
|
max_safe_f = self._max_safe_F(cf_w)
|
|
2904
2910
|
|
|
2905
|
-
# Guard against division by zero/negative denominator
|
|
2906
2911
|
denominator = self.TARGET_HEALTH_FACTOR + 0.001 - cf_w
|
|
2907
2912
|
if denominator <= 0:
|
|
2908
2913
|
logger.warning(
|
|
@@ -3053,7 +3058,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3053
3058
|
|
|
3054
3059
|
snap, _ = await self._accounting_snapshot(collateral_factors=collateral_factors)
|
|
3055
3060
|
|
|
3056
|
-
# Log current state
|
|
3057
3061
|
logger.info("-" * 40)
|
|
3058
3062
|
logger.info("CURRENT STATE:")
|
|
3059
3063
|
logger.info(f" Health Factor: {snap.hf:.3f}")
|
|
@@ -3410,7 +3414,7 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3410
3414
|
err: Exception | None = None
|
|
3411
3415
|
|
|
3412
3416
|
try:
|
|
3413
|
-
status = await self._withdraw_impl(
|
|
3417
|
+
status = await self._withdraw_impl()
|
|
3414
3418
|
except Exception as exc:
|
|
3415
3419
|
err = exc
|
|
3416
3420
|
if isinstance(exc, SwapOutcomeUnknownError):
|
|
@@ -3428,8 +3432,7 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3428
3432
|
)
|
|
3429
3433
|
return (False, f"{status[1]} | {suffix}")
|
|
3430
3434
|
|
|
3431
|
-
async def _withdraw_impl(self
|
|
3432
|
-
# NOTE: amount is currently unused; withdraw() is all-or-nothing in this strategy.
|
|
3435
|
+
async def _withdraw_impl(self) -> StatusTuple:
|
|
3433
3436
|
logger.info("=" * 60)
|
|
3434
3437
|
logger.info("WITHDRAW START - Full position unwind")
|
|
3435
3438
|
logger.info("=" * 60)
|
|
@@ -3537,7 +3540,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3537
3540
|
|
|
3538
3541
|
transferred_items = []
|
|
3539
3542
|
|
|
3540
|
-
# Transfer USDC to main wallet
|
|
3541
3543
|
usdc_balance = await self._get_balance_raw(
|
|
3542
3544
|
token_id=USDC_TOKEN_ID, wallet_address=self._get_strategy_wallet_address()
|
|
3543
3545
|
)
|
|
@@ -3559,7 +3561,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3559
3561
|
transferred_items.append(f"{usdc_amount:.2f} USDC")
|
|
3560
3562
|
logger.info(f"USDC transfer successful: {usdc_amount:.2f} USDC")
|
|
3561
3563
|
|
|
3562
|
-
# Transfer ETH (minus small reserve for tx fees) to main wallet
|
|
3563
3564
|
gas_balance = await self._get_balance_raw(
|
|
3564
3565
|
token_id=ETH_TOKEN_ID, wallet_address=self._get_strategy_wallet_address()
|
|
3565
3566
|
)
|
|
@@ -3588,7 +3589,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3588
3589
|
async def _unlend_remaining_positions(self) -> tuple[bool, str]:
|
|
3589
3590
|
logger.info("UNLEND: Redeeming remaining Moonwell positions...")
|
|
3590
3591
|
|
|
3591
|
-
# Unlend remaining wstETH
|
|
3592
3592
|
wsteth_pos = await self.moonwell_adapter.get_pos(mtoken=M_WSTETH)
|
|
3593
3593
|
if wsteth_pos[0]:
|
|
3594
3594
|
mtoken_bal = wsteth_pos[1].get("mtoken_balance", 0)
|
|
@@ -3600,7 +3600,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3600
3600
|
)
|
|
3601
3601
|
if not ok:
|
|
3602
3602
|
return (False, f"Failed to unlend wstETH: {msg}")
|
|
3603
|
-
# Swap to USDC with retries
|
|
3604
3603
|
wsteth_bal = await self._get_balance_raw(
|
|
3605
3604
|
token_id=WSTETH_TOKEN_ID,
|
|
3606
3605
|
wallet_address=self._get_strategy_wallet_address(),
|
|
@@ -3614,7 +3613,6 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3614
3613
|
if swap_result is None:
|
|
3615
3614
|
return (False, "Failed to swap wstETH to USDC after retries")
|
|
3616
3615
|
|
|
3617
|
-
# Unlend remaining USDC
|
|
3618
3616
|
usdc_pos = await self.moonwell_adapter.get_pos(mtoken=M_USDC)
|
|
3619
3617
|
if usdc_pos[0]:
|
|
3620
3618
|
mtoken_bal = usdc_pos[1].get("mtoken_balance", 0)
|
|
@@ -3627,10 +3625,8 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3627
3625
|
if not ok:
|
|
3628
3626
|
return (False, f"Failed to unlend USDC: {msg}")
|
|
3629
3627
|
|
|
3630
|
-
# Claim any remaining rewards
|
|
3631
3628
|
await self.moonwell_adapter.claim_rewards(min_rewards_usd=0)
|
|
3632
3629
|
|
|
3633
|
-
# Sweep any remaining tokens to USDC
|
|
3634
3630
|
ok, msg = await self._sweep_token_balances(
|
|
3635
3631
|
target_token_id=USDC_TOKEN_ID,
|
|
3636
3632
|
exclude={ETH_TOKEN_ID, WELL_TOKEN_ID},
|
|
@@ -3661,6 +3657,27 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3661
3657
|
totals_usd = dict(snap.totals_usd)
|
|
3662
3658
|
|
|
3663
3659
|
ltv = float(snap.ltv)
|
|
3660
|
+
|
|
3661
|
+
wallet_balances: dict[str, dict[str, float]] = {}
|
|
3662
|
+
wallet_tokens = {
|
|
3663
|
+
"WETH": ("wallet_weth", TOKEN_ID_WETH_BASE),
|
|
3664
|
+
"USDC": ("wallet_usdc", TOKEN_ID_USDC_BASE),
|
|
3665
|
+
"wstETH": ("wallet_wsteth", TOKEN_ID_WSTETH_BASE),
|
|
3666
|
+
}
|
|
3667
|
+
# Assume snapshot exposes token prices and decimals keyed by token id
|
|
3668
|
+
prices_usd = getattr(snap, "prices_usd", {}) or {}
|
|
3669
|
+
token_decimals = getattr(snap, "token_decimals", {}) or {}
|
|
3670
|
+
for symbol, (attr_name, token_id) in wallet_tokens.items():
|
|
3671
|
+
raw_balance = getattr(snap, attr_name, 0) or 0
|
|
3672
|
+
if raw_balance:
|
|
3673
|
+
decimals = token_decimals.get(token_id, 18)
|
|
3674
|
+
price = prices_usd.get(token_id, 0.0) or 0.0
|
|
3675
|
+
tokens = float(raw_balance) / float(10**decimals)
|
|
3676
|
+
usd_value = tokens * float(price)
|
|
3677
|
+
wallet_balances[symbol] = {
|
|
3678
|
+
"tokens": tokens,
|
|
3679
|
+
"usd": usd_value,
|
|
3680
|
+
}
|
|
3664
3681
|
hf = (1 / ltv) if ltv and ltv > 0 and not (ltv != ltv) else None
|
|
3665
3682
|
|
|
3666
3683
|
gas_balance = await self._get_gas_balance()
|
|
@@ -3678,12 +3695,21 @@ class MoonwellWstethLoopStrategy(Strategy):
|
|
|
3678
3695
|
|
|
3679
3696
|
peg_diff = await self.get_peg_diff()
|
|
3680
3697
|
|
|
3681
|
-
|
|
3698
|
+
# Calculate displayed portfolio value.
|
|
3699
|
+
# Note: snap.net_equity_usd represents net equity (wallet + supplies - debt),
|
|
3700
|
+
# so wallet_value is added here only for this aggregate display metric.
|
|
3701
|
+
net_equity_value = float(snap.net_equity_usd)
|
|
3702
|
+
wallet_value = sum(wb["usd"] for wb in wallet_balances.values())
|
|
3703
|
+
portfolio_value = net_equity_value + wallet_value
|
|
3682
3704
|
|
|
3683
|
-
quote = await self.
|
|
3705
|
+
quote = await self._quote()
|
|
3684
3706
|
|
|
3685
3707
|
strategy_status = {
|
|
3686
3708
|
"current_positions_usd_value": totals_usd,
|
|
3709
|
+
"wallet_balances": {
|
|
3710
|
+
k: v for k, v in wallet_balances.items() if v["tokens"] > 0
|
|
3711
|
+
},
|
|
3712
|
+
"wallet_balances_total_usd": wallet_value,
|
|
3687
3713
|
"credit_remaining": f"{credit_remaining * 100:.2f}%",
|
|
3688
3714
|
"LTV": ltv,
|
|
3689
3715
|
"health_factor": hf,
|