wayfinder-paths 0.1.28__py3-none-any.whl → 0.1.29__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of wayfinder-paths might be problematic. Click here for more details.

Files changed (28) hide show
  1. wayfinder_paths/adapters/boros_adapter/adapter.py +142 -12
  2. wayfinder_paths/adapters/boros_adapter/client.py +7 -5
  3. wayfinder_paths/adapters/boros_adapter/test_adapter.py +147 -18
  4. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +2 -12
  5. wayfinder_paths/adapters/multicall_adapter/adapter.py +2 -4
  6. wayfinder_paths/core/clients/TokenClient.py +1 -1
  7. wayfinder_paths/core/constants/__init__.py +23 -1
  8. wayfinder_paths/core/constants/contracts.py +22 -0
  9. wayfinder_paths/core/constants/hyperliquid.py +20 -3
  10. wayfinder_paths/core/engine/manifest.py +1 -1
  11. wayfinder_paths/mcp/scripting.py +2 -2
  12. wayfinder_paths/mcp/tools/discovery.py +3 -72
  13. wayfinder_paths/mcp/tools/execute.py +8 -4
  14. wayfinder_paths/mcp/tools/hyperliquid.py +1 -1
  15. wayfinder_paths/mcp/tools/quotes.py +7 -8
  16. wayfinder_paths/mcp/tools/wallets.py +4 -7
  17. wayfinder_paths/mcp/utils.py +0 -22
  18. wayfinder_paths/policies/lifi.py +5 -2
  19. wayfinder_paths/policies/moonwell.py +3 -1
  20. wayfinder_paths/policies/util.py +4 -2
  21. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +1 -2
  22. wayfinder_paths/strategies/boros_hype_strategy/constants.py +23 -16
  23. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +24 -63
  24. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +2 -1
  25. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.29.dist-info}/METADATA +1 -1
  26. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.29.dist-info}/RECORD +28 -28
  27. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.29.dist-info}/LICENSE +0 -0
  28. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.29.dist-info}/WHEEL +0 -0
@@ -5,12 +5,11 @@ import time
5
5
  from collections.abc import Callable
6
6
  from typing import Any
7
7
 
8
- from eth_abi import encode
8
+ from eth_abi import decode, encode
9
9
  from eth_utils import function_signature_to_4byte_selector, to_checksum_address
10
10
  from loguru import logger
11
11
 
12
12
  from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
13
- from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
14
13
  from wayfinder_paths.core.constants.contracts import BOROS_MARKET_HUB, BOROS_ROUTER
15
14
  from wayfinder_paths.core.utils.tokens import build_approve_transaction
16
15
  from wayfinder_paths.core.utils.transaction import send_transaction
@@ -115,6 +114,121 @@ class BorosAdapter(BaseAdapter):
115
114
  }
116
115
  ]
117
116
 
117
+ async def get_cash_fee_data(self, *, token_id: int) -> tuple[bool, dict[str, Any]]:
118
+ """Read MarketHub.getCashFeeData(tokenId) from chain.
119
+
120
+ This is useful for guarding Boros actions that require a minimum amount
121
+ of cross cash (e.g., MMInsufficientMinCash()).
122
+ """
123
+ try:
124
+ selector = function_signature_to_4byte_selector("getCashFeeData(uint16)")
125
+ params = encode(["uint16"], [int(token_id)])
126
+ data = "0x" + selector.hex() + params.hex()
127
+
128
+ async with web3_from_chain_id(self.chain_id) as web3:
129
+ raw: bytes = await web3.eth.call(
130
+ {
131
+ "to": to_checksum_address(BOROS_MARKET_HUB),
132
+ "data": data,
133
+ }
134
+ )
135
+
136
+ if len(raw) % 32 != 0:
137
+ return False, {"error": f"Unexpected getCashFeeData() size: {len(raw)}"}
138
+
139
+ n_words = len(raw) // 32
140
+ values = decode(["uint256"] * n_words, raw)
141
+ if len(values) < 4:
142
+ return False, {
143
+ "error": f"Unexpected getCashFeeData() words: {len(values)}"
144
+ }
145
+
146
+ # Empirically (2026-02-01 on Arbitrum MarketHub), the return is 4 uint256s.
147
+ # We expose all 4, and provide float conversions for the commonly used ones.
148
+ scaling_factor_wei = int(values[0])
149
+ fee_rate_wei = int(values[1])
150
+ min_cash_cross_wei = int(values[2])
151
+ min_cash_isolated_wei = int(values[3])
152
+
153
+ return True, {
154
+ "token_id": int(token_id),
155
+ "scaling_factor_wei": scaling_factor_wei,
156
+ "fee_rate_wei": fee_rate_wei,
157
+ "min_cash_cross_wei": min_cash_cross_wei,
158
+ "min_cash_isolated_wei": min_cash_isolated_wei,
159
+ "fee_rate": fee_rate_wei / 1e18,
160
+ "min_cash_cross": min_cash_cross_wei / 1e18,
161
+ "min_cash_isolated": min_cash_isolated_wei / 1e18,
162
+ }
163
+ except Exception as exc: # noqa: BLE001
164
+ return False, {"error": str(exc)}
165
+
166
+ async def sweep_isolated_to_cross(
167
+ self,
168
+ *,
169
+ token_id: int,
170
+ market_id: int | None = None,
171
+ token_decimals: int = 18,
172
+ ) -> tuple[bool, dict[str, Any]]:
173
+ """Sweep isolated cash -> cross cash for a given token (optionally per-market).
174
+
175
+ Boros deposits can sometimes show up as isolated cash for the target market;
176
+ this helper moves that isolated cash back to cross margin using cash_transfer.
177
+
178
+ Notes:
179
+ - cash_transfer uses 1e18 internal cash units, not token native decimals.
180
+ - This does NOT touch isolated positions for other markets unless market_id is None.
181
+ """
182
+ if self.simulation:
183
+ return True, {"status": "simulated"}
184
+
185
+ ok_state, state = await self.get_full_user_state(
186
+ token_id=int(token_id),
187
+ token_decimals=int(token_decimals),
188
+ include_open_orders=False,
189
+ include_withdrawal_status=False,
190
+ )
191
+ if not ok_state or not isinstance(state, dict):
192
+ return False, {"error": f"Failed to read Boros state: {state}"}
193
+
194
+ balances = state.get("balances") or {}
195
+ isolated_positions = balances.get("isolated_positions") or []
196
+ if not isinstance(isolated_positions, list):
197
+ isolated_positions = []
198
+
199
+ moved: list[dict[str, Any]] = []
200
+ for iso in isolated_positions:
201
+ try:
202
+ iso_market_id = int(iso.get("market_id"))
203
+ if market_id is not None and iso_market_id != int(market_id):
204
+ continue
205
+ balance_wei = int(iso.get("balance_wei") or 0)
206
+ if balance_wei <= 0:
207
+ continue
208
+
209
+ tx_ok, tx_res = await self.cash_transfer(
210
+ market_id=iso_market_id,
211
+ amount_wei=balance_wei,
212
+ is_deposit=False, # isolated -> cross
213
+ )
214
+ moved.append(
215
+ {
216
+ "market_id": iso_market_id,
217
+ "balance_wei": balance_wei,
218
+ "ok": tx_ok,
219
+ "tx": tx_res,
220
+ }
221
+ )
222
+ if not tx_ok:
223
+ return False, {
224
+ "error": f"Failed sweep isolated->cross for market {iso_market_id}",
225
+ "moved": moved,
226
+ }
227
+ except Exception as exc: # noqa: BLE001
228
+ return False, {"error": f"Failed sweep isolated->cross: {exc}"}
229
+
230
+ return True, {"status": "ok", "moved": moved}
231
+
118
232
  @staticmethod
119
233
  def _unwrap_tx_payload(payload: dict[str, Any]) -> dict[str, Any]:
120
234
  """Best-effort unwrap of API payloads that may nest the tx dict."""
@@ -195,7 +309,6 @@ class BorosAdapter(BaseAdapter):
195
309
  self,
196
310
  calldata: dict[str, Any],
197
311
  *,
198
- timeout: int = DEFAULT_TRANSACTION_TIMEOUT,
199
312
  max_retries: int = 2,
200
313
  ) -> tuple[bool, dict[str, Any]]:
201
314
  """Broadcast calldata from Boros API with retry logic.
@@ -292,14 +405,6 @@ class BorosAdapter(BaseAdapter):
292
405
  "attempts": max_retries + 1,
293
406
  }
294
407
 
295
- async def connect(self) -> bool:
296
- try:
297
- markets = await self.boros_client.list_markets(limit=1)
298
- return len(markets) > 0
299
- except Exception as exc:
300
- logger.error(f"BorosAdapter connection failed: {exc}")
301
- return False
302
-
303
408
  # ------------------------------------------------------------------ #
304
409
  # Tick Math Utilities #
305
410
  # ------------------------------------------------------------------ #
@@ -1015,6 +1120,14 @@ class BorosAdapter(BaseAdapter):
1015
1120
  token_id: int,
1016
1121
  market_id: int,
1017
1122
  ) -> tuple[bool, dict[str, Any]]:
1123
+ """Deposit collateral into Boros cross margin.
1124
+
1125
+ IMPORTANT: amount_wei is in the collateral token's native decimals.
1126
+ Example: USDT has 6 decimals, so 1 USDT = 1_000_000.
1127
+
1128
+ After deposit, Boros may credit the cash as isolated for market_id. This
1129
+ helper sweeps isolated -> cross for that market to match the method name.
1130
+ """
1018
1131
  if self.simulation:
1019
1132
  logger.info(
1020
1133
  f"[SIMULATION] deposit_to_cross_margin: {amount_wei} wei, "
@@ -1074,7 +1187,24 @@ class BorosAdapter(BaseAdapter):
1074
1187
  "tx": tx_res,
1075
1188
  }
1076
1189
 
1077
- return True, {"status": "ok", "approve": approve_res, "tx": tx_res}
1190
+ sweep_ok, sweep_res = await self.sweep_isolated_to_cross(
1191
+ token_id=int(token_id),
1192
+ market_id=int(market_id),
1193
+ )
1194
+ if not sweep_ok:
1195
+ return False, {
1196
+ "error": f"Deposit succeeded but isolated->cross sweep failed: {sweep_res}",
1197
+ "approve": approve_res,
1198
+ "tx": tx_res,
1199
+ "sweep": sweep_res,
1200
+ }
1201
+
1202
+ return True, {
1203
+ "status": "ok",
1204
+ "approve": approve_res,
1205
+ "tx": tx_res,
1206
+ "sweep": sweep_res,
1207
+ }
1078
1208
  except Exception as e:
1079
1209
  logger.error(f"Failed to deposit to cross margin: {e}")
1080
1210
  return False, {"error": str(e)}
@@ -250,10 +250,11 @@ class BorosClient:
250
250
 
251
251
  Args:
252
252
  token_id: Boros token ID (e.g., 3 for USDT).
253
- amount_wei: Amount in scaled wei (1e18).
254
- market_id: Target market ID.
255
- user_address: User wallet address.
256
- account_id: Boros account ID (0 = cross margin).
253
+ amount_wei: Amount in NATIVE token decimals (despite the param name).
254
+ Example: USDT has 6 decimals, so 1 USDT = 1_000_000.
255
+ market_id: Target market ID.
256
+ user_address: User wallet address.
257
+ account_id: Boros account ID (0 = cross margin).
257
258
 
258
259
  Returns:
259
260
  Calldata dictionary with 'to', 'data', 'value' fields.
@@ -283,7 +284,8 @@ class BorosClient:
283
284
 
284
285
  Args:
285
286
  token_id: Boros token ID.
286
- amount_wei: Amount in scaled wei.
287
+ amount_wei: Amount in NATIVE token decimals (despite the param name).
288
+ Example: USDT has 6 decimals, so 1 USDT = 1_000_000.
287
289
  user_address: User wallet address.
288
290
  account_id: Account ID.
289
291
 
@@ -3,6 +3,7 @@
3
3
  from unittest.mock import AsyncMock, patch
4
4
 
5
5
  import pytest
6
+ from eth_abi import encode as abi_encode
6
7
 
7
8
  from wayfinder_paths.adapters.boros_adapter.adapter import (
8
9
  BorosAdapter,
@@ -44,24 +45,6 @@ class TestBorosAdapter:
44
45
  """Test adapter has correct type."""
45
46
  assert adapter.adapter_type == "BOROS"
46
47
 
47
- @pytest.mark.asyncio
48
- async def test_connect_success(self, adapter, mock_boros_client):
49
- """Test successful connection."""
50
- mock_boros_client.list_markets = AsyncMock(
51
- return_value=[{"marketId": 1, "symbol": "HYPE-USD"}]
52
- )
53
-
54
- result = await adapter.connect()
55
- assert result is True
56
-
57
- @pytest.mark.asyncio
58
- async def test_connect_failure(self, adapter, mock_boros_client):
59
- """Test connection failure."""
60
- mock_boros_client.list_markets = AsyncMock(side_effect=Exception("API Error"))
61
-
62
- result = await adapter.connect()
63
- assert result is False
64
-
65
48
  @pytest.mark.asyncio
66
49
  async def test_list_markets_success(self, adapter, mock_boros_client):
67
50
  """Test successful market listing."""
@@ -358,6 +341,152 @@ class TestBorosAdapter:
358
341
  assert success is True
359
342
  assert result["status"] == "simulated"
360
343
 
344
+ @pytest.mark.asyncio
345
+ async def test_get_cash_fee_data_decodes_values(self, adapter):
346
+ """Test MarketHub.getCashFeeData decoding (on-chain read is mocked)."""
347
+ scaling_factor_wei = 123
348
+ fee_rate_wei = 5_000_000_000_000_000 # 0.005e18
349
+ min_cash_cross_wei = 400_000_000_000_000_000 # 0.4e18
350
+ min_cash_isolated_wei = 1_000_000_000_000_000_000 # 1.0e18
351
+ raw = abi_encode(
352
+ ["uint256", "uint256", "uint256", "uint256"],
353
+ [
354
+ scaling_factor_wei,
355
+ fee_rate_wei,
356
+ min_cash_cross_wei,
357
+ min_cash_isolated_wei,
358
+ ],
359
+ )
360
+
361
+ mock_web3 = AsyncMock()
362
+ mock_web3.eth.call = AsyncMock(return_value=raw)
363
+ mock_cm = AsyncMock()
364
+ mock_cm.__aenter__.return_value = mock_web3
365
+ mock_cm.__aexit__.return_value = False
366
+
367
+ with patch(
368
+ "wayfinder_paths.adapters.boros_adapter.adapter.web3_from_chain_id",
369
+ return_value=mock_cm,
370
+ ):
371
+ ok, data = await adapter.get_cash_fee_data(token_id=5)
372
+
373
+ assert ok is True
374
+ assert data["token_id"] == 5
375
+ assert data["scaling_factor_wei"] == scaling_factor_wei
376
+ assert data["fee_rate_wei"] == fee_rate_wei
377
+ assert data["min_cash_cross_wei"] == min_cash_cross_wei
378
+ assert data["min_cash_isolated_wei"] == min_cash_isolated_wei
379
+ assert data["fee_rate"] == pytest.approx(fee_rate_wei / 1e18)
380
+ assert data["min_cash_cross"] == pytest.approx(0.4)
381
+ assert data["min_cash_isolated"] == pytest.approx(1.0)
382
+
383
+ @pytest.mark.asyncio
384
+ async def test_sweep_isolated_to_cross_filters_by_market(self, adapter):
385
+ """Test isolated -> cross sweep only affects the specified market."""
386
+ adapter.simulation = False
387
+ adapter.get_full_user_state = AsyncMock(
388
+ return_value=(
389
+ True,
390
+ {
391
+ "balances": {
392
+ "isolated_positions": [
393
+ {"market_id": 18, "balance_wei": 111},
394
+ {"market_id": 19, "balance_wei": 222},
395
+ ]
396
+ }
397
+ },
398
+ )
399
+ )
400
+ adapter.cash_transfer = AsyncMock(return_value=(True, {"status": "ok"}))
401
+
402
+ ok, res = await adapter.sweep_isolated_to_cross(token_id=3, market_id=19)
403
+ assert ok is True
404
+ assert res["status"] == "ok"
405
+ assert len(res["moved"]) == 1
406
+ assert res["moved"][0]["market_id"] == 19
407
+ assert res["moved"][0]["balance_wei"] == 222
408
+
409
+ adapter.cash_transfer.assert_awaited_once_with(
410
+ market_id=19, amount_wei=222, is_deposit=False
411
+ )
412
+
413
+ @pytest.mark.asyncio
414
+ async def test_sweep_isolated_to_cross_errors_on_failed_transfer(self, adapter):
415
+ """Test sweep fails fast when an isolated->cross transfer fails."""
416
+ adapter.simulation = False
417
+ adapter.get_full_user_state = AsyncMock(
418
+ return_value=(
419
+ True,
420
+ {
421
+ "balances": {
422
+ "isolated_positions": [
423
+ {"market_id": 18, "balance_wei": 111},
424
+ ]
425
+ }
426
+ },
427
+ )
428
+ )
429
+ adapter.cash_transfer = AsyncMock(return_value=(False, {"error": "nope"}))
430
+
431
+ ok, res = await adapter.sweep_isolated_to_cross(token_id=3, market_id=18)
432
+ assert ok is False
433
+ assert "Failed sweep isolated->cross" in res["error"]
434
+ assert res["moved"][0]["market_id"] == 18
435
+ assert res["moved"][0]["ok"] is False
436
+
437
+ @pytest.mark.asyncio
438
+ async def test_deposit_to_cross_margin_sweeps_after_deposit(
439
+ self, adapter, mock_boros_client
440
+ ):
441
+ """Test deposit triggers isolated->cross sweep on success (non-simulation path is mocked)."""
442
+ adapter.simulation = False
443
+ adapter.sign_callback = object()
444
+
445
+ mock_boros_client.build_deposit_calldata = AsyncMock(
446
+ return_value={
447
+ "to": "0x0000000000000000000000000000000000000002",
448
+ "data": "0xdeadbeef",
449
+ "value": 0,
450
+ }
451
+ )
452
+
453
+ with (
454
+ patch(
455
+ "wayfinder_paths.adapters.boros_adapter.adapter.build_approve_transaction",
456
+ new=AsyncMock(
457
+ return_value={"to": "0x0", "data": "0x0", "chainId": 42161}
458
+ ),
459
+ ),
460
+ patch(
461
+ "wayfinder_paths.adapters.boros_adapter.adapter.send_transaction",
462
+ new=AsyncMock(return_value="0xapprove"),
463
+ ),
464
+ patch.object(
465
+ adapter,
466
+ "_broadcast_calldata",
467
+ new=AsyncMock(return_value=(True, {"tx_hash": "0xdeposit"})),
468
+ ),
469
+ patch.object(
470
+ adapter,
471
+ "sweep_isolated_to_cross",
472
+ new=AsyncMock(return_value=(True, {"status": "ok", "moved": []})),
473
+ ) as mock_sweep,
474
+ ):
475
+ ok, res = await adapter.deposit_to_cross_margin(
476
+ collateral_address="0x0000000000000000000000000000000000000001",
477
+ amount_wei=1_000_000, # 1 USDT
478
+ token_id=3,
479
+ market_id=18,
480
+ )
481
+
482
+ assert ok is True
483
+ assert res["status"] == "ok"
484
+ assert res["approve"]["tx_hash"] == "0xapprove"
485
+ assert res["tx"]["tx_hash"] == "0xdeposit"
486
+ assert res["sweep"]["status"] == "ok"
487
+
488
+ mock_sweep.assert_awaited_once_with(token_id=3, market_id=18)
489
+
361
490
  @pytest.mark.asyncio
362
491
  async def test_withdraw_collateral_simulation(self, adapter):
363
492
  """Test withdraw in simulation mode."""
@@ -11,6 +11,7 @@ from loguru import logger
11
11
 
12
12
  from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
13
13
  from wayfinder_paths.core.constants import ZERO_ADDRESS
14
+ from wayfinder_paths.core.constants.contracts import HYPERCORE_SENTINEL_ADDRESS
14
15
  from wayfinder_paths.core.constants.hyperliquid import (
15
16
  ARBITRUM_USDC_ADDRESS as _ARBITRUM_USDC_ADDRESS,
16
17
  )
@@ -113,17 +114,6 @@ class HyperliquidAdapter(BaseAdapter):
113
114
  self._asset_to_sz_decimals: dict[int, int] | None = None
114
115
  self._coin_to_asset: dict[str, int] | None = None
115
116
 
116
- async def connect(self) -> bool:
117
- try:
118
- meta = self.info.meta_and_asset_ctxs()
119
- if meta:
120
- self.logger.debug("HyperliquidAdapter connected successfully")
121
- return True
122
- return False
123
- except Exception as exc:
124
- self.logger.error(f"HyperliquidAdapter connection failed: {exc}")
125
- return False
126
-
127
117
  # ------------------------------------------------------------------ #
128
118
  # Market Data - Read Operations #
129
119
  # ------------------------------------------------------------------ #
@@ -714,7 +704,7 @@ class HyperliquidAdapter(BaseAdapter):
714
704
  @staticmethod
715
705
  def hypercore_index_to_system_address(index: int) -> str:
716
706
  if index == 150:
717
- return "0x2222222222222222222222222222222222222222"
707
+ return HYPERCORE_SENTINEL_ADDRESS
718
708
 
719
709
  hex_index = f"{index:x}"
720
710
  padding_length = 42 - len("0x20") - len(hex_index)
@@ -7,9 +7,9 @@ from typing import Any
7
7
  from hexbytes import HexBytes
8
8
 
9
9
  from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
10
+ from wayfinder_paths.core.constants.contracts import MULTICALL3_ADDRESS
10
11
  from wayfinder_paths.core.constants.erc20_abi import ERC20_ABI
11
12
 
12
- MULTICALL3_DEFAULT_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11"
13
13
  MULTICALL3_ABI = [
14
14
  {
15
15
  "inputs": [
@@ -79,9 +79,7 @@ class MulticallAdapter(BaseAdapter):
79
79
  self.chain_id = int(chain_id) if chain_id is not None else None
80
80
  self.web3 = web3
81
81
 
82
- checksum_address = self.web3.to_checksum_address(
83
- address or MULTICALL3_DEFAULT_ADDRESS
84
- )
82
+ checksum_address = self.web3.to_checksum_address(address or MULTICALL3_ADDRESS)
85
83
  self.contract = self.web3.eth.contract(
86
84
  address=checksum_address, abi=abi or MULTICALL3_ABI
87
85
  )
@@ -90,7 +90,7 @@ class FuzzyTokenResult(TypedDict):
90
90
  class TokenClient(WayfinderClient):
91
91
  def __init__(self):
92
92
  super().__init__()
93
- self.api_base_url = f"{get_api_base_url()}/v1/blockchain/tokens"
93
+ self.api_base_url = f"{get_api_base_url()}/blockchain/tokens"
94
94
 
95
95
  async def get_token_details(
96
96
  self, query: str, market_data: bool = False, chain_id: int | None = None
@@ -33,6 +33,18 @@ from wayfinder_paths.core.constants.chains import (
33
33
  SUPPORTED_CHAINS,
34
34
  )
35
35
  from wayfinder_paths.core.constants.contracts import (
36
+ ENSO_ROUTER,
37
+ HYPE_FEE_WALLET,
38
+ HYPE_OFT_ADDRESS,
39
+ HYPERCORE_SENTINEL_ADDRESS,
40
+ HYPEREVM_WHYPE,
41
+ KHYPE_ADDRESS,
42
+ KHYPE_STAKING_ACCOUNTANT,
43
+ LHYPE_ACCOUNTANT,
44
+ LIFI_GENERIC,
45
+ LIFI_ROUTER_HYPEREVM,
46
+ LOOPED_HYPE_ADDRESS,
47
+ MULTICALL3_ADDRESS,
36
48
  NATIVE_TOKEN_SENTINEL,
37
49
  ZERO_ADDRESS,
38
50
  )
@@ -42,7 +54,6 @@ from .hyperliquid import (
42
54
  ARBITRUM_USDC_TOKEN_ID,
43
55
  DEFAULT_HYPERLIQUID_BUILDER_FEE,
44
56
  DEFAULT_HYPERLIQUID_BUILDER_FEE_TENTHS_BP,
45
- HYPE_FEE_WALLET,
46
57
  HYPERLIQUID_BRIDGE_ADDRESS,
47
58
  )
48
59
 
@@ -85,4 +96,15 @@ __all__ = [
85
96
  "HYPE_FEE_WALLET",
86
97
  "DEFAULT_HYPERLIQUID_BUILDER_FEE_TENTHS_BP",
87
98
  "DEFAULT_HYPERLIQUID_BUILDER_FEE",
99
+ "ENSO_ROUTER",
100
+ "HYPE_OFT_ADDRESS",
101
+ "HYPERCORE_SENTINEL_ADDRESS",
102
+ "HYPEREVM_WHYPE",
103
+ "KHYPE_ADDRESS",
104
+ "KHYPE_STAKING_ACCOUNTANT",
105
+ "LHYPE_ACCOUNTANT",
106
+ "LIFI_GENERIC",
107
+ "LIFI_ROUTER_HYPEREVM",
108
+ "LOOPED_HYPE_ADDRESS",
109
+ "MULTICALL3_ADDRESS",
88
110
  ]
@@ -45,6 +45,28 @@ USDT_ETHEREUM = to_checksum_address("0xdAC17F958D2ee523a2206206994597C13D831ec7"
45
45
  USDT_POLYGON = to_checksum_address("0xc2132D05D31c914a87C6611C10748AEb04B58e8F")
46
46
  USDT_BSC = to_checksum_address("0x55d398326f99059fF775485246999027B3197955")
47
47
 
48
+ HYPE_FEE_WALLET = to_checksum_address("0xaA1D89f333857eD78F8434CC4f896A9293EFE65c")
49
+
50
+ # HyperEVM token addresses
51
+ KHYPE_ADDRESS = to_checksum_address("0xfD739d4e423301CE9385c1fb8850539D657C296D")
52
+ LOOPED_HYPE_ADDRESS = to_checksum_address("0x5748ae796AE46A4F1348a1693de4b50560485562")
53
+
54
+ # Accountant contracts for exchange rate calculations
55
+ KHYPE_STAKING_ACCOUNTANT = to_checksum_address(
56
+ "0x9209648Ec9D448EF57116B73A2f081835643dc7A"
57
+ )
58
+ LHYPE_ACCOUNTANT = to_checksum_address("0xcE621a3CA6F72706678cFF0572ae8d15e5F001c3")
59
+
60
+ # LayerZero OFT bridge (HyperEVM native HYPE -> Arbitrum OFT HYPE)
61
+ HYPE_OFT_ADDRESS = to_checksum_address("0x007C26Ed5C33Fe6fEF62223d4c363A01F1b1dDc1")
62
+
63
+ # Multicall3 (deployed at same address on all EVM chains)
64
+ MULTICALL3_ADDRESS = to_checksum_address("0xcA11bde05977b3631167028862bE2a173976CA11")
65
+
66
+ # LI.FI routers
67
+ LIFI_ROUTER_HYPEREVM = to_checksum_address("0x0a0758d937d1059c356D4714e57F5df0239bce1A")
68
+ LIFI_GENERIC = to_checksum_address("0x31a9b1835864706Af10103b31Ea2b79bdb995F5F")
69
+
48
70
  TOKENS_REQUIRING_APPROVAL_RESET: set[tuple[int, str]] = {
49
71
  (1, USDT_ETHEREUM),
50
72
  (137, USDT_POLYGON),
@@ -2,10 +2,27 @@ from __future__ import annotations
2
2
 
3
3
  from typing import Any
4
4
 
5
- HYPERLIQUID_BRIDGE_ADDRESS: str = "0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7"
6
- ARBITRUM_USDC_ADDRESS: str = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
5
+ from wayfinder_paths.core.constants.contracts import (
6
+ ARBITRUM_USDC as ARBITRUM_USDC_ADDRESS,
7
+ )
8
+ from wayfinder_paths.core.constants.contracts import (
9
+ HYPE_FEE_WALLET,
10
+ )
11
+ from wayfinder_paths.core.constants.contracts import (
12
+ HYPERLIQUID_BRIDGE as HYPERLIQUID_BRIDGE_ADDRESS,
13
+ )
14
+
15
+ # Re-export addresses for backwards compatibility
16
+ __all__ = [
17
+ "ARBITRUM_USDC_ADDRESS",
18
+ "ARBITRUM_USDC_TOKEN_ID",
19
+ "HYPE_FEE_WALLET",
20
+ "HYPERLIQUID_BRIDGE_ADDRESS",
21
+ "DEFAULT_HYPERLIQUID_BUILDER_FEE_TENTHS_BP",
22
+ "DEFAULT_HYPERLIQUID_BUILDER_FEE",
23
+ ]
24
+
7
25
  ARBITRUM_USDC_TOKEN_ID: str = "usd-coin-arbitrum"
8
- HYPE_FEE_WALLET: str = "0xaA1D89f333857eD78F8434CC4f896A9293EFE65c"
9
26
 
10
27
  # Tenths of a basis point: 30 -> 0.030% (3 bps)
11
28
  DEFAULT_HYPERLIQUID_BUILDER_FEE_TENTHS_BP: int = 30
@@ -19,7 +19,7 @@ class StrategyManifest(BaseModel):
19
19
  )
20
20
  name: str | None = Field(
21
21
  default=None,
22
- description="Unique name identifier for this strategy instance. Used to look up dedicated wallet in wallets.json by label.",
22
+ description="Unique name identifier for this strategy instance. Used to look up dedicated wallet in config.json by label.",
23
23
  )
24
24
  permissions: dict[str, Any] = Field(default_factory=dict)
25
25
  adapters: list[AdapterRequirement] = Field(default_factory=list)
@@ -57,8 +57,8 @@ def get_adapter[T](
57
57
  wallet = find_wallet_by_label(wallet_label)
58
58
  if not wallet:
59
59
  raise ValueError(
60
- f"Wallet '{wallet_label}' not found in wallets.json. "
61
- "Run 'just create-wallets' or check WALLETS_PATH."
60
+ f"Wallet '{wallet_label}' not found in config.json. "
61
+ "Run 'just create-wallets'."
62
62
  )
63
63
 
64
64
  private_key = wallet.get("private_key") or wallet.get("private_key_hex")