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.

Files changed (156) hide show
  1. wayfinder_paths/__init__.py +0 -4
  2. wayfinder_paths/adapters/balance_adapter/README.md +0 -1
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +313 -167
  4. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  5. wayfinder_paths/adapters/balance_adapter/test_adapter.py +41 -124
  6. wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
  7. wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
  8. wayfinder_paths/adapters/boros_adapter/client.py +476 -0
  9. wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
  10. wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
  11. wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
  12. wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
  13. wayfinder_paths/adapters/boros_adapter/types.py +70 -0
  14. wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
  15. wayfinder_paths/adapters/brap_adapter/README.md +22 -75
  16. wayfinder_paths/adapters/brap_adapter/adapter.py +187 -576
  17. wayfinder_paths/adapters/brap_adapter/examples.json +21 -140
  18. wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
  19. wayfinder_paths/adapters/brap_adapter/test_adapter.py +6 -234
  20. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +180 -92
  21. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
  22. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +82 -14
  23. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
  24. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +586 -61
  25. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
  26. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
  27. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
  28. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
  29. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
  30. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
  31. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
  32. wayfinder_paths/adapters/ledger_adapter/README.md +4 -1
  33. wayfinder_paths/adapters/ledger_adapter/adapter.py +3 -3
  34. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
  35. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
  36. wayfinder_paths/adapters/moonwell_adapter/adapter.py +649 -547
  37. wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
  38. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +160 -239
  39. wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
  40. wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
  41. wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
  42. wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
  43. wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
  44. wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
  45. wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
  46. wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
  47. wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
  48. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
  49. wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
  50. wayfinder_paths/adapters/token_adapter/adapter.py +14 -0
  51. wayfinder_paths/adapters/token_adapter/examples.json +0 -4
  52. wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
  53. wayfinder_paths/conftest.py +24 -17
  54. wayfinder_paths/core/__init__.py +0 -3
  55. wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
  56. wayfinder_paths/core/adapters/models.py +17 -7
  57. wayfinder_paths/core/clients/BRAPClient.py +4 -1
  58. wayfinder_paths/core/clients/ClientManager.py +0 -7
  59. wayfinder_paths/core/clients/LedgerClient.py +196 -172
  60. wayfinder_paths/core/clients/TokenClient.py +47 -1
  61. wayfinder_paths/core/clients/WayfinderClient.py +1 -3
  62. wayfinder_paths/core/clients/__init__.py +0 -5
  63. wayfinder_paths/core/clients/protocols.py +21 -35
  64. wayfinder_paths/core/clients/test_ledger_client.py +448 -0
  65. wayfinder_paths/core/config.py +10 -162
  66. wayfinder_paths/core/constants/__init__.py +73 -2
  67. wayfinder_paths/core/constants/base.py +8 -17
  68. wayfinder_paths/core/constants/chains.py +36 -0
  69. wayfinder_paths/core/constants/contracts.py +52 -0
  70. wayfinder_paths/core/constants/erc20_abi.py +0 -1
  71. wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
  72. wayfinder_paths/core/constants/hyperliquid.py +16 -0
  73. wayfinder_paths/core/constants/moonwell_abi.py +0 -15
  74. wayfinder_paths/core/constants/tokens.py +9 -0
  75. wayfinder_paths/core/engine/manifest.py +66 -0
  76. wayfinder_paths/core/strategies/Strategy.py +0 -71
  77. wayfinder_paths/core/strategies/__init__.py +10 -1
  78. wayfinder_paths/core/strategies/opa_loop.py +167 -0
  79. wayfinder_paths/core/utils/evm_helpers.py +5 -15
  80. wayfinder_paths/core/utils/test_transaction.py +289 -0
  81. wayfinder_paths/core/utils/tokens.py +28 -0
  82. wayfinder_paths/core/utils/transaction.py +57 -8
  83. wayfinder_paths/core/utils/web3.py +8 -3
  84. wayfinder_paths/mcp/__init__.py +5 -0
  85. wayfinder_paths/mcp/preview.py +185 -0
  86. wayfinder_paths/mcp/scripting.py +84 -0
  87. wayfinder_paths/mcp/server.py +52 -0
  88. wayfinder_paths/mcp/state/profile_store.py +195 -0
  89. wayfinder_paths/mcp/state/store.py +89 -0
  90. wayfinder_paths/mcp/test_scripting.py +267 -0
  91. wayfinder_paths/mcp/tools/__init__.py +0 -0
  92. wayfinder_paths/mcp/tools/balances.py +290 -0
  93. wayfinder_paths/mcp/tools/discovery.py +158 -0
  94. wayfinder_paths/mcp/tools/execute.py +770 -0
  95. wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
  96. wayfinder_paths/mcp/tools/quotes.py +288 -0
  97. wayfinder_paths/mcp/tools/run_script.py +286 -0
  98. wayfinder_paths/mcp/tools/strategies.py +188 -0
  99. wayfinder_paths/mcp/tools/tokens.py +46 -0
  100. wayfinder_paths/mcp/tools/wallets.py +354 -0
  101. wayfinder_paths/mcp/utils.py +129 -0
  102. wayfinder_paths/policies/enso.py +1 -2
  103. wayfinder_paths/policies/hyper_evm.py +6 -3
  104. wayfinder_paths/policies/hyperlend.py +1 -2
  105. wayfinder_paths/policies/hyperliquid.py +1 -1
  106. wayfinder_paths/policies/lifi.py +18 -0
  107. wayfinder_paths/policies/moonwell.py +12 -7
  108. wayfinder_paths/policies/prjx.py +1 -3
  109. wayfinder_paths/policies/util.py +8 -2
  110. wayfinder_paths/run_strategy.py +97 -300
  111. wayfinder_paths/strategies/basis_trading_strategy/constants.py +3 -1
  112. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +47 -133
  113. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
  114. wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
  115. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
  116. wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
  117. wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
  118. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
  119. wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
  120. wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
  121. wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
  122. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
  123. wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
  124. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
  125. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
  126. wayfinder_paths/{templates/strategy → strategies/boros_hype_strategy}/test_strategy.py +99 -63
  127. wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
  128. wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
  129. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +15 -23
  130. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +27 -62
  131. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +84 -58
  132. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
  133. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +69 -164
  134. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +43 -76
  135. wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
  136. wayfinder_paths/tests/test_test_coverage.py +1 -4
  137. wayfinder_paths-0.1.24.dist-info/METADATA +378 -0
  138. wayfinder_paths-0.1.24.dist-info/RECORD +185 -0
  139. {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.24.dist-info}/WHEEL +1 -1
  140. wayfinder_paths/core/clients/WalletClient.py +0 -41
  141. wayfinder_paths/core/engine/StrategyJob.py +0 -110
  142. wayfinder_paths/core/services/test_local_evm_txn.py +0 -145
  143. wayfinder_paths/scripts/create_strategy.py +0 -139
  144. wayfinder_paths/scripts/make_wallets.py +0 -142
  145. wayfinder_paths/templates/adapter/README.md +0 -150
  146. wayfinder_paths/templates/adapter/adapter.py +0 -16
  147. wayfinder_paths/templates/adapter/examples.json +0 -8
  148. wayfinder_paths/templates/adapter/test_adapter.py +0 -30
  149. wayfinder_paths/templates/strategy/README.md +0 -186
  150. wayfinder_paths/templates/strategy/examples.json +0 -11
  151. wayfinder_paths/templates/strategy/strategy.py +0 -35
  152. wayfinder_paths/tests/test_smoke_manifest.py +0 -63
  153. wayfinder_paths-0.1.22.dist-info/METADATA +0 -355
  154. wayfinder_paths-0.1.22.dist-info/RECORD +0 -129
  155. /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
  156. {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.24.dist-info}/LICENSE +0 -0
@@ -4,6 +4,7 @@ import asyncio
4
4
  import math
5
5
  import random
6
6
  import time
7
+ import traceback
7
8
  from collections.abc import Awaitable, Callable
8
9
  from datetime import UTC, datetime, timedelta
9
10
  from decimal import ROUND_DOWN, ROUND_UP, Decimal, getcontext
@@ -12,12 +13,8 @@ from statistics import fmean
12
13
  from typing import Any
13
14
 
14
15
  from wayfinder_paths.adapters.balance_adapter.adapter import BalanceAdapter
15
- from wayfinder_paths.adapters.hyperliquid_adapter.adapter import (
16
- HYPERLIQUID_BRIDGE_ADDRESS,
17
- HyperliquidAdapter,
18
- )
16
+ from wayfinder_paths.adapters.hyperliquid_adapter.adapter import HyperliquidAdapter
19
17
  from wayfinder_paths.adapters.hyperliquid_adapter.executor import (
20
- HyperliquidExecutor,
21
18
  LocalHyperliquidExecutor,
22
19
  )
23
20
  from wayfinder_paths.adapters.hyperliquid_adapter.paired_filler import (
@@ -54,6 +51,10 @@ from wayfinder_paths.core.analytics import (
54
51
  from wayfinder_paths.core.analytics import (
55
52
  z_from_conf as analytics_z_from_conf,
56
53
  )
54
+ from wayfinder_paths.core.clients.protocols import (
55
+ HyperliquidExecutorProtocol as HyperliquidExecutor,
56
+ )
57
+ from wayfinder_paths.core.constants.contracts import HYPERLIQUID_BRIDGE
57
58
  from wayfinder_paths.core.strategies.descriptors import (
58
59
  Complexity,
59
60
  Directionality,
@@ -63,6 +64,11 @@ from wayfinder_paths.core.strategies.descriptors import (
63
64
  Volatility,
64
65
  )
65
66
  from wayfinder_paths.core.strategies.Strategy import StatusDict, StatusTuple, Strategy
67
+ from wayfinder_paths.policies.erc20 import any_erc20_function
68
+ from wayfinder_paths.policies.hyperliquid import (
69
+ any_hyperliquid_l1_payload,
70
+ any_hyperliquid_user_payload,
71
+ )
66
72
  from wayfinder_paths.strategies.basis_trading_strategy.constants import (
67
73
  USDC_ARBITRUM_TOKEN_ID,
68
74
  )
@@ -201,7 +207,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
201
207
  merged_config["strategy_wallet"] = strategy_wallet
202
208
  self.config = merged_config
203
209
 
204
- # Position tracking
205
210
  self.current_position: BasisPosition | None = None
206
211
  self.deposit_amount: float = 0.0
207
212
 
@@ -266,8 +271,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
266
271
  adapters.append(self.ledger_adapter)
267
272
  if self.hyperliquid_adapter is not None:
268
273
  adapters.append(self.hyperliquid_adapter)
269
- if adapters:
270
- self.register_adapters(adapters)
271
274
 
272
275
  async def setup(self) -> None:
273
276
  self.logger.info("Starting BasisTradingStrategy setup")
@@ -332,7 +335,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
332
335
  )
333
336
  return
334
337
 
335
- # Find matching spot position
336
338
  spot_position = None
337
339
  spot_balances = spot_state.get("balances", [])
338
340
  for bal in spot_balances:
@@ -437,7 +439,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
437
439
  return (False, f"Failed to transfer ETH for gas: {gas_res}")
438
440
  self.logger.info(f"Gas transfer successful: {gas_res}")
439
441
 
440
- # Real deposit: ensure funds are in the strategy wallet.
441
442
  try:
442
443
  main_address = self._get_main_wallet_address()
443
444
  strategy_address = self._get_strategy_wallet_address()
@@ -446,7 +447,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
446
447
  strategy_balance_ok,
447
448
  strategy_balance,
448
449
  ) = await self.balance_adapter.get_balance(
449
- query=USDC_ARBITRUM_TOKEN_ID,
450
+ token_id=USDC_ARBITRUM_TOKEN_ID,
450
451
  wallet_address=strategy_address,
451
452
  )
452
453
  strategy_usdc = 0.0
@@ -481,7 +482,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
481
482
  f"Strategy wallet already has {strategy_usdc:.2f} USDC, skipping transfer from main"
482
483
  )
483
484
 
484
- # Accumulate deposit amount for bridging in update()
485
485
  self.deposit_amount += main_token_amount
486
486
 
487
487
  return (
@@ -505,7 +505,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
505
505
  strategy_balance_ok,
506
506
  strategy_balance,
507
507
  ) = await self.balance_adapter.get_balance(
508
- query=USDC_ARBITRUM_TOKEN_ID,
508
+ token_id=USDC_ARBITRUM_TOKEN_ID,
509
509
  wallet_address=strategy_address,
510
510
  )
511
511
  if strategy_balance_ok and strategy_balance:
@@ -527,7 +527,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
527
527
  if total_available < 1.0 and self.current_position is None:
528
528
  return (False, "No funds to manage. Call deposit() first.")
529
529
 
530
- # Bridge USDC from strategy wallet to Hyperliquid if needed
531
530
  if strategy_usdc > 10.0:
532
531
  try:
533
532
  self.logger.info(
@@ -538,7 +537,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
538
537
  token_id=USDC_ARBITRUM_TOKEN_ID,
539
538
  amount=strategy_usdc,
540
539
  from_wallet=strategy_wallet,
541
- to_address=HYPERLIQUID_BRIDGE_ADDRESS,
540
+ to_address=HYPERLIQUID_BRIDGE,
542
541
  )
543
542
 
544
543
  if not success:
@@ -547,7 +546,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
547
546
 
548
547
  self.logger.info(f"USDC sent to bridge, tx: {result}")
549
548
 
550
- # Wait for Hyperliquid to credit the deposit
551
549
  self.logger.info("Waiting for Hyperliquid to credit the deposit...")
552
550
 
553
551
  (
@@ -578,7 +576,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
578
576
  except Exception as e:
579
577
  self.logger.warning(f"Failed to bridge USDC to Hyperliquid: {e}")
580
578
 
581
- # If no position, find and open one
582
579
  if self.current_position is None:
583
580
  return await self._find_and_open_position()
584
581
 
@@ -702,8 +699,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
702
699
 
703
700
  except Exception as e:
704
701
  self.logger.error(f"Analysis failed: {e}")
705
- import traceback
706
-
707
702
  traceback.print_exc()
708
703
  return {
709
704
  "success": False,
@@ -759,7 +754,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
759
754
  strategy_usdc = 0.0
760
755
  try:
761
756
  success, balance_data = await self.balance_adapter.get_balance(
762
- query=usdc_token_id,
757
+ token_id=usdc_token_id,
763
758
  wallet_address=address,
764
759
  )
765
760
  if success:
@@ -774,7 +769,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
774
769
  margin_summary = user_state.get("marginSummary", {})
775
770
  hl_perp_value = float(margin_summary.get("accountValue", 0))
776
771
 
777
- # Also check spot USDC balance
778
772
  success, spot_state = await self.hyperliquid_adapter.get_spot_user_state(
779
773
  address
780
774
  )
@@ -798,13 +792,11 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
798
792
  f"Call exit() to transfer to main wallet.",
799
793
  )
800
794
 
801
- # Close any open position
802
795
  if self.current_position is not None:
803
796
  close_success, close_msg = await self._close_position()
804
797
  if not close_success:
805
798
  return (False, f"Failed to close position: {close_msg}")
806
799
 
807
- # Step 1: Transfer any spot USDC to perp for withdrawal
808
800
  # Wait for spot sale to settle before checking balance
809
801
  await asyncio.sleep(5)
810
802
 
@@ -850,7 +842,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
850
842
  break
851
843
  break
852
844
 
853
- # Step 2: Get updated perp balance for withdrawal (with retry)
854
845
  # Wait a moment for transfers to settle
855
846
  await asyncio.sleep(2)
856
847
 
@@ -874,7 +865,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
874
865
  if withdrawable <= 0:
875
866
  return (False, "No withdrawable funds available")
876
867
 
877
- # Step 3: Withdraw from Hyperliquid to Arbitrum (strategy wallet)
878
868
  self.logger.info(
879
869
  f"Withdrawing ${withdrawable:.2f} from Hyperliquid to Arbitrum"
880
870
  )
@@ -888,7 +878,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
888
878
 
889
879
  self.logger.info(f"Withdrawal initiated: {withdraw_result}")
890
880
 
891
- # Step 4: Wait for withdrawal to appear on-chain
892
881
  # Hyperliquid withdrawals typically take 5-15 minutes
893
882
  self.logger.info("Waiting for withdrawal to appear on-chain...")
894
883
 
@@ -915,13 +904,12 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
915
904
  f"Withdrawal confirmed: tx={tx_hash}, amount=${withdrawn_amount:.2f}"
916
905
  )
917
906
 
918
- # Step 5: Wait a bit for the USDC to be credited on Arbitrum
919
907
  await asyncio.sleep(10)
920
908
 
921
909
  final_balance = 0.0
922
910
  try:
923
911
  success, balance_data = await self.balance_adapter.get_balance(
924
- query=usdc_token_id,
912
+ token_id=usdc_token_id,
925
913
  wallet_address=address,
926
914
  )
927
915
  if success:
@@ -949,7 +937,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
949
937
 
950
938
  transferred_items = []
951
939
 
952
- # Transfer USDC to main wallet
953
940
  usdc_ok, usdc_raw = await self.balance_adapter.get_balance(
954
941
  token_id=USDC_ARBITRUM_TOKEN_ID,
955
942
  wallet_address=strategy_address,
@@ -962,7 +949,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
962
949
  success,
963
950
  msg,
964
951
  ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
965
- query=USDC_ARBITRUM_TOKEN_ID,
952
+ token_id=USDC_ARBITRUM_TOKEN_ID,
966
953
  amount=usdc_balance,
967
954
  strategy_name=self.name,
968
955
  skip_ledger=False,
@@ -989,7 +976,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
989
976
  success,
990
977
  msg,
991
978
  ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
992
- query="ethereum-arbitrum",
979
+ token_id="ethereum-arbitrum",
993
980
  amount=transferable_eth,
994
981
  strategy_name=self.name,
995
982
  skip_ledger=False,
@@ -1045,63 +1032,16 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1045
1032
  gassed_up=True,
1046
1033
  )
1047
1034
 
1048
- @staticmethod
1049
- async def policies() -> list[str]:
1050
- # Placeholder - would include Hyperliquid-specific policies
1051
- return []
1052
-
1053
1035
  async def ensure_builder_fee_approved(self) -> StatusTuple:
1054
- if not self.builder_fee:
1055
- return True, "No builder fee configured"
1056
-
1036
+ if not self.hyperliquid_adapter:
1037
+ return False, "Hyperliquid adapter not configured"
1057
1038
  address = self._get_strategy_wallet_address()
1058
- builder = self.builder_fee.get("b", "")
1059
- required_fee = self.builder_fee.get("f", 0)
1060
-
1061
- if not builder or required_fee <= 0:
1062
- return True, "Builder fee not required"
1063
-
1064
- try:
1065
- success, current_fee = await self.hyperliquid_adapter.get_max_builder_fee(
1066
- user=address,
1067
- builder=builder,
1068
- )
1069
-
1070
- if not success:
1071
- self.logger.warning(
1072
- "Failed to check builder fee approval, continuing anyway"
1073
- )
1074
- return True, "Could not verify builder fee, proceeding"
1075
-
1076
- self.logger.info(
1077
- f"Builder fee approval check: current={current_fee}, required={required_fee}"
1078
- )
1079
-
1080
- if current_fee >= required_fee:
1081
- return True, f"Builder fee already approved: {current_fee}"
1082
-
1083
- # Need to approve
1084
- max_fee_rate = f"{required_fee / 1000:.3f}%"
1085
- self.logger.info(
1086
- f"Approving builder fee: builder={builder}, rate={max_fee_rate}"
1087
- )
1088
-
1089
- success, result = await self.hyperliquid_adapter.approve_builder_fee(
1090
- builder=builder,
1091
- max_fee_rate=max_fee_rate,
1092
- address=address,
1093
- )
1094
-
1095
- if not success:
1096
- self.logger.error(f"Builder fee approval failed: {result}")
1097
- return False, f"Builder fee approval failed: {result}"
1098
-
1099
- self.logger.info(f"Builder fee approved: {result}")
1100
- return True, f"Builder fee approved at {max_fee_rate}"
1101
-
1102
- except Exception as e:
1103
- self.logger.error(f"Builder fee approval error: {e}")
1104
- return False, f"Builder fee approval error: {e}"
1039
+ if not address:
1040
+ return False, "No strategy wallet address"
1041
+ return await self.hyperliquid_adapter.ensure_builder_fee_approved(
1042
+ address=address,
1043
+ builder_fee=self.builder_fee,
1044
+ )
1105
1045
 
1106
1046
  # ------------------------------------------------------------------ #
1107
1047
  # Position Management #
@@ -1218,7 +1158,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1218
1158
  coin = best.get("coin", "unknown")
1219
1159
  safe = best.get("safe", {})
1220
1160
 
1221
- # Use 7-day horizon sizing by default
1222
1161
  safe_7 = safe.get("7") or {}
1223
1162
  if safe_7.get("spot_usdc", 0) <= 0 or safe_7.get("perp_amount", 0) <= 0:
1224
1163
  return (True, f"Best opportunity ({coin}) returned zero sizing.")
@@ -1237,19 +1176,16 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1237
1176
  f"expected net APY: {expected_net_apy_pct:.2f}%, target qty: {target_qty}"
1238
1177
  )
1239
1178
 
1240
- # Execute position using PairedFiller
1241
1179
  address = self._get_strategy_wallet_address()
1242
1180
  order_usd = float(safe_7.get("spot_usdc", 0.0) or 0.0)
1243
1181
  order_usd = float(
1244
1182
  Decimal(str(order_usd)).quantize(Decimal("0.01"), rounding=ROUND_UP)
1245
1183
  )
1246
1184
 
1247
- # Step 1: Ensure builder fee is approved
1248
1185
  fee_success, fee_msg = await self.ensure_builder_fee_approved()
1249
1186
  if not fee_success:
1250
1187
  return (False, f"Builder fee approval failed: {fee_msg}")
1251
1188
 
1252
- # Step 2: Update leverage for the perp asset
1253
1189
  self.logger.info(f"Setting leverage to {leverage}x for {coin}")
1254
1190
  success, lev_result = await self.hyperliquid_adapter.update_leverage(
1255
1191
  asset_id=perp_asset_id,
@@ -1261,7 +1197,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1261
1197
  self.logger.warning(f"Failed to set leverage: {lev_result}")
1262
1198
  # Continue anyway - leverage might already be set
1263
1199
 
1264
- # Step 3: Ensure USDC is split correctly between spot and perp.
1265
1200
  # Target: spot has order_usd for the spot buy, perp holds the remainder as margin.
1266
1201
  split_ok, split_msg = await self._rebalance_usdc_between_perp_and_spot(
1267
1202
  target_spot_usdc=order_usd,
@@ -1270,7 +1205,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1270
1205
  if not split_ok:
1271
1206
  self.logger.warning(f"USDC rebalance failed: {split_msg}")
1272
1207
 
1273
- # Step 4: Execute paired fill
1274
1208
  filler = PairedFiller(
1275
1209
  adapter=self.hyperliquid_adapter,
1276
1210
  address=address,
@@ -1304,7 +1238,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1304
1238
  success, mids = await self.hyperliquid_adapter.get_all_mid_prices()
1305
1239
  entry_price = self._resolve_mid_price(coin, mids) if success else 0.0
1306
1240
 
1307
- # Step 5: Get liquidation price and place stop-loss
1308
1241
  success, user_state = await self.hyperliquid_adapter.get_user_state(address)
1309
1242
  liquidation_price = None
1310
1243
  if success:
@@ -1473,7 +1406,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1473
1406
 
1474
1407
  units_to_add = order_usd / price
1475
1408
 
1476
- # Round to valid decimals for the assets
1477
1409
  spot_valid = self.hyperliquid_adapter.get_valid_order_size(
1478
1410
  pos.spot_asset_id, units_to_add
1479
1411
  )
@@ -1493,7 +1425,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1493
1425
  f"(${order_usd:.2f}) at {leverage}x leverage"
1494
1426
  )
1495
1427
 
1496
- # Ensure idle USDC is split correctly between spot and perp for this scale-up.
1497
1428
  # Target: spot has order_usd for the spot buy, perp holds the remainder as margin.
1498
1429
  split_ok, split_msg = await self._rebalance_usdc_between_perp_and_spot(
1499
1430
  target_spot_usdc=order_usd,
@@ -1502,7 +1433,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1502
1433
  if not split_ok:
1503
1434
  self.logger.warning(f"USDC rebalance failed: {split_msg}")
1504
1435
 
1505
- # Execute paired fill to add to both legs
1506
1436
  filler = PairedFiller(
1507
1437
  adapter=self.hyperliquid_adapter,
1508
1438
  address=address,
@@ -1585,8 +1515,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1585
1515
  )
1586
1516
  return await self._find_and_open_position()
1587
1517
 
1588
- # ------------------------------------------------------------------ #
1589
- # ------------------------------------------------------------------ #
1590
1518
  needs_rebalance, reason = await self._needs_new_position(state, hl_value)
1591
1519
 
1592
1520
  if needs_rebalance:
@@ -1599,31 +1527,22 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1599
1527
  f"Position needs attention but in cooldown: {cooldown_reason}",
1600
1528
  )
1601
1529
 
1602
- # Perform rebalance: close and reopen
1603
1530
  self.logger.info(f"Rebalancing position: {reason}")
1604
-
1605
- # Close existing position
1606
1531
  close_success, close_msg = await self._close_position()
1607
1532
  if not close_success:
1608
1533
  return (False, f"Rebalance failed - could not close: {close_msg}")
1609
1534
 
1610
- # Open new position
1611
1535
  return await self._find_and_open_position()
1612
1536
 
1613
- # ------------------------------------------------------------------ #
1614
- # ------------------------------------------------------------------ #
1615
1537
  leg_ok, leg_msg = await self._verify_leg_balance(state)
1616
1538
  if not leg_ok:
1617
1539
  self.logger.warning(f"Leg imbalance detected: {leg_msg}")
1618
- # Try to repair the imbalance
1619
1540
  repair_ok, repair_msg = await self._repair_leg_imbalance(state)
1620
1541
  if repair_ok:
1621
1542
  actions_taken.append(f"Repaired leg imbalance: {repair_msg}")
1622
1543
  else:
1623
1544
  actions_taken.append(f"Leg imbalance repair failed: {repair_msg}")
1624
1545
 
1625
- # ------------------------------------------------------------------ #
1626
- # ------------------------------------------------------------------ #
1627
1546
  perp_margin, spot_usdc = await self._get_undeployed_capital()
1628
1547
  total_idle = perp_margin + spot_usdc
1629
1548
  min_deploy = max(self.MIN_UNUSED_USD, self.UNUSED_REL_EPS * self.deposit_amount)
@@ -1642,8 +1561,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1642
1561
  else:
1643
1562
  actions_taken.append(f"Scale-up failed: {scale_msg}")
1644
1563
 
1645
- # ------------------------------------------------------------------ #
1646
- # ------------------------------------------------------------------ #
1647
1564
  sl_ok, sl_msg = await self._ensure_stop_loss_valid(state)
1648
1565
  if not sl_ok:
1649
1566
  actions_taken.append(f"Stop-loss issue: {sl_msg}")
@@ -1915,7 +1832,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1915
1832
  order_coin = order.get("coin", "")
1916
1833
  order_id = order.get("oid")
1917
1834
 
1918
- # Cancel perp orders for this coin
1919
1835
  if order_coin == pos.coin and order_id:
1920
1836
  self.logger.info(f"Canceling perp order {order_id} for {pos.coin}")
1921
1837
  await self.hyperliquid_adapter.cancel_order(
@@ -1924,7 +1840,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1924
1840
  address=address,
1925
1841
  )
1926
1842
 
1927
- # Cancel spot orders for this coin
1928
1843
  if spot_coin and order_coin == spot_coin and order_id:
1929
1844
  self.logger.info(f"Canceling spot order {order_id} for {spot_coin}")
1930
1845
  await self.hyperliquid_adapter.cancel_order(
@@ -1940,10 +1855,8 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1940
1855
  pos = self.current_position
1941
1856
  self.logger.info(f"Closing position on {pos.coin}")
1942
1857
 
1943
- # Cancel all stop-loss and limit orders first
1944
1858
  await self._cancel_all_position_orders()
1945
1859
 
1946
- # Real execution via PairedFiller - reverse direction to close
1947
1860
  try:
1948
1861
  address = self._get_strategy_wallet_address()
1949
1862
  filler = PairedFiller(
@@ -2011,7 +1924,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2011
1924
  if funding_earned > deposited_amount * self.FUNDING_REBALANCE_THRESHOLD:
2012
1925
  return True, f"Funding earned {funding_earned:.2f} exceeds threshold"
2013
1926
 
2014
- # Check 4: Perp must be SHORT
2015
1927
  perp_size = float(perp_position.get("szi", 0))
2016
1928
  if perp_size >= 0:
2017
1929
  return True, "Perp position is not short"
@@ -2110,7 +2022,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2110
2022
  address
2111
2023
  )
2112
2024
 
2113
- # Track existing valid orders and orders to cancel
2114
2025
  has_valid_perp_stop = False
2115
2026
  has_valid_spot_limit = False
2116
2027
  orders_to_cancel = []
@@ -2183,7 +2094,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2183
2094
  (spot_asset_id, order_id, "spot limit")
2184
2095
  )
2185
2096
 
2186
- # Cancel invalid/duplicate orders
2187
2097
  for asset_id, order_id, order_desc in orders_to_cancel:
2188
2098
  self.logger.info(f"Canceling {order_desc} order {order_id}")
2189
2099
  await self.hyperliquid_adapter.cancel_order(
@@ -2192,7 +2102,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2192
2102
  address=address,
2193
2103
  )
2194
2104
 
2195
- # Place perp stop-loss if not valid one exists
2196
2105
  if not has_valid_perp_stop:
2197
2106
  success, result = await self.hyperliquid_adapter.place_stop_loss(
2198
2107
  asset_id=perp_asset_id,
@@ -2205,7 +2114,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2205
2114
  return False, f"Failed to place perp stop-loss: {result}"
2206
2115
  self.logger.info(f"Placed perp stop-loss at {stop_loss_price} for {coin}")
2207
2116
 
2208
- # Place spot limit sell if needed
2209
2117
  if (
2210
2118
  spot_asset_id
2211
2119
  and spot_position_size
@@ -2249,17 +2157,22 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2249
2157
  if not success or not transactions:
2250
2158
  return None
2251
2159
 
2252
- # Find most recent spot buy transaction (indicates rotation)
2253
- for txn in transactions:
2254
- data = txn.get("data", {})
2255
- op_data = data.get("op_data", {})
2160
+ tx_list = (
2161
+ transactions.get("transactions", [])
2162
+ if isinstance(transactions, dict)
2163
+ else []
2164
+ )
2165
+ for txn in tx_list:
2166
+ op_data = txn.get("op_data", {})
2256
2167
  if (
2257
2168
  op_data.get("type") == "HYPE_SPOT"
2258
2169
  and op_data.get("buy_or_sell") == "buy"
2259
2170
  ):
2260
- created_at = txn.get("created_at")
2261
- if created_at:
2262
- return datetime.fromisoformat(created_at.replace("Z", "+00:00"))
2171
+ created_str = txn.get("created")
2172
+ if created_str:
2173
+ return datetime.fromisoformat(
2174
+ str(created_str).replace("Z", "+00:00")
2175
+ )
2263
2176
 
2264
2177
  return None
2265
2178
  except Exception as e:
@@ -2326,7 +2239,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2326
2239
  # USDC is 1:1
2327
2240
  hl_value += total
2328
2241
  else:
2329
- # Look up mid price for non-USDC assets
2330
2242
  mid_price = self._resolve_mid_price(coin, mid_prices)
2331
2243
  if mid_price > 0:
2332
2244
  hl_value += total * mid_price
@@ -2339,7 +2251,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2339
2251
  try:
2340
2252
  strategy_address = self._get_strategy_wallet_address()
2341
2253
  success, balance = await self.balance_adapter.get_balance(
2342
- query=USDC_ARBITRUM_TOKEN_ID,
2254
+ token_id=USDC_ARBITRUM_TOKEN_ID,
2343
2255
  wallet_address=strategy_address,
2344
2256
  )
2345
2257
  if success and balance:
@@ -2419,7 +2331,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2419
2331
  oi_usd = oi_base * mark_px
2420
2332
  day_ntl_usd = float(ctx.get("dayNtlVlm") or 0.0)
2421
2333
 
2422
- # Apply liquidity filters
2423
2334
  if oi_usd < oi_floor or day_ntl_usd < day_vlm_floor:
2424
2335
  continue
2425
2336
 
@@ -2533,14 +2444,12 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2533
2444
  )
2534
2445
  continue
2535
2446
 
2536
- # Dedupe and merge
2537
2447
  for record in data:
2538
2448
  ts = record.get("time", 0)
2539
2449
  if ts not in seen_times:
2540
2450
  seen_times.add(ts)
2541
2451
  all_funding.append(record)
2542
2452
 
2543
- # Sort by time
2544
2453
  all_funding.sort(key=lambda x: x.get("time", 0))
2545
2454
 
2546
2455
  if not all_funding:
@@ -2580,14 +2489,12 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2580
2489
  )
2581
2490
  continue
2582
2491
 
2583
- # Dedupe and merge
2584
2492
  for candle in data:
2585
2493
  ts = candle.get("t", 0)
2586
2494
  if ts not in seen_times:
2587
2495
  seen_times.add(ts)
2588
2496
  all_candles.append(candle)
2589
2497
 
2590
- # Sort by time
2591
2498
  all_candles.sort(key=lambda x: x.get("t", 0))
2592
2499
 
2593
2500
  if not all_candles:
@@ -3405,7 +3312,6 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
3405
3312
  if len(pts) > 256:
3406
3313
  break
3407
3314
  pts.append(float(upper))
3408
- # Dedupe + sort
3409
3315
  return sorted({float(p) for p in pts if p > 0.0})
3410
3316
 
3411
3317
  async def max_spot_order_usd_for_book(
@@ -3828,3 +3734,11 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
3828
3734
  if not address:
3829
3735
  raise ValueError("main_wallet address not found")
3830
3736
  return str(address)
3737
+
3738
+ @staticmethod
3739
+ async def policies() -> list[str]:
3740
+ return [
3741
+ any_hyperliquid_l1_payload(),
3742
+ any_hyperliquid_user_payload(),
3743
+ any_erc20_function(HYPERLIQUID_BRIDGE),
3744
+ ]