wayfinder-paths 0.1.28__py3-none-any.whl → 0.1.30__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 (36) hide show
  1. wayfinder_paths/adapters/boros_adapter/adapter.py +445 -14
  2. wayfinder_paths/adapters/boros_adapter/client.py +7 -5
  3. wayfinder_paths/adapters/boros_adapter/test_adapter.py +259 -19
  4. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +4 -14
  5. wayfinder_paths/adapters/hyperliquid_adapter/exchange.py +2 -2
  6. wayfinder_paths/adapters/hyperliquid_adapter/local_signer.py +2 -33
  7. wayfinder_paths/adapters/multicall_adapter/adapter.py +2 -4
  8. wayfinder_paths/core/clients/TokenClient.py +1 -1
  9. wayfinder_paths/core/constants/__init__.py +23 -1
  10. wayfinder_paths/core/constants/contracts.py +22 -0
  11. wayfinder_paths/core/constants/hype_oft_abi.py +151 -0
  12. wayfinder_paths/core/constants/hyperliquid.py +20 -3
  13. wayfinder_paths/core/engine/manifest.py +1 -1
  14. wayfinder_paths/core/strategies/Strategy.py +1 -2
  15. wayfinder_paths/mcp/scripting.py +2 -2
  16. wayfinder_paths/mcp/tools/discovery.py +3 -72
  17. wayfinder_paths/mcp/tools/execute.py +8 -4
  18. wayfinder_paths/mcp/tools/hyperliquid.py +1 -1
  19. wayfinder_paths/mcp/tools/quotes.py +11 -133
  20. wayfinder_paths/mcp/tools/wallets.py +4 -7
  21. wayfinder_paths/mcp/utils.py +0 -22
  22. wayfinder_paths/policies/lifi.py +5 -2
  23. wayfinder_paths/policies/moonwell.py +3 -1
  24. wayfinder_paths/policies/util.py +4 -2
  25. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +2 -4
  26. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +57 -201
  27. wayfinder_paths/strategies/boros_hype_strategy/constants.py +24 -168
  28. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +24 -63
  29. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +2 -0
  30. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2 -0
  31. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +4 -1
  32. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.30.dist-info}/METADATA +1 -1
  33. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.30.dist-info}/RECORD +35 -35
  34. wayfinder_paths/core/types.py +0 -19
  35. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.30.dist-info}/LICENSE +0 -0
  36. {wayfinder_paths-0.1.28.dist-info → wayfinder_paths-0.1.30.dist-info}/WHEEL +0 -0
@@ -1,8 +1,10 @@
1
1
  """Tests for BorosAdapter."""
2
2
 
3
- from unittest.mock import AsyncMock, patch
3
+ from types import SimpleNamespace
4
+ from unittest.mock import AsyncMock, MagicMock, patch
4
5
 
5
6
  import pytest
7
+ from eth_abi import encode as abi_encode
6
8
 
7
9
  from wayfinder_paths.adapters.boros_adapter.adapter import (
8
10
  BorosAdapter,
@@ -44,24 +46,6 @@ class TestBorosAdapter:
44
46
  """Test adapter has correct type."""
45
47
  assert adapter.adapter_type == "BOROS"
46
48
 
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
49
  @pytest.mark.asyncio
66
50
  async def test_list_markets_success(self, adapter, mock_boros_client):
67
51
  """Test successful market listing."""
@@ -358,6 +342,262 @@ class TestBorosAdapter:
358
342
  assert success is True
359
343
  assert result["status"] == "simulated"
360
344
 
345
+ @pytest.mark.asyncio
346
+ async def test_get_cash_fee_data_decodes_values(self, adapter):
347
+ """Test MarketHub.getCashFeeData decoding (on-chain read is mocked)."""
348
+ scaling_factor_wei = 123
349
+ fee_rate_wei = 5_000_000_000_000_000 # 0.005e18
350
+ min_cash_cross_wei = 400_000_000_000_000_000 # 0.4e18
351
+ min_cash_isolated_wei = 1_000_000_000_000_000_000 # 1.0e18
352
+ raw = abi_encode(
353
+ ["uint256", "uint256", "uint256", "uint256"],
354
+ [
355
+ scaling_factor_wei,
356
+ fee_rate_wei,
357
+ min_cash_cross_wei,
358
+ min_cash_isolated_wei,
359
+ ],
360
+ )
361
+
362
+ mock_web3 = AsyncMock()
363
+ mock_web3.eth.call = AsyncMock(return_value=raw)
364
+ mock_cm = AsyncMock()
365
+ mock_cm.__aenter__.return_value = mock_web3
366
+ mock_cm.__aexit__.return_value = False
367
+
368
+ with patch(
369
+ "wayfinder_paths.adapters.boros_adapter.adapter.web3_from_chain_id",
370
+ return_value=mock_cm,
371
+ ):
372
+ ok, data = await adapter.get_cash_fee_data(token_id=5)
373
+
374
+ assert ok is True
375
+ assert data["token_id"] == 5
376
+ assert data["scaling_factor_wei"] == scaling_factor_wei
377
+ assert data["fee_rate_wei"] == fee_rate_wei
378
+ assert data["min_cash_cross_wei"] == min_cash_cross_wei
379
+ assert data["min_cash_isolated_wei"] == min_cash_isolated_wei
380
+ assert data["fee_rate"] == pytest.approx(fee_rate_wei / 1e18)
381
+ assert data["min_cash_cross"] == pytest.approx(0.4)
382
+ assert data["min_cash_isolated"] == pytest.approx(1.0)
383
+
384
+ @pytest.mark.asyncio
385
+ async def test_sweep_isolated_to_cross_filters_by_market(self, adapter):
386
+ """Test isolated -> cross sweep only affects the specified market."""
387
+ adapter.simulation = False
388
+ adapter.get_account_balances = AsyncMock(
389
+ return_value=(
390
+ True,
391
+ {
392
+ "isolated_positions": [
393
+ {"market_id": 18, "balance_wei": 111},
394
+ {"market_id": 19, "balance_wei": 222},
395
+ ]
396
+ },
397
+ )
398
+ )
399
+ adapter.cash_transfer = AsyncMock(return_value=(True, {"status": "ok"}))
400
+
401
+ ok, res = await adapter.sweep_isolated_to_cross(token_id=3, market_id=19)
402
+ assert ok is True
403
+ assert res["status"] == "ok"
404
+ assert len(res["moved"]) == 1
405
+ assert res["moved"][0]["market_id"] == 19
406
+ assert res["moved"][0]["balance_wei"] == 222
407
+
408
+ adapter.cash_transfer.assert_awaited_once_with(
409
+ market_id=19, amount_wei=222, is_deposit=False
410
+ )
411
+
412
+ @pytest.mark.asyncio
413
+ async def test_sweep_isolated_to_cross_errors_on_failed_transfer(self, adapter):
414
+ """Test sweep fails fast when an isolated->cross transfer fails."""
415
+ adapter.simulation = False
416
+ adapter.get_account_balances = AsyncMock(
417
+ return_value=(
418
+ True,
419
+ {
420
+ "isolated_positions": [
421
+ {"market_id": 18, "balance_wei": 111},
422
+ ]
423
+ },
424
+ )
425
+ )
426
+ adapter.cash_transfer = AsyncMock(return_value=(False, {"error": "nope"}))
427
+
428
+ ok, res = await adapter.sweep_isolated_to_cross(token_id=3, market_id=18)
429
+ assert ok is False
430
+ assert "Failed sweep isolated->cross" in res["error"]
431
+ assert res["moved"][0]["market_id"] == 18
432
+ assert res["moved"][0]["ok"] is False
433
+
434
+ @pytest.mark.asyncio
435
+ async def test_deposit_to_cross_margin_sweeps_after_deposit(
436
+ self, adapter, mock_boros_client
437
+ ):
438
+ """Test deposit triggers isolated->cross sweep on success (non-simulation path is mocked)."""
439
+ adapter.simulation = False
440
+ adapter.sign_callback = object()
441
+
442
+ mock_boros_client.build_deposit_calldata = AsyncMock(
443
+ return_value={
444
+ "to": "0x0000000000000000000000000000000000000002",
445
+ "data": "0xdeadbeef",
446
+ "value": 0,
447
+ }
448
+ )
449
+
450
+ with (
451
+ patch(
452
+ "wayfinder_paths.adapters.boros_adapter.adapter.build_approve_transaction",
453
+ new=AsyncMock(
454
+ return_value={"to": "0x0", "data": "0x0", "chainId": 42161}
455
+ ),
456
+ ),
457
+ patch(
458
+ "wayfinder_paths.adapters.boros_adapter.adapter.send_transaction",
459
+ new=AsyncMock(return_value="0xapprove"),
460
+ ),
461
+ patch.object(
462
+ adapter,
463
+ "_broadcast_calldata",
464
+ new=AsyncMock(return_value=(True, {"tx_hash": "0xdeposit"})),
465
+ ),
466
+ patch.object(
467
+ adapter,
468
+ "sweep_isolated_to_cross",
469
+ new=AsyncMock(return_value=(True, {"status": "ok", "moved": []})),
470
+ ) as mock_sweep,
471
+ ):
472
+ ok, res = await adapter.deposit_to_cross_margin(
473
+ collateral_address="0x0000000000000000000000000000000000000001",
474
+ amount_wei=1_000_000, # 1 USDT
475
+ token_id=3,
476
+ market_id=18,
477
+ )
478
+
479
+ assert ok is True
480
+ assert res["status"] == "ok"
481
+ assert res["approve"]["tx_hash"] == "0xapprove"
482
+ assert res["tx"]["tx_hash"] == "0xdeposit"
483
+ assert res["sweep"]["status"] == "ok"
484
+
485
+ mock_sweep.assert_awaited_once_with(token_id=3, market_id=18)
486
+
487
+ @pytest.mark.asyncio
488
+ async def test_close_positions_except_skips_keep_market(self, adapter):
489
+ adapter.simulation = False
490
+ adapter.close_positions_market = AsyncMock(
491
+ return_value=(True, {"status": "ok"})
492
+ )
493
+
494
+ ok, res = await adapter.close_positions_except(
495
+ keep_market_id=19, token_id=3, market_ids=[18, 19, 20]
496
+ )
497
+ assert ok is True
498
+ assert res["status"] == "ok"
499
+
500
+ calls = adapter.close_positions_market.await_args_list
501
+ assert len(calls) == 2
502
+ assert calls[0].args[0] == 18
503
+ assert calls[1].args[0] == 20
504
+
505
+ @pytest.mark.asyncio
506
+ async def test_ensure_position_size_yu_increases_short(self, adapter):
507
+ adapter.simulation = False
508
+ adapter.get_active_positions = AsyncMock(return_value=(True, []))
509
+ adapter.place_rate_order = AsyncMock(return_value=(True, {"tx_hash": "0xopen"}))
510
+
511
+ ok, res = await adapter.ensure_position_size_yu(
512
+ market_id=18, token_id=3, target_size_yu=1.5
513
+ )
514
+ assert ok is True
515
+ assert res["action"] == "increase_short"
516
+ adapter.place_rate_order.assert_awaited_once()
517
+
518
+ _, kwargs = adapter.place_rate_order.await_args
519
+ assert kwargs["market_id"] == 18
520
+ assert kwargs["token_id"] == 3
521
+ assert kwargs["side"] == "short"
522
+ assert kwargs["tif"] == "IOC"
523
+ assert kwargs["size_yu_wei"] == int(1.5 * 1e18)
524
+
525
+ @pytest.mark.asyncio
526
+ async def test_ensure_position_size_yu_decreases(self, adapter):
527
+ adapter.simulation = False
528
+ adapter.get_active_positions = AsyncMock(
529
+ return_value=(True, [{"size": 2.0, "sizeWei": int(2e18)}])
530
+ )
531
+ adapter.close_positions_market = AsyncMock(
532
+ return_value=(True, {"tx_hash": "0xclose"})
533
+ )
534
+
535
+ ok, res = await adapter.ensure_position_size_yu(
536
+ market_id=18, token_id=3, target_size_yu=1.0
537
+ )
538
+ assert ok is True
539
+ assert res["action"] == "decrease"
540
+ adapter.close_positions_market.assert_awaited_once_with(
541
+ market_id=18, token_id=3, size_yu_wei=int(1.0 * 1e18)
542
+ )
543
+
544
+ @pytest.mark.asyncio
545
+ async def test_bridge_hype_oft_rounds_amount_and_builds_tx(self, adapter):
546
+ adapter.simulation = False
547
+ adapter.sign_callback = object()
548
+
549
+ mock_contract = SimpleNamespace()
550
+ mock_dec_fn = SimpleNamespace(call=AsyncMock(return_value=10))
551
+ mock_quote_fn = SimpleNamespace(call=AsyncMock(return_value=(5, 0)))
552
+ mock_functions = SimpleNamespace(
553
+ decimalConversionRate=MagicMock(return_value=mock_dec_fn),
554
+ quoteSend=MagicMock(return_value=mock_quote_fn),
555
+ )
556
+ mock_contract.functions = mock_functions
557
+
558
+ mock_web3 = SimpleNamespace(
559
+ eth=SimpleNamespace(contract=MagicMock(return_value=mock_contract)),
560
+ to_checksum_address=lambda x: x,
561
+ )
562
+ mock_cm = AsyncMock()
563
+ mock_cm.__aenter__.return_value = mock_web3
564
+ mock_cm.__aexit__.return_value = False
565
+
566
+ with (
567
+ patch(
568
+ "wayfinder_paths.adapters.boros_adapter.adapter.web3_from_chain_id",
569
+ return_value=mock_cm,
570
+ ),
571
+ patch(
572
+ "wayfinder_paths.adapters.boros_adapter.adapter.encode_call",
573
+ new=AsyncMock(return_value={"chainId": 999}),
574
+ ) as mock_encode,
575
+ patch(
576
+ "wayfinder_paths.adapters.boros_adapter.adapter.send_transaction",
577
+ new=AsyncMock(return_value="0xtx"),
578
+ ),
579
+ ):
580
+ ok, res = await adapter.bridge_hype_oft_hyperevm_to_arbitrum(
581
+ amount_wei=123,
582
+ max_value_wei=1000,
583
+ to_address=adapter.user_address,
584
+ from_address=adapter.user_address,
585
+ )
586
+
587
+ assert ok is True
588
+ assert res["status"] == "ok"
589
+ assert res["tx_hash"] == "0xtx"
590
+ assert res["amount_wei"] == 120 # rounded down by conversion rate 10
591
+ assert res["native_fee_wei"] == 5
592
+ assert res["total_value_wei"] == 125
593
+
594
+ _, kwargs = mock_encode.await_args
595
+ assert kwargs["fn_name"] == "send"
596
+ assert kwargs["value"] == 125
597
+ send_params = kwargs["args"][0]
598
+ assert send_params[0] == 30110
599
+ assert send_params[2] == 120
600
+
361
601
  @pytest.mark.asyncio
362
602
  async def test_withdraw_collateral_simulation(self, adapter):
363
603
  """Test withdraw in simulation mode."""
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import asyncio
4
4
  import time
5
+ from collections.abc import Awaitable, Callable
5
6
  from decimal import ROUND_DOWN, Decimal, getcontext
6
7
  from typing import Any
7
8
 
@@ -11,6 +12,7 @@ from loguru import logger
11
12
 
12
13
  from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
13
14
  from wayfinder_paths.core.constants import ZERO_ADDRESS
15
+ from wayfinder_paths.core.constants.contracts import HYPERCORE_SENTINEL_ADDRESS
14
16
  from wayfinder_paths.core.constants.hyperliquid import (
15
17
  ARBITRUM_USDC_ADDRESS as _ARBITRUM_USDC_ADDRESS,
16
18
  )
@@ -21,7 +23,6 @@ from wayfinder_paths.core.constants.hyperliquid import (
21
23
  from wayfinder_paths.core.constants.hyperliquid import (
22
24
  HYPERLIQUID_BRIDGE_ADDRESS as _HYPERLIQUID_BRIDGE_ADDRESS,
23
25
  )
24
- from wayfinder_paths.core.types import HyperliquidSignCallback
25
26
 
26
27
  # Re-export Bridge2 constants for backwards compatibility.
27
28
  HYPERLIQUID_BRIDGE_ADDRESS = _HYPERLIQUID_BRIDGE_ADDRESS
@@ -67,7 +68,7 @@ class HyperliquidAdapter(BaseAdapter):
67
68
  config: dict[str, Any] | None = None,
68
69
  *,
69
70
  simulation: bool = False,
70
- sign_callback: HyperliquidSignCallback | None = None,
71
+ sign_callback: Callable[[dict], Awaitable[str]] | None = None,
71
72
  ) -> None:
72
73
  super().__init__("hyperliquid_adapter", config)
73
74
 
@@ -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)
@@ -1,3 +1,4 @@
1
+ from collections.abc import Awaitable, Callable
1
2
  from decimal import Decimal
2
3
  from typing import Any, Literal
3
4
 
@@ -24,7 +25,6 @@ from loguru import logger
24
25
  from web3 import Web3
25
26
 
26
27
  from wayfinder_paths.adapters.hyperliquid_adapter.util import Util
27
- from wayfinder_paths.core.types import HyperliquidSignCallback
28
28
 
29
29
  ARBITRUM_CHAIN_ID = "0xa4b1"
30
30
  MAINNET = "Mainnet"
@@ -39,7 +39,7 @@ class Exchange:
39
39
  self,
40
40
  info: Info,
41
41
  util: Util,
42
- sign_callback: HyperliquidSignCallback,
42
+ sign_callback: Callable[[dict], Awaitable[str]],
43
43
  signing_type: Literal["eip712", "local"],
44
44
  ):
45
45
  self.info = info
@@ -1,9 +1,8 @@
1
+ from collections.abc import Awaitable, Callable
1
2
  from typing import Any
2
3
 
3
4
  from eth_account import Account
4
5
 
5
- from wayfinder_paths.core.types import HyperliquidSignCallback
6
-
7
6
 
8
7
  def _resolve_private_key(config: dict[str, Any]) -> str | None:
9
8
  """Extract private key from config."""
@@ -26,23 +25,7 @@ def _resolve_private_key(config: dict[str, Any]) -> str | None:
26
25
  return None
27
26
 
28
27
 
29
- def create_local_signer(config: dict[str, Any]) -> HyperliquidSignCallback:
30
- """
31
- Create a Hyperliquid signing callback using private key from config.
32
-
33
- For local signing, the payload is a keccak hash (0x...) of the encoded EIP-712 typed data.
34
- The callback signs with the private key and returns a signature dict.
35
-
36
- Args:
37
- config: Configuration dict containing private key in strategy_wallet or main_wallet
38
-
39
- Returns:
40
- HyperliquidSignCallback that signs payloads with the local private key
41
-
42
- Raises:
43
- ValueError: If no private key found in config
44
- """
45
- # Extract private key from config
28
+ def create_local_signer(config: dict[str, Any]) -> Callable[[dict], Awaitable[str]]:
46
29
  private_key = _resolve_private_key(config)
47
30
  if not private_key:
48
31
  raise ValueError(
@@ -57,20 +40,6 @@ def create_local_signer(config: dict[str, Any]) -> HyperliquidSignCallback:
57
40
  async def sign(
58
41
  action: dict[str, Any], payload: str, address: str
59
42
  ) -> dict[str, str] | None:
60
- """
61
- Sign a Hyperliquid action with local private key.
62
-
63
- For local signing, payload is keccak hash (0x...) of encoded typed data.
64
- Sign with account and return signature dict.
65
-
66
- Args:
67
- action: The action being signed (not used for local signing)
68
- payload: Keccak hash (0x...) of encoded EIP-712 typed data
69
- address: The address signing (validation check)
70
-
71
- Returns:
72
- Signature dict {"r": "0x...", "s": "0x...", "v": 28} or None if validation fails
73
- """
74
43
  # Verify address matches account
75
44
  if address.lower() != account.address.lower():
76
45
  return None
@@ -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),