wayfinder-paths 0.1.22__py3-none-any.whl → 0.1.23__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 (63) 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 +65 -169
  4. wayfinder_paths/adapters/balance_adapter/test_adapter.py +41 -113
  5. wayfinder_paths/adapters/brap_adapter/README.md +22 -75
  6. wayfinder_paths/adapters/brap_adapter/adapter.py +187 -576
  7. wayfinder_paths/adapters/brap_adapter/examples.json +21 -140
  8. wayfinder_paths/adapters/brap_adapter/test_adapter.py +6 -234
  9. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +39 -86
  10. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +5 -1
  11. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +6 -5
  12. wayfinder_paths/adapters/ledger_adapter/README.md +4 -1
  13. wayfinder_paths/adapters/ledger_adapter/adapter.py +3 -3
  14. wayfinder_paths/adapters/moonwell_adapter/adapter.py +108 -198
  15. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +37 -23
  16. wayfinder_paths/adapters/token_adapter/adapter.py +14 -0
  17. wayfinder_paths/core/__init__.py +0 -3
  18. wayfinder_paths/core/clients/BRAPClient.py +3 -0
  19. wayfinder_paths/core/clients/ClientManager.py +0 -7
  20. wayfinder_paths/core/clients/LedgerClient.py +196 -172
  21. wayfinder_paths/core/clients/WayfinderClient.py +0 -1
  22. wayfinder_paths/core/clients/__init__.py +0 -5
  23. wayfinder_paths/core/clients/protocols.py +0 -13
  24. wayfinder_paths/core/config.py +0 -164
  25. wayfinder_paths/core/constants/__init__.py +58 -2
  26. wayfinder_paths/core/constants/base.py +8 -22
  27. wayfinder_paths/core/constants/chains.py +36 -0
  28. wayfinder_paths/core/constants/contracts.py +39 -0
  29. wayfinder_paths/core/constants/tokens.py +9 -0
  30. wayfinder_paths/core/strategies/Strategy.py +0 -10
  31. wayfinder_paths/core/utils/evm_helpers.py +5 -15
  32. wayfinder_paths/core/utils/tokens.py +28 -0
  33. wayfinder_paths/core/utils/transaction.py +13 -7
  34. wayfinder_paths/core/utils/web3.py +5 -3
  35. wayfinder_paths/policies/enso.py +1 -2
  36. wayfinder_paths/policies/hyper_evm.py +6 -3
  37. wayfinder_paths/policies/hyperlend.py +1 -2
  38. wayfinder_paths/policies/moonwell.py +12 -7
  39. wayfinder_paths/policies/prjx.py +1 -3
  40. wayfinder_paths/run_strategy.py +97 -300
  41. wayfinder_paths/strategies/basis_trading_strategy/constants.py +3 -1
  42. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +19 -14
  43. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +12 -11
  44. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +20 -33
  45. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +21 -18
  46. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +69 -130
  47. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +32 -42
  48. {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.23.dist-info}/METADATA +2 -3
  49. {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.23.dist-info}/RECORD +51 -60
  50. {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.23.dist-info}/WHEEL +1 -1
  51. wayfinder_paths/core/clients/WalletClient.py +0 -41
  52. wayfinder_paths/core/engine/StrategyJob.py +0 -110
  53. wayfinder_paths/core/services/test_local_evm_txn.py +0 -145
  54. wayfinder_paths/templates/adapter/README.md +0 -150
  55. wayfinder_paths/templates/adapter/adapter.py +0 -16
  56. wayfinder_paths/templates/adapter/examples.json +0 -8
  57. wayfinder_paths/templates/adapter/test_adapter.py +0 -30
  58. wayfinder_paths/templates/strategy/README.md +0 -186
  59. wayfinder_paths/templates/strategy/examples.json +0 -11
  60. wayfinder_paths/templates/strategy/strategy.py +0 -35
  61. wayfinder_paths/templates/strategy/test_strategy.py +0 -166
  62. wayfinder_paths/tests/test_smoke_manifest.py +0 -63
  63. {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.23.dist-info}/LICENSE +0 -0
@@ -2,345 +2,142 @@
2
2
 
3
3
  import argparse
4
4
  import asyncio
5
+ import importlib
6
+ import inspect
5
7
  import json
6
8
  import sys
7
- from pathlib import Path
9
+ from typing import Any
8
10
 
9
11
  from loguru import logger
10
12
 
11
- from wayfinder_paths.core.config import StrategyJobConfig
12
- from wayfinder_paths.core.engine.StrategyJob import StrategyJob
13
+ from wayfinder_paths.core.config import CONFIG
14
+ from wayfinder_paths.core.strategies.Strategy import Strategy
13
15
  from wayfinder_paths.core.utils.evm_helpers import resolve_private_key_for_from_address
14
16
  from wayfinder_paths.core.utils.web3 import get_transaction_chain_id, web3_from_chain_id
15
17
 
16
18
 
17
- def load_strategy(
18
- strategy_name: str,
19
- *,
20
- config: StrategyJobConfig,
21
- ):
22
- strategies_dir = Path(__file__).parent / "strategies"
23
- strategy_dir = strategies_dir / strategy_name
19
+ def get_strategy_config(strategy_name: str) -> dict[str, Any]:
20
+ config = dict(CONFIG.get("strategy", {}))
21
+ wallets = {w["label"]: w for w in CONFIG.get("wallets", [])}
24
22
 
25
- if not strategy_dir.exists():
26
- # List available strategies for better error message
27
- available = []
28
- if strategies_dir.exists():
29
- for path in strategies_dir.iterdir():
30
- if path.is_dir() and (path / "strategy.py").exists():
31
- available.append(path.name)
32
- available_str = ", ".join(available) if available else "none"
33
- raise ValueError(
34
- f"Unknown strategy: {strategy_name}. Available strategies: {available_str}"
35
- )
36
-
37
- module_path = f"strategies.{strategy_name}.strategy"
38
- module = __import__(module_path, fromlist=[""])
39
-
40
- # Find the Strategy subclass in the module
41
- from wayfinder_paths.core.strategies.Strategy import Strategy
42
-
43
- strategy_class = None
44
- for attr_name in dir(module):
45
- attr = getattr(module, attr_name)
46
- if (
47
- isinstance(attr, type)
48
- and issubclass(attr, Strategy)
49
- and attr is not Strategy
50
- ):
51
- strategy_class = attr
52
- break
23
+ if "main_wallet" not in config and "main" in wallets:
24
+ config["main_wallet"] = {"address": wallets["main"]["address"]}
25
+ if "strategy_wallet" not in config and strategy_name in wallets:
26
+ config["strategy_wallet"] = {"address": wallets[strategy_name]["address"]}
53
27
 
54
- if strategy_class is None:
55
- raise ValueError(f"No Strategy class found in {module_path}")
28
+ by_addr = {w["address"].lower(): w for w in CONFIG.get("wallets", [])}
29
+ for key in ("main_wallet", "strategy_wallet"):
30
+ if wallet := config.get(key):
31
+ if entry := by_addr.get(wallet.get("address", "").lower()):
32
+ if pk := entry.get("private_key") or entry.get("private_key_hex"):
33
+ wallet["private_key_hex"] = pk
34
+ return config
56
35
 
57
- main_wallet = config.strategy_config.get("main_wallet") or {}
58
- strategy_wallet = config.strategy_config.get("strategy_wallet") or {}
59
- main_wallet_address = main_wallet.get("address")
60
- strategy_wallet_address = strategy_wallet.get("address")
61
36
 
62
- async def main_wallet_signing_callback(transaction):
63
- private_key = resolve_private_key_for_from_address(
64
- main_wallet_address, config.strategy_config
65
- )
37
+ def create_signing_callback(address: str, config: dict[str, Any]):
38
+ async def sign(transaction: dict) -> str:
39
+ pk = resolve_private_key_for_from_address(address, config)
66
40
  async with web3_from_chain_id(get_transaction_chain_id(transaction)) as web3:
67
- signed = web3.eth.account.sign_transaction(transaction, private_key)
68
- return signed.raw_transaction.hex()
69
-
70
- async def strategy_wallet_signing_callback(transaction):
71
- private_key = resolve_private_key_for_from_address(
72
- strategy_wallet_address,
73
- config.strategy_config,
74
- )
75
- async with web3_from_chain_id(get_transaction_chain_id(transaction)) as web3:
76
- signed = web3.eth.account.sign_transaction(transaction, private_key)
77
- return signed.raw_transaction.hex()
78
-
79
- return strategy_class(
80
- config=config.strategy_config,
81
- main_wallet_signing_callback=main_wallet_signing_callback,
82
- strategy_wallet_signing_callback=strategy_wallet_signing_callback,
83
- )
84
-
85
-
86
- def load_config(
87
- config_path: str | None = None, strategy_name: str | None = None
88
- ) -> StrategyJobConfig:
89
- # Default to config.json if not provided
90
- if not config_path:
91
- config_path = "config.json"
41
+ return web3.eth.account.sign_transaction(
42
+ transaction, pk
43
+ ).raw_transaction.hex()
92
44
 
93
- config_file = Path(config_path)
94
- if not config_file.exists():
95
- raise FileNotFoundError(
96
- f"Config file not found: {config_path}. "
97
- "Please create config.json (see wayfinder_paths/config.example.json for template)"
98
- )
99
-
100
- logger.info(f"Loading config from {config_path}")
101
- with open(config_file) as f:
102
- config_data = json.load(f)
45
+ return sign
103
46
 
104
- config = StrategyJobConfig.from_dict(config_data, strategy_name=strategy_name)
105
- if strategy_name:
106
- config.strategy_config["_strategy_name"] = strategy_name
107
- config.__post_init__()
108
- return config
109
47
 
48
+ def find_strategy_class(module) -> type[Strategy]:
49
+ for _, obj in inspect.getmembers(module, inspect.isclass):
50
+ if issubclass(obj, Strategy) and obj is not Strategy:
51
+ return obj
52
+ raise ValueError(f"No Strategy subclass found in {module.__name__}")
110
53
 
111
- async def run_strategy(
112
- strategy_name: str | None = None,
113
- config_path: str | None = None,
114
- action: str = "run",
115
- **kwargs,
116
- ):
117
- try:
118
- if not strategy_name:
119
- raise ValueError("strategy_name is required")
120
54
 
121
- logger.debug(f"Loading strategy by name: {strategy_name}")
55
+ async def run_strategy(strategy_name: str, action: str = "status", **kw):
56
+ config = get_strategy_config(strategy_name)
122
57
 
123
- logger.debug(f"Config path provided: {config_path}")
124
- config = load_config(config_path, strategy_name=strategy_name)
125
- main_wallet_cfg = config.strategy_config.get("main_wallet") or {}
126
- strategy_wallet_cfg = config.strategy_config.get("strategy_wallet") or {}
127
- logger.debug(
128
- "Loaded config: wallets(main={} strategy={})",
129
- main_wallet_cfg.get("address") or "none",
130
- strategy_wallet_cfg.get("address") or "none",
131
- )
58
+ def signing_cb(key: str):
59
+ if addr := config.get(key, {}).get("address"):
60
+ return create_signing_callback(addr, config)
61
+ return None
132
62
 
133
- # Authentication is via system.api_key in config.json
134
-
135
- strategy = load_strategy(
136
- strategy_name,
137
- config=config,
63
+ module = importlib.import_module(
64
+ f"wayfinder_paths.strategies.{strategy_name}.strategy"
65
+ )
66
+ strategy_cls = find_strategy_class(module)
67
+ strategy = strategy_cls(
68
+ config,
69
+ main_wallet_signing_callback=signing_cb("main_wallet"),
70
+ strategy_wallet_signing_callback=signing_cb("strategy_wallet"),
71
+ )
72
+ await strategy.setup()
73
+
74
+ if action == "policy":
75
+ policies = strategy.policies() if hasattr(strategy, "policies") else []
76
+ if wallet_id := kw.get("wallet_id"):
77
+ policies = [p.replace("FORMAT_WALLET_ID", wallet_id) for p in policies]
78
+ result = {"policies": policies}
79
+ elif action == "status":
80
+ result = await strategy.status()
81
+ elif action == "deposit":
82
+ result = await strategy.deposit(
83
+ main_token_amount=kw.get("main_token_amount", 0.0),
84
+ gas_token_amount=kw.get("gas_token_amount", 0.0),
138
85
  )
139
- logger.info(f"Loaded strategy: {strategy.name}")
140
-
141
- strategy_job = StrategyJob(strategy, config)
142
-
143
- logger.info("Setting up strategy job...")
144
- logger.debug("Auth mode: API key (from system.api_key)")
145
- await strategy_job.setup()
146
-
147
- # Execute action
148
- if action == "run":
149
- logger.info("Starting continuous execution...")
150
- await strategy_job.run_continuous(interval_seconds=kwargs.get("interval"))
151
-
152
- elif action == "deposit":
153
- main_token_amount = kwargs.get("main_token_amount")
154
- gas_token_amount = kwargs.get("gas_token_amount")
155
-
156
- if main_token_amount is None and gas_token_amount is None:
157
- raise ValueError(
158
- "Either main token amount or gas token amount required for deposit (use --main-token-amount and/or --gas-token-amount)"
159
- )
160
-
161
- # Default to 0.0 if not provided
162
- if main_token_amount is None:
163
- main_token_amount = 0.0
164
- if gas_token_amount is None:
165
- gas_token_amount = 0.0
166
-
167
- result = await strategy_job.execute_strategy(
168
- "deposit",
169
- main_token_amount=main_token_amount,
170
- gas_token_amount=gas_token_amount,
171
- )
172
- logger.info(f"Deposit result: {result}")
173
-
174
- elif action == "withdraw":
175
- amount = kwargs.get("amount")
176
- result = await strategy_job.execute_strategy("withdraw", amount=amount)
177
- logger.info(f"Withdraw result: {result}")
178
-
179
- elif action == "status":
180
- result = await strategy_job.execute_strategy("status")
181
- logger.info(f"Status: {json.dumps(result, indent=2)}")
182
-
183
- elif action == "update":
184
- result = await strategy_job.execute_strategy("update")
185
- logger.info(f"Update result: {result}")
186
-
187
- elif action == "exit":
188
- result = await strategy_job.execute_strategy("exit")
189
- logger.info(f"Exit result: {result}")
190
-
191
- elif action == "partial-liquidate":
192
- usd_value = kwargs.get("amount")
193
- if not usd_value:
194
- raise ValueError("Amount (USD value) required for partial-liquidate")
195
- result = await strategy_job.execute_strategy(
196
- "partial_liquidate", usd_value=usd_value
197
- )
198
- logger.info(f"Partial liquidation result: {result}")
199
-
200
- elif action == "policy":
201
- policies: list[str] = []
202
-
86
+ elif action == "withdraw":
87
+ result = await strategy.withdraw(amount=kw.get("amount"))
88
+ elif action == "update":
89
+ result = await strategy.update()
90
+ elif action == "exit":
91
+ result = await strategy.exit()
92
+ elif action == "run":
93
+ while True:
203
94
  try:
204
- spols = getattr(strategy, "policies", None)
205
- if callable(spols):
206
- result = spols() # type: ignore[misc]
207
- if isinstance(result, list) and result:
208
- policies = [p for p in result if isinstance(p, str)]
209
- except Exception:
210
- pass
211
-
212
- seen = set()
213
- deduped: list[str] = []
214
- for p in policies:
215
- if p not in seen:
216
- seen.add(p)
217
- deduped.append(p)
218
-
219
- wallet_id = kwargs.get("wallet_id")
220
- if not wallet_id:
221
- wallet_id = config.strategy_config.get("wallet_id")
222
- if not wallet_id:
223
- wallet_id = config.system.wallet_id
224
-
225
- # Render policies with wallet_id if available
226
- if wallet_id:
227
- rendered = [
228
- p.replace("FORMAT_WALLET_ID", str(wallet_id)) for p in deduped
229
- ]
230
- else:
231
- rendered = deduped
232
- logger.info(
233
- "Policy rendering without wallet_id - policies contain FORMAT_WALLET_ID placeholder"
234
- )
235
-
236
- logger.info(json.dumps({"policies": rendered}, indent=2))
237
-
238
- elif action == "script":
239
- duration = kwargs.get("duration") or 300
240
- logger.info(f"Running script mode for {duration}s...")
241
- task = asyncio.create_task(
242
- strategy_job.run_continuous(
243
- interval_seconds=kwargs.get("interval") or 60
244
- )
245
- )
246
- await asyncio.sleep(duration)
247
- task.cancel()
248
- try:
249
- await task
95
+ result = await strategy.update()
96
+ logger.info(f"Update: {result}")
97
+ await asyncio.sleep(kw.get("interval", 60))
250
98
  except asyncio.CancelledError:
251
- logger.info("Script mode execution completed")
252
-
253
- else:
254
- raise ValueError(f"Unknown action: {action}")
255
-
256
- except KeyboardInterrupt:
257
- logger.info("Shutting down...")
258
- except Exception as e:
259
- logger.error(f"Error: {e}")
260
- sys.exit(1)
261
- finally:
262
- if "strategy_job" in locals():
263
- await strategy_job.stop()
99
+ result = (True, "stopped")
100
+ break
101
+ else:
102
+ raise ValueError(f"Unknown action: {action}")
103
+
104
+ print(
105
+ json.dumps(result, indent=2)
106
+ if isinstance(result, dict)
107
+ else f"{action}: {result}"
108
+ )
264
109
 
265
110
 
266
111
  def main():
267
- parser = argparse.ArgumentParser(description="Run strategy strategies")
268
- parser.add_argument(
269
- "strategy",
270
- help="Strategy to run (stablecoin_yield_strategy)",
271
- )
272
- parser.add_argument(
273
- "--config", help="Path to config file (defaults to config.json)"
274
- )
275
- parser.add_argument(
112
+ p = argparse.ArgumentParser()
113
+ p.add_argument("strategy")
114
+ p.add_argument(
276
115
  "--action",
277
- default="run",
278
- choices=[
279
- "run",
280
- "deposit",
281
- "withdraw",
282
- "status",
283
- "update",
284
- "exit",
285
- "policy",
286
- "script",
287
- "partial-liquidate",
288
- ],
289
- help="Action to perform (default: run)",
116
+ default="status",
117
+ choices=["run", "deposit", "withdraw", "status", "update", "exit", "policy"],
290
118
  )
291
- parser.add_argument(
292
- "--amount",
293
- type=float,
294
- help="Amount for withdraw/partial-liquidate actions",
119
+ p.add_argument("--amount", type=float)
120
+ p.add_argument("--main-token-amount", type=float, dest="main_token_amount")
121
+ p.add_argument(
122
+ "--gas-token-amount", type=float, dest="gas_token_amount", default=0.0
295
123
  )
296
- parser.add_argument(
297
- "--main-token-amount",
298
- "--main_token_amount",
299
- type=float,
300
- dest="main_token_amount",
301
- help="Main token amount for deposit action",
302
- )
303
- parser.add_argument(
304
- "--gas-token-amount",
305
- "--gas_token_amount",
306
- type=float,
307
- dest="gas_token_amount",
308
- default=0.0,
309
- help="Gas token amount for deposit action (default: 0.0)",
310
- )
311
- parser.add_argument(
312
- "--interval",
313
- type=int,
314
- help="Update interval in seconds for continuous/script modes",
315
- )
316
- parser.add_argument(
317
- "--duration", type=int, help="Duration in seconds for script action"
318
- )
319
- parser.add_argument("--debug", action="store_true", help="Enable debug logging")
320
- parser.add_argument(
321
- "--wallet-id",
322
- help="Wallet ID for policy rendering (replaces FORMAT_WALLET_ID in policies)",
323
- )
324
-
325
- args = parser.parse_args()
124
+ p.add_argument("--interval", type=int, default=60)
125
+ p.add_argument("--wallet-id", dest="wallet_id")
126
+ p.add_argument("--debug", action="store_true")
127
+ args = p.parse_args()
326
128
 
327
- # Configure logging
328
- log_level = "DEBUG" if args.debug else "INFO"
329
129
  logger.remove()
330
- logger.add(sys.stderr, level=log_level)
130
+ logger.add(sys.stderr, level="DEBUG" if args.debug else "INFO")
331
131
 
332
- # Run strategy
333
132
  asyncio.run(
334
133
  run_strategy(
335
- strategy_name=args.strategy,
336
- config_path=args.config,
337
- action=args.action,
134
+ args.strategy,
135
+ args.action,
338
136
  amount=args.amount,
339
137
  main_token_amount=args.main_token_amount,
340
138
  gas_token_amount=args.gas_token_amount,
341
139
  interval=args.interval,
342
- duration=args.duration,
343
- wallet_id=getattr(args, "wallet_id", None),
140
+ wallet_id=args.wallet_id,
344
141
  )
345
142
  )
346
143
 
@@ -1 +1,3 @@
1
- USDC_ARBITRUM_TOKEN_ID = "usd-coin-arbitrum"
1
+ from wayfinder_paths.core.constants.tokens import TOKEN_ID_USDC_ARBITRUM
2
+
3
+ USDC_ARBITRUM_TOKEN_ID = TOKEN_ID_USDC_ARBITRUM
@@ -446,7 +446,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
446
446
  strategy_balance_ok,
447
447
  strategy_balance,
448
448
  ) = await self.balance_adapter.get_balance(
449
- query=USDC_ARBITRUM_TOKEN_ID,
449
+ token_id=USDC_ARBITRUM_TOKEN_ID,
450
450
  wallet_address=strategy_address,
451
451
  )
452
452
  strategy_usdc = 0.0
@@ -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:
@@ -759,7 +759,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
759
759
  strategy_usdc = 0.0
760
760
  try:
761
761
  success, balance_data = await self.balance_adapter.get_balance(
762
- query=usdc_token_id,
762
+ token_id=usdc_token_id,
763
763
  wallet_address=address,
764
764
  )
765
765
  if success:
@@ -921,7 +921,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
921
921
  final_balance = 0.0
922
922
  try:
923
923
  success, balance_data = await self.balance_adapter.get_balance(
924
- query=usdc_token_id,
924
+ token_id=usdc_token_id,
925
925
  wallet_address=address,
926
926
  )
927
927
  if success:
@@ -962,7 +962,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
962
962
  success,
963
963
  msg,
964
964
  ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
965
- query=USDC_ARBITRUM_TOKEN_ID,
965
+ token_id=USDC_ARBITRUM_TOKEN_ID,
966
966
  amount=usdc_balance,
967
967
  strategy_name=self.name,
968
968
  skip_ledger=False,
@@ -989,7 +989,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
989
989
  success,
990
990
  msg,
991
991
  ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
992
- query="ethereum-arbitrum",
992
+ token_id="ethereum-arbitrum",
993
993
  amount=transferable_eth,
994
994
  strategy_name=self.name,
995
995
  skip_ledger=False,
@@ -2249,17 +2249,22 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2249
2249
  if not success or not transactions:
2250
2250
  return None
2251
2251
 
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", {})
2252
+ tx_list = (
2253
+ transactions.get("transactions", [])
2254
+ if isinstance(transactions, dict)
2255
+ else []
2256
+ )
2257
+ for txn in tx_list:
2258
+ op_data = txn.get("op_data", {})
2256
2259
  if (
2257
2260
  op_data.get("type") == "HYPE_SPOT"
2258
2261
  and op_data.get("buy_or_sell") == "buy"
2259
2262
  ):
2260
- created_at = txn.get("created_at")
2261
- if created_at:
2262
- return datetime.fromisoformat(created_at.replace("Z", "+00:00"))
2263
+ created_str = txn.get("created")
2264
+ if created_str:
2265
+ return datetime.fromisoformat(
2266
+ str(created_str).replace("Z", "+00:00")
2267
+ )
2263
2268
 
2264
2269
  return None
2265
2270
  except Exception as e:
@@ -2339,7 +2344,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
2339
2344
  try:
2340
2345
  strategy_address = self._get_strategy_wallet_address()
2341
2346
  success, balance = await self.balance_adapter.get_balance(
2342
- query=USDC_ARBITRUM_TOKEN_ID,
2347
+ token_id=USDC_ARBITRUM_TOKEN_ID,
2343
2348
  wallet_address=strategy_address,
2344
2349
  )
2345
2350
  if success and balance:
@@ -18,6 +18,7 @@ from wayfinder_paths.adapters.hyperlend_adapter.adapter import HyperlendAdapter
18
18
  from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
19
19
  from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
20
20
  from wayfinder_paths.core.constants.base import DEFAULT_SLIPPAGE
21
+ from wayfinder_paths.core.constants.contracts import HYPEREVM_WHYPE
21
22
  from wayfinder_paths.core.strategies.descriptors import (
22
23
  Complexity,
23
24
  Directionality,
@@ -50,7 +51,7 @@ SYMBOL_TRANSLATION_TABLE = str.maketrans(
50
51
  "Ξ": "X",
51
52
  }
52
53
  )
53
- WRAPPED_HYPE_ADDRESS = "0x5555555555555555555555555555555555555555"
54
+ WRAPPED_HYPE_ADDRESS = HYPEREVM_WHYPE
54
55
 
55
56
 
56
57
  class HyperlendStableYieldStrategy(Strategy):
@@ -338,7 +339,7 @@ class HyperlendStableYieldStrategy(Strategy):
338
339
  success,
339
340
  main_usdt0_balance,
340
341
  ) = await self.balance_adapter.get_balance(
341
- query=self.usdt_token_info.get("token_id"),
342
+ token_id=self.usdt_token_info.get("token_id"),
342
343
  wallet_address=self._get_main_wallet_address(),
343
344
  )
344
345
  if not success:
@@ -351,7 +352,7 @@ class HyperlendStableYieldStrategy(Strategy):
351
352
  success,
352
353
  main_hype_balance,
353
354
  ) = await self.balance_adapter.get_balance(
354
- query=self.hype_token_info.get("token_id"),
355
+ token_id=self.hype_token_info.get("token_id"),
355
356
  wallet_address=self._get_main_wallet_address(),
356
357
  )
357
358
  if not success:
@@ -839,7 +840,7 @@ class HyperlendStableYieldStrategy(Strategy):
839
840
  total_usdt = 0.0
840
841
  try:
841
842
  _, total_usdt_wei = await self.balance_adapter.get_balance(
842
- query=self.usdt_token_info.get("token_id"),
843
+ token_id=self.usdt_token_info.get("token_id"),
843
844
  wallet_address=self._get_strategy_wallet_address(),
844
845
  )
845
846
  if total_usdt_wei and total_usdt_wei > 0:
@@ -852,7 +853,7 @@ class HyperlendStableYieldStrategy(Strategy):
852
853
  total_hype = 0.0
853
854
  try:
854
855
  _, total_hype_wei = await self.balance_adapter.get_balance(
855
- query=self.hype_token_info.get("token_id"),
856
+ token_id=self.hype_token_info.get("token_id"),
856
857
  wallet_address=self._get_strategy_wallet_address(),
857
858
  )
858
859
  if total_hype_wei and total_hype_wei > 0:
@@ -908,7 +909,7 @@ class HyperlendStableYieldStrategy(Strategy):
908
909
  wallet_address=strategy_address,
909
910
  )
910
911
  if usdt_ok and usdt_raw:
911
- usdt_balance = float(usdt_raw.get("balance", 0))
912
+ usdt_balance = float(usdt_raw) / 1e6 # USDT has 6 decimals
912
913
  if usdt_balance > 1.0:
913
914
  self.logger.info(
914
915
  f"Transferring {usdt_balance:.2f} USDT0 to main wallet"
@@ -917,7 +918,7 @@ class HyperlendStableYieldStrategy(Strategy):
917
918
  success,
918
919
  msg,
919
920
  ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
920
- query="usdt0-hyperevm",
921
+ token_id="usdt0-hyperevm",
921
922
  amount=usdt_balance,
922
923
  strategy_name=self.name,
923
924
  skip_ledger=False,
@@ -933,7 +934,7 @@ class HyperlendStableYieldStrategy(Strategy):
933
934
  wallet_address=strategy_address,
934
935
  )
935
936
  if hype_ok and hype_raw:
936
- hype_balance = float(hype_raw.get("balance", 0))
937
+ hype_balance = float(hype_raw) / 1e18 # HYPE has 18 decimals
937
938
  tx_fee_reserve = 0.1
938
939
  transferable_hype = hype_balance - tx_fee_reserve
939
940
  if transferable_hype > 0.01:
@@ -944,7 +945,7 @@ class HyperlendStableYieldStrategy(Strategy):
944
945
  success,
945
946
  msg,
946
947
  ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
947
- query="hyperliquid-hyperevm",
948
+ token_id="hyperliquid-hyperevm",
948
949
  amount=transferable_hype,
949
950
  strategy_name=self.name,
950
951
  skip_ledger=False,
@@ -1014,7 +1015,7 @@ class HyperlendStableYieldStrategy(Strategy):
1014
1015
  success,
1015
1016
  message,
1016
1017
  ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
1017
- query=token_info.get("token_id"),
1018
+ token_id=token_info.get("token_id"),
1018
1019
  amount=amount_tokens,
1019
1020
  strategy_name=self.name,
1020
1021
  )
@@ -2275,7 +2276,7 @@ class HyperlendStableYieldStrategy(Strategy):
2275
2276
  success,
2276
2277
  strategy_hype_balance_wei,
2277
2278
  ) = await self.balance_adapter.get_balance(
2278
- query=self.hype_token_info.get("token_id"),
2279
+ token_id=self.hype_token_info.get("token_id"),
2279
2280
  wallet_address=self._get_strategy_wallet_address(),
2280
2281
  )
2281
2282
  hype_price = asset_map.get(WRAPPED_HYPE_ADDRESS, {}).get("price_usd") or 0.0