wayfinder-paths 0.1.13__py3-none-any.whl → 0.1.14__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 (47) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +13 -14
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +33 -32
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +123 -0
  4. wayfinder_paths/adapters/brap_adapter/README.md +11 -16
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +78 -63
  6. wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
  7. wayfinder_paths/adapters/brap_adapter/test_adapter.py +121 -59
  8. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +16 -14
  9. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +114 -60
  10. wayfinder_paths/adapters/pool_adapter/README.md +9 -10
  11. wayfinder_paths/adapters/pool_adapter/adapter.py +9 -10
  12. wayfinder_paths/adapters/token_adapter/README.md +2 -14
  13. wayfinder_paths/adapters/token_adapter/adapter.py +16 -10
  14. wayfinder_paths/adapters/token_adapter/examples.json +4 -8
  15. wayfinder_paths/adapters/token_adapter/test_adapter.py +5 -3
  16. wayfinder_paths/core/clients/BRAPClient.py +102 -61
  17. wayfinder_paths/core/clients/ClientManager.py +1 -68
  18. wayfinder_paths/core/clients/HyperlendClient.py +125 -64
  19. wayfinder_paths/core/clients/LedgerClient.py +1 -4
  20. wayfinder_paths/core/clients/PoolClient.py +122 -48
  21. wayfinder_paths/core/clients/TokenClient.py +91 -36
  22. wayfinder_paths/core/clients/WalletClient.py +26 -56
  23. wayfinder_paths/core/clients/WayfinderClient.py +28 -160
  24. wayfinder_paths/core/clients/__init__.py +0 -2
  25. wayfinder_paths/core/clients/protocols.py +35 -46
  26. wayfinder_paths/core/clients/sdk_example.py +37 -22
  27. wayfinder_paths/core/engine/StrategyJob.py +7 -55
  28. wayfinder_paths/core/services/local_evm_txn.py +6 -6
  29. wayfinder_paths/core/services/local_token_txn.py +1 -1
  30. wayfinder_paths/core/strategies/Strategy.py +0 -2
  31. wayfinder_paths/core/utils/evm_helpers.py +2 -2
  32. wayfinder_paths/run_strategy.py +8 -19
  33. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +10 -11
  34. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +40 -25
  35. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
  36. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +3 -3
  37. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +12 -6
  38. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +1 -1
  39. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +88 -56
  40. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +16 -12
  41. wayfinder_paths/templates/strategy/README.md +3 -3
  42. wayfinder_paths/templates/strategy/test_strategy.py +3 -2
  43. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/METADATA +14 -49
  44. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/RECORD +46 -47
  45. wayfinder_paths/core/clients/AuthClient.py +0 -83
  46. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/LICENSE +0 -0
  47. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.14.dist-info}/WHEEL +0 -0
@@ -154,7 +154,7 @@ class LocalTokenTxnService(TokenTxn):
154
154
 
155
155
  def _chain_id(self, chain: Any) -> int:
156
156
  if isinstance(chain, dict):
157
- chain_id = chain.get("id") or chain.get("chain_id")
157
+ chain_id = chain.get("id")
158
158
  else:
159
159
  chain_id = getattr(chain, "id", None)
160
160
  if chain_id is None:
@@ -57,12 +57,10 @@ class Strategy(ABC):
57
57
  main_wallet: WalletConfig | dict[str, Any] | None = None,
58
58
  strategy_wallet: WalletConfig | dict[str, Any] | None = None,
59
59
  web3_service: Web3Service | None = None,
60
- api_key: str | None = None,
61
60
  ):
62
61
  self.adapters = {}
63
62
  self.ledger_adapter = None
64
63
  self.logger = logger.bind(strategy=self.__class__.__name__)
65
- # Note: api_key is passed to ClientManager, not set in environment
66
64
  self.config = config
67
65
 
68
66
  async def setup(self) -> None:
@@ -43,12 +43,12 @@ def resolve_chain_id(token_info: dict[str, Any], logger_instance=None) -> int |
43
43
  """
44
44
  log = logger_instance or logger
45
45
  chain_meta = token_info.get("chain") or {}
46
- chain_id = chain_meta.get("chain_id")
46
+ chain_id = chain_meta.get("id")
47
47
  try:
48
48
  if chain_id is not None:
49
49
  return int(chain_id)
50
50
  except (ValueError, TypeError):
51
- log.debug("Invalid chain_id in token_info.chain_id: %s", chain_id)
51
+ log.debug("Invalid chain_id in token_info.chain: %s", chain_id)
52
52
  return chain_code_to_chain_id(chain_meta.get("code"))
53
53
 
54
54
 
@@ -20,7 +20,6 @@ def load_strategy(
20
20
  strategy_name: str,
21
21
  *,
22
22
  strategy_config: dict | None = None,
23
- api_key: str | None = None,
24
23
  ):
25
24
  """
26
25
  Dynamically load a strategy by name
@@ -28,7 +27,6 @@ def load_strategy(
28
27
  Args:
29
28
  strategy_name: Name of the strategy to load (directory name in strategies/)
30
29
  strategy_config: Configuration dict for the strategy
31
- api_key: Optional API key for service account authentication
32
30
 
33
31
  Returns:
34
32
  Strategy instance
@@ -70,7 +68,7 @@ def load_strategy(
70
68
  if strategy_class is None:
71
69
  raise ValueError(f"No Strategy class found in {module_path}")
72
70
 
73
- return strategy_class(config=strategy_config, api_key=api_key)
71
+ return strategy_class(config=strategy_config)
74
72
 
75
73
 
76
74
  def load_config(
@@ -135,18 +133,15 @@ async def run_strategy(
135
133
  # Load configuration with strategy name for wallet lookup
136
134
  logger.debug(f"Config path provided: {config_path}")
137
135
  config = load_config(config_path, strategy_name=strategy_name)
138
- creds = (
139
- "yes"
140
- if (config.user.username and config.user.password)
141
- or config.user.refresh_token
142
- else "no"
143
- )
144
- main_wallet = config.user.main_wallet_address or "none"
145
- strategy_wallet = config.user.strategy_wallet_address or "none"
146
136
  logger.debug(
147
- f"Loaded config: creds={creds} wallets(main={main_wallet} strategy={strategy_wallet})"
137
+ "Loaded config: wallets(main={} strategy={})",
138
+ config.user.main_wallet_address or "none",
139
+ config.user.strategy_wallet_address or "none",
148
140
  )
149
141
 
142
+ # Validate required configuration
143
+ # Authentication is via system.api_key in config.json
144
+
150
145
  # Load strategy with the enriched config
151
146
  strategy = load_strategy(
152
147
  strategy_name,
@@ -159,13 +154,7 @@ async def run_strategy(
159
154
 
160
155
  # Setup strategy job
161
156
  logger.info("Setting up strategy job...")
162
- auth_mode = (
163
- "credentials"
164
- if (config.user.username and config.user.password)
165
- or config.user.refresh_token
166
- else "missing"
167
- )
168
- logger.debug(f"Auth mode: {auth_mode}")
157
+ logger.debug("Auth mode: API key (from system.api_key)")
169
158
  await strategy_job.setup()
170
159
 
171
160
  # Execute action
@@ -203,9 +203,8 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
203
203
  strategy_wallet: dict[str, Any] | None = None,
204
204
  web3_service: Web3Service | None = None,
205
205
  hyperliquid_executor: HyperliquidExecutor | None = None,
206
- api_key: str | None = None,
207
206
  ) -> None:
208
- super().__init__(api_key=api_key)
207
+ super().__init__()
209
208
 
210
209
  merged_config = dict(config or {})
211
210
  if main_wallet:
@@ -479,7 +478,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
479
478
  gas_ok,
480
479
  gas_res,
481
480
  ) = await self.balance_adapter.move_from_main_wallet_to_strategy_wallet(
482
- token_id="ethereum-arbitrum", # Native ETH on Arbitrum
481
+ query="ethereum-arbitrum", # Native ETH on Arbitrum
483
482
  amount=gas_token_amount,
484
483
  strategy_name=self.name or "basis_trading_strategy",
485
484
  skip_ledger=True,
@@ -500,7 +499,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
500
499
  strategy_balance_ok,
501
500
  strategy_balance,
502
501
  ) = await self.balance_adapter.get_balance(
503
- token_id=USDC_ARBITRUM_TOKEN_ID,
502
+ query=USDC_ARBITRUM_TOKEN_ID,
504
503
  wallet_address=strategy_address,
505
504
  )
506
505
  strategy_usdc = 0.0
@@ -518,7 +517,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
518
517
  move_ok,
519
518
  move_res,
520
519
  ) = await self.balance_adapter.move_from_main_wallet_to_strategy_wallet(
521
- token_id=USDC_ARBITRUM_TOKEN_ID,
520
+ query=USDC_ARBITRUM_TOKEN_ID,
522
521
  amount=need_to_move,
523
522
  strategy_name=self.name or "basis_trading_strategy",
524
523
  skip_ledger=True,
@@ -543,7 +542,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
543
542
 
544
543
  # Send USDC to bridge address (deposit credits the sender address on Hyperliquid)
545
544
  success, result = await self.balance_adapter.send_to_address(
546
- token_id=USDC_ARBITRUM_TOKEN_ID,
545
+ query=USDC_ARBITRUM_TOKEN_ID,
547
546
  amount=main_token_amount,
548
547
  from_wallet=strategy_wallet,
549
548
  to_address=HYPERLIQUID_BRIDGE_ADDRESS,
@@ -821,7 +820,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
821
820
  strategy_usdc = 0.0
822
821
  try:
823
822
  success, balance_data = await self.balance_adapter.get_balance(
824
- token_id=usdc_token_id,
823
+ query=usdc_token_id,
825
824
  wallet_address=address,
826
825
  )
827
826
  if success:
@@ -866,7 +865,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
866
865
  send_success,
867
866
  send_result,
868
867
  ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
869
- token_id=usdc_token_id,
868
+ query=usdc_token_id,
870
869
  amount=amount_to_send,
871
870
  strategy_name=self.name,
872
871
  skip_ledger=False,
@@ -1011,7 +1010,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1011
1010
  final_balance = 0.0
1012
1011
  try:
1013
1012
  success, balance_data = await self.balance_adapter.get_balance(
1014
- token_id=usdc_token_id,
1013
+ query=usdc_token_id,
1015
1014
  wallet_address=address,
1016
1015
  )
1017
1016
  if success:
@@ -1032,7 +1031,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
1032
1031
  send_success,
1033
1032
  send_result,
1034
1033
  ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
1035
- token_id=usdc_token_id,
1034
+ query=usdc_token_id,
1036
1035
  amount=amount_to_send,
1037
1036
  strategy_name=self.name,
1038
1037
  skip_ledger=False, # Record in ledger
@@ -2569,7 +2568,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2569
2568
  try:
2570
2569
  strategy_address = self._get_strategy_wallet_address()
2571
2570
  success, balance = await self.balance_adapter.get_balance(
2572
- token_id=USDC_ARBITRUM_TOKEN_ID,
2571
+ query=USDC_ARBITRUM_TOKEN_ID,
2573
2572
  wallet_address=strategy_address,
2574
2573
  )
2575
2574
  if success and balance:
@@ -197,9 +197,8 @@ class HyperlendStableYieldStrategy(Strategy):
197
197
  main_wallet: dict[str, Any] | None = None,
198
198
  strategy_wallet: dict[str, Any] | None = None,
199
199
  web3_service: Web3Service = None,
200
- api_key: str | None = None,
201
200
  ):
202
- super().__init__(api_key=api_key)
201
+ super().__init__()
203
202
  merged_config: dict[str, Any] = dict(config or {})
204
203
  if main_wallet is not None:
205
204
  merged_config["main_wallet"] = main_wallet
@@ -350,7 +349,7 @@ class HyperlendStableYieldStrategy(Strategy):
350
349
  success,
351
350
  main_usdt0_balance,
352
351
  ) = await self.balance_adapter.get_balance(
353
- token_id=self.usdt_token_info.get("token_id"),
352
+ query=self.usdt_token_info.get("token_id"),
354
353
  wallet_address=self._get_main_wallet_address(),
355
354
  )
356
355
  if not success:
@@ -363,7 +362,7 @@ class HyperlendStableYieldStrategy(Strategy):
363
362
  success,
364
363
  main_hype_balance,
365
364
  ) = await self.balance_adapter.get_balance(
366
- token_id=self.hype_token_info.get("token_id"),
365
+ query=self.hype_token_info.get("token_id"),
367
366
  wallet_address=self._get_main_wallet_address(),
368
367
  )
369
368
  if not success:
@@ -530,11 +529,10 @@ class HyperlendStableYieldStrategy(Strategy):
530
529
  return self._assets_snapshot
531
530
 
532
531
  _, snapshot = await self.hyperlend_adapter.get_assets_view(
533
- chain_id=self.hype_token_info.get("chain").get("id"),
534
532
  user_address=self._get_strategy_wallet_address(),
535
533
  )
536
534
 
537
- assets = snapshot.get("assets_view", {}).get("assets", [])
535
+ assets = snapshot.get("assets", [])
538
536
  asset_map = {}
539
537
 
540
538
  for asset in assets:
@@ -582,11 +580,9 @@ class HyperlendStableYieldStrategy(Strategy):
582
580
 
583
581
  try:
584
582
  _, data = await self.hyperlend_adapter.get_stable_markets(
585
- chain_id=self.hype_token_info.get("chain").get("id"),
586
583
  required_underlying_tokens=required_tokens,
587
584
  buffer_bps=self.SUPPLY_CAP_BUFFER_BPS,
588
585
  min_buffer_tokens=self.SUPPLY_CAP_MIN_BUFFER_TOKENS,
589
- is_stable_symbol=True,
590
586
  )
591
587
  markets = data.get("markets", {}) if isinstance(data, dict) else {}
592
588
  except Exception:
@@ -609,7 +605,7 @@ class HyperlendStableYieldStrategy(Strategy):
609
605
  async def _get_lent_positions(self, snapshot=None) -> dict[str, dict[str, Any]]:
610
606
  if not snapshot:
611
607
  snapshot = await self._get_assets_snapshot()
612
- assets = snapshot.get("assets_view", {}).get("assets", None)
608
+ assets = snapshot.get("assets", None)
613
609
 
614
610
  if not assets:
615
611
  return {}
@@ -630,7 +626,14 @@ class HyperlendStableYieldStrategy(Strategy):
630
626
  continue
631
627
 
632
628
  try:
633
- success, token = await self.token_adapter.get_token(checksum)
629
+ chain_id = None
630
+ try:
631
+ chain_id = int((self.hype_token_info.get("chain") or {}).get("id"))
632
+ except Exception:
633
+ chain_id = None
634
+ success, token = await self.token_adapter.get_token(
635
+ checksum, chain_id=chain_id
636
+ )
634
637
  if not success or not isinstance(token, dict):
635
638
  logger.info(f"Error getting token for asset: {asset}")
636
639
  continue
@@ -716,8 +719,8 @@ class HyperlendStableYieldStrategy(Strategy):
716
719
  result,
717
720
  tx_data,
718
721
  ) = await self.brap_adapter.swap_from_token_ids(
719
- from_token_id=from_token_id,
720
- to_token_id=to_token_id,
722
+ from_query=from_token_id,
723
+ to_query=to_token_id,
721
724
  from_address=strategy_address,
722
725
  amount=amount_wei_str,
723
726
  slippage=slippage,
@@ -862,7 +865,7 @@ class HyperlendStableYieldStrategy(Strategy):
862
865
 
863
866
  try:
864
867
  _, total_usdt_wei = await self.balance_adapter.get_balance(
865
- token_id=self.usdt_token_info.get("token_id"),
868
+ query=self.usdt_token_info.get("token_id"),
866
869
  wallet_address=self._get_strategy_wallet_address(),
867
870
  )
868
871
  except Exception:
@@ -891,7 +894,7 @@ class HyperlendStableYieldStrategy(Strategy):
891
894
 
892
895
  try:
893
896
  _, total_hype_wei = await self.balance_adapter.get_balance(
894
- token_id=self.hype_token_info.get("token_id"),
897
+ query=self.hype_token_info.get("token_id"),
895
898
  wallet_address=self._get_strategy_wallet_address(),
896
899
  )
897
900
  except Exception:
@@ -987,7 +990,7 @@ class HyperlendStableYieldStrategy(Strategy):
987
990
  success,
988
991
  message,
989
992
  ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
990
- token_id=token_info.get("token_id"),
993
+ query=token_info.get("token_id"),
991
994
  amount=amount_tokens,
992
995
  strategy_name=self.name,
993
996
  )
@@ -1549,11 +1552,9 @@ class HyperlendStableYieldStrategy(Strategy):
1549
1552
  )
1550
1553
 
1551
1554
  _, stable_markets = await self.hyperlend_adapter.get_stable_markets(
1552
- chain_id=self.hype_token_info.get("chain").get("id"),
1553
1555
  required_underlying_tokens=required_underlying_tokens,
1554
1556
  buffer_bps=self.SUPPLY_CAP_BUFFER_BPS,
1555
1557
  min_buffer_tokens=self.SUPPLY_CAP_MIN_BUFFER_TOKENS,
1556
- is_stable_symbol=True,
1557
1558
  )
1558
1559
  filtered_notes = stable_markets.get("notes", [])
1559
1560
  filtered_map = stable_markets.get("markets", {})
@@ -1585,8 +1586,7 @@ class HyperlendStableYieldStrategy(Strategy):
1585
1586
  if current_checksum_lower not in existing_addresses:
1586
1587
  try:
1587
1588
  _, current_entry = await self.hyperlend_adapter.get_market_entry(
1588
- chain_id=self.hype_token_info.get("chain").get("id"),
1589
- token_address=current_checksum_value,
1589
+ token=current_checksum_value,
1590
1590
  )
1591
1591
  except Exception:
1592
1592
  current_entry = None
@@ -1633,8 +1633,7 @@ class HyperlendStableYieldStrategy(Strategy):
1633
1633
  histories = await asyncio.gather(
1634
1634
  *[
1635
1635
  self.hyperlend_adapter.get_lend_rate_history(
1636
- chain_id=self.hype_token_info.get("chain").get("id"),
1637
- token_address=addr,
1636
+ token=addr,
1638
1637
  lookback_hours=lookback_hours,
1639
1638
  )
1640
1639
  for addr, _ in filtered
@@ -1656,7 +1655,7 @@ class HyperlendStableYieldStrategy(Strategy):
1656
1655
  if not history_status:
1657
1656
  continue
1658
1657
  history_data = history[1]
1659
- for row in history_data.get("rate_history", []):
1658
+ for row in history_data.get("history", []):
1660
1659
  ts_ms = row.get("timestamp_ms")
1661
1660
  if ts_ms is None:
1662
1661
  continue
@@ -1812,7 +1811,14 @@ class HyperlendStableYieldStrategy(Strategy):
1812
1811
  token = None
1813
1812
  if address:
1814
1813
  try:
1815
- success, token = await self.token_adapter.get_token(address.lower())
1814
+ chain_id = None
1815
+ try:
1816
+ chain_id = int((self.hype_token_info.get("chain") or {}).get("id"))
1817
+ except Exception:
1818
+ chain_id = None
1819
+ success, token = await self.token_adapter.get_token(
1820
+ address.lower(), chain_id=chain_id
1821
+ )
1816
1822
  except Exception:
1817
1823
  token = None
1818
1824
  if not success:
@@ -2128,7 +2134,16 @@ class HyperlendStableYieldStrategy(Strategy):
2128
2134
  continue
2129
2135
 
2130
2136
  try:
2131
- success, token = await self.token_adapter.get_token(checksum)
2137
+ chain_id = None
2138
+ try:
2139
+ chain_id = int(
2140
+ (self.hype_token_info.get("chain") or {}).get("id")
2141
+ )
2142
+ except Exception:
2143
+ chain_id = None
2144
+ success, token = await self.token_adapter.get_token(
2145
+ checksum, chain_id=chain_id
2146
+ )
2132
2147
  if not success or not isinstance(token, dict):
2133
2148
  continue
2134
2149
  except Exception:
@@ -2239,7 +2254,7 @@ class HyperlendStableYieldStrategy(Strategy):
2239
2254
  success,
2240
2255
  strategy_hype_balance_wei,
2241
2256
  ) = await self.balance_adapter.get_balance(
2242
- token_id=self.hype_token_info.get("token_id"),
2257
+ query=self.hype_token_info.get("token_id"),
2243
2258
  wallet_address=self._get_strategy_wallet_address(),
2244
2259
  )
2245
2260
  hype_price = asset_map.get(WRAPPED_HYPE_ADDRESS, {}).get("price_usd") or 0.0
@@ -50,7 +50,10 @@ def strategy():
50
50
 
51
51
  if hasattr(s, "balance_adapter") and s.balance_adapter:
52
52
  # Mock balances: 1000 USDT0 (with 6 decimals) and 2 HYPE (with 18 decimals)
53
- def get_balance_side_effect(token_id, wallet_address, **kwargs):
53
+ def get_balance_side_effect(query, wallet_address, **kwargs):
54
+ token_id = (
55
+ query if isinstance(query, str) else (query or {}).get("token_id")
56
+ )
54
57
  token_id_str = str(token_id).lower() if token_id else ""
55
58
  if "usdt0" in token_id_str or token_id_str == "usdt0":
56
59
  # 1000 USDT0 with 6 decimals = 1000 * 10^6 = 1000000000
@@ -209,7 +212,31 @@ def strategy():
209
212
 
210
213
  if hasattr(s, "hyperlend_adapter") and s.hyperlend_adapter:
211
214
  s.hyperlend_adapter.get_assets_view = AsyncMock(
212
- return_value=(True, {"assets_view": {"assets": []}})
215
+ return_value=(
216
+ True,
217
+ {
218
+ "block_number": 12345,
219
+ "user": "0x0",
220
+ "native_balance_wei": 0,
221
+ "native_balance": 0.0,
222
+ "assets": [],
223
+ "account_data": {
224
+ "total_collateral_base": 0,
225
+ "total_debt_base": 0,
226
+ "available_borrows_base": 0,
227
+ "current_liquidation_threshold": 0,
228
+ "ltv": 0,
229
+ "health_factor_wad": 0,
230
+ "health_factor": 0.0,
231
+ },
232
+ "base_currency_info": {
233
+ "marketReferenceCurrencyUnit": 100000000,
234
+ "marketReferenceCurrencyPriceInUsd": 100000000,
235
+ "networkBaseTokenPriceInUsd": 0,
236
+ "networkBaseTokenPriceDecimals": 8,
237
+ },
238
+ },
239
+ )
213
240
  )
214
241
  s.hyperlend_adapter.get_stable_markets = AsyncMock(
215
242
  return_value=(
@@ -232,14 +259,24 @@ def strategy():
232
259
  },
233
260
  )
234
261
  )
262
+ # Block bootstrap needs at least BLOCK_LEN (6) rows; provide enough history
263
+ _history_base = {
264
+ "timestamp_ms": 1700000000000,
265
+ "timestamp": 1700000000.0,
266
+ "supply_apr": 0.05,
267
+ "supply_apy": 0.05,
268
+ "borrow_apr": 0.07,
269
+ "borrow_apy": 0.07,
270
+ "token": "0x1234567890123456789012345678901234567890",
271
+ "symbol": "usdt0",
272
+ "display_symbol": "USDT0",
273
+ }
274
+ history_rows = [
275
+ {**_history_base, "timestamp_ms": 1700000000000 + i * 3600000}
276
+ for i in range(24)
277
+ ]
235
278
  s.hyperlend_adapter.get_lend_rate_history = AsyncMock(
236
- return_value=(
237
- True,
238
- {
239
- "rates": [{"rate": 5.0, "timestamp": 1700000000}],
240
- "avg_rate": 5.0,
241
- },
242
- )
279
+ return_value=(True, {"history": history_rows})
243
280
  )
244
281
 
245
282
  s.usdt_token_info = {
@@ -259,6 +296,14 @@ def strategy():
259
296
  "chain": {"code": "hyperevm", "id": 9999, "name": "HyperEVM"},
260
297
  }
261
298
  s.current_token = None
299
+ # Attributes normally set in setup()
300
+ s.rotation_policy = "hysteresis"
301
+ s.hys_dwell_hours = 168
302
+ s.hys_z = 1.15
303
+ s.rotation_tx_cost = 0.002
304
+ s.last_summary = None
305
+ s.last_dominance = None
306
+ s.last_samples = None
262
307
 
263
308
  if hasattr(s, "token_adapter") and s.token_adapter:
264
309
  if not hasattr(s.token_adapter, "get_token_price"):
@@ -508,7 +508,7 @@ class MoonwellWstethLoopStrategy(Strategy):
508
508
  if self.balance_adapter is None:
509
509
  return 0
510
510
  success, raw = await self.balance_adapter.get_balance(
511
- token_id=token_id,
511
+ query=token_id,
512
512
  wallet_address=wallet_address,
513
513
  )
514
514
  return self._parse_balance(raw) if success else 0
@@ -535,7 +535,7 @@ class MoonwellWstethLoopStrategy(Strategy):
535
535
 
536
536
  try:
537
537
  ok, bal = await self.balance_adapter.get_balance(
538
- token_id=token_id,
538
+ query=token_id,
539
539
  wallet_address=wallet_address,
540
540
  )
541
541
  return int(bal) if ok else 0
@@ -1719,7 +1719,7 @@ class MoonwellWstethLoopStrategy(Strategy):
1719
1719
 
1720
1720
  # Get actual wstETH balance
1721
1721
  wsteth_success, wsteth_bal_raw = await self.balance_adapter.get_balance(
1722
- token_id=WSTETH_TOKEN_ID, wallet_address=self._get_strategy_wallet_address()
1722
+ query=WSTETH_TOKEN_ID, wallet_address=self._get_strategy_wallet_address()
1723
1723
  )
1724
1724
  if not wsteth_success:
1725
1725
  raise Exception("Failed to get wstETH balance after swap")
@@ -523,7 +523,8 @@ async def test_atomic_deposit_iteration_swaps_from_eth_when_borrow_surfaces_as_e
523
523
  WSTETH_TOKEN_ID: 0,
524
524
  }
525
525
 
526
- async def get_balance_side_effect(*, token_id: str, wallet_address: str, **_):
526
+ async def get_balance_side_effect(*, query: str, wallet_address: str, **_):
527
+ token_id = query if isinstance(query, str) else (query or {}).get("token_id")
527
528
  return (True, balances.get(token_id, 0))
528
529
 
529
530
  strategy.balance_adapter.get_balance = AsyncMock(
@@ -587,7 +588,8 @@ async def test_complete_unpaired_weth_borrow_uses_eth_inventory(
587
588
  WSTETH_TOKEN_ID: 0,
588
589
  }
589
590
 
590
- async def get_balance_side_effect(*, token_id: str, wallet_address: str):
591
+ async def get_balance_side_effect(*, query: str, wallet_address: str):
592
+ token_id = query if isinstance(query, str) else (query or {}).get("token_id")
591
593
  return (True, balances.get(token_id, 0))
592
594
 
593
595
  strategy.balance_adapter.get_balance = AsyncMock(
@@ -631,8 +633,10 @@ async def test_sweep_token_balances_sweeps_tokens(strategy, mock_adapter_respons
631
633
  strategy.min_withdraw_usd = 1.0
632
634
 
633
635
  # Mock balance returns (has some WETH dust)
634
- def balance_side_effect(token_id, wallet_address):
635
- if "weth" in token_id.lower():
636
+ def balance_side_effect(*, query, wallet_address, **_):
637
+ token_id = query if isinstance(query, str) else (query or {}).get("token_id")
638
+ token_id_str = (token_id or "").lower()
639
+ if "weth" in token_id_str:
636
640
  return (True, 100 * 10**18) # 100 WETH
637
641
  return (True, 0)
638
642
 
@@ -751,7 +755,8 @@ async def test_partial_liquidate_prefers_wsteth_when_excess(strategy):
751
755
  # Wallet balances (raw)
752
756
  balances: dict[str, int] = {USDC_TOKEN_ID: 0, WSTETH_TOKEN_ID: 0}
753
757
 
754
- async def mock_get_balance(*, token_id: str, wallet_address: str):
758
+ async def mock_get_balance(*, query: str, wallet_address: str):
759
+ token_id = query if isinstance(query, str) else (query or {}).get("token_id")
755
760
  return (True, balances.get(token_id, 0))
756
761
 
757
762
  strategy.balance_adapter.get_balance = AsyncMock(side_effect=mock_get_balance)
@@ -839,7 +844,8 @@ async def test_partial_liquidate_uses_usdc_collateral_when_no_wsteth_excess(stra
839
844
 
840
845
  balances: dict[str, int] = {USDC_TOKEN_ID: 0}
841
846
 
842
- async def mock_get_balance(*, token_id: str, wallet_address: str):
847
+ async def mock_get_balance(*, query: str, wallet_address: str):
848
+ token_id = query if isinstance(query, str) else (query or {}).get("token_id")
843
849
  return (True, balances.get(token_id, 0))
844
850
 
845
851
  strategy.balance_adapter.get_balance = AsyncMock(side_effect=mock_get_balance)
@@ -62,7 +62,7 @@ Transactions are scoped to the strategy wallet and Enso Router approval/swap cal
62
62
  ### Withdraw
63
63
 
64
64
  - Requires a prior deposit (the strategy tracks `self.DEPOSIT_USDC`).
65
- - Reads the pool balance via `BalanceAdapter.get_pool_balance`, unwinds via BRAP swaps back to USDC, and moves USDC from the strategy wallet to the main wallet via `BalanceAdapter.move_from_strategy_wallet_to_main_wallet`.
65
+ - Reads the pool balance via `BalanceAdapter.get_balance` (with pool address and chain_id), unwinds via BRAP swaps back to USDC, and moves USDC from the strategy wallet to the main wallet via `BalanceAdapter.move_from_strategy_wallet_to_main_wallet`.
66
66
  - Updates the ledger and clears cached pool state.
67
67
 
68
68
  ## Running locally