wayfinder-paths 0.1.7__py3-none-any.whl → 0.1.9__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.
Files changed (51) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +5 -14
  2. wayfinder_paths/adapters/brap_adapter/README.md +1 -1
  3. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -53
  4. wayfinder_paths/adapters/brap_adapter/test_adapter.py +5 -7
  5. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
  6. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
  7. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
  8. wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
  9. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +3 -0
  10. wayfinder_paths/adapters/pool_adapter/README.md +3 -104
  11. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -194
  12. wayfinder_paths/adapters/pool_adapter/examples.json +0 -100
  13. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -134
  14. wayfinder_paths/adapters/token_adapter/README.md +1 -1
  15. wayfinder_paths/core/clients/AuthClient.py +0 -3
  16. wayfinder_paths/core/clients/BRAPClient.py +1 -0
  17. wayfinder_paths/core/clients/ClientManager.py +1 -22
  18. wayfinder_paths/core/clients/PoolClient.py +0 -16
  19. wayfinder_paths/core/clients/WalletClient.py +0 -8
  20. wayfinder_paths/core/clients/WayfinderClient.py +9 -14
  21. wayfinder_paths/core/clients/__init__.py +0 -8
  22. wayfinder_paths/core/clients/protocols.py +0 -64
  23. wayfinder_paths/core/config.py +5 -45
  24. wayfinder_paths/core/engine/StrategyJob.py +0 -3
  25. wayfinder_paths/core/services/base.py +0 -49
  26. wayfinder_paths/core/services/local_evm_txn.py +3 -82
  27. wayfinder_paths/core/services/local_token_txn.py +61 -70
  28. wayfinder_paths/core/services/web3_service.py +0 -2
  29. wayfinder_paths/core/settings.py +8 -8
  30. wayfinder_paths/core/strategies/Strategy.py +1 -5
  31. wayfinder_paths/core/utils/evm_helpers.py +7 -12
  32. wayfinder_paths/core/wallets/README.md +3 -6
  33. wayfinder_paths/run_strategy.py +29 -32
  34. wayfinder_paths/scripts/make_wallets.py +1 -25
  35. wayfinder_paths/scripts/run_strategy.py +0 -2
  36. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
  37. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +86 -137
  38. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
  39. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
  40. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
  41. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +106 -28
  42. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
  43. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1 -6
  44. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +40 -17
  45. wayfinder_paths/templates/strategy/test_strategy.py +0 -4
  46. {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/METADATA +33 -15
  47. {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/RECORD +49 -51
  48. wayfinder_paths/core/clients/SimulationClient.py +0 -192
  49. wayfinder_paths/core/clients/TransactionClient.py +0 -63
  50. {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/LICENSE +0 -0
  51. {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/WHEEL +0 -0
@@ -119,12 +119,32 @@ class TestBasisTradingStrategy:
119
119
  return_value=(
120
120
  True,
121
121
  {
122
- "marginSummary": {"accountValue": "0", "withdrawable": "0"},
122
+ "marginSummary": {"accountValue": "0"},
123
+ "withdrawable": "100.0", # Top-level withdrawable for withdraw() method
123
124
  "assetPositions": [],
124
125
  },
125
126
  )
126
127
  )
127
128
  mock.get_spot_user_state = AsyncMock(return_value=(True, {"balances": []}))
129
+ mock.get_max_builder_fee = AsyncMock(return_value=(True, 0))
130
+ mock.approve_builder_fee = AsyncMock(return_value=(True, {"status": "ok"}))
131
+ mock.update_leverage = AsyncMock(return_value=(True, {"status": "ok"}))
132
+ mock.transfer_perp_to_spot = AsyncMock(return_value=(True, {"status": "ok"}))
133
+ mock.transfer_spot_to_perp = AsyncMock(return_value=(True, {"status": "ok"}))
134
+ mock.place_market_order = AsyncMock(return_value=(True, {"status": "ok"}))
135
+ mock.place_limit_order = AsyncMock(return_value=(True, {"status": "ok"}))
136
+ mock.place_stop_loss = AsyncMock(return_value=(True, {"status": "ok"}))
137
+ mock.cancel_order = AsyncMock(return_value=(True, {"status": "ok"}))
138
+ mock.withdraw = AsyncMock(return_value=(True, {"status": "ok"}))
139
+ mock.get_open_orders = AsyncMock(return_value=(True, []))
140
+ mock.get_frontend_open_orders = AsyncMock(return_value=(True, []))
141
+ mock.get_valid_order_size = MagicMock(side_effect=lambda _asset, size: size)
142
+ mock.wait_for_deposit = AsyncMock(
143
+ return_value=(True, 100.0) # (deposit_confirmed, final_balance)
144
+ )
145
+ mock.wait_for_withdrawal = AsyncMock(
146
+ return_value=(True, {"0x123456": 100.0}) # tx_hash -> amount (float)
147
+ )
128
148
  return mock
129
149
 
130
150
  @pytest.fixture
@@ -162,7 +182,6 @@ class TestBasisTradingStrategy:
162
182
  "main_wallet": {"address": "0x1234"},
163
183
  "strategy_wallet": {"address": "0x5678"},
164
184
  },
165
- simulation=True,
166
185
  )
167
186
  s.hyperliquid_adapter = mock_hyperliquid_adapter
168
187
  s.ledger_adapter = ledger_adapter
@@ -173,9 +192,45 @@ class TestBasisTradingStrategy:
173
192
  s.balance_adapter.move_from_main_wallet_to_strategy_wallet = AsyncMock(
174
193
  return_value=(True, {})
175
194
  )
195
+ s.balance_adapter.move_from_strategy_wallet_to_main_wallet = AsyncMock(
196
+ return_value=(True, {})
197
+ )
176
198
  s.balance_adapter.send_to_address = AsyncMock(
177
199
  return_value=(True, {"tx_hash": "0x123"})
178
200
  )
201
+ # Mock internal dependencies to prevent MagicMock await errors
202
+ # These are needed if the real method somehow gets called
203
+ s.balance_adapter.token_client = AsyncMock()
204
+ s.balance_adapter.token_client.get_token_details = (
205
+ AsyncMock(
206
+ return_value={
207
+ "id": "usdc",
208
+ "address": "0x1234",
209
+ "decimals": 6,
210
+ }
211
+ )
212
+ )
213
+ s.balance_adapter.token_transactions = AsyncMock()
214
+ s.balance_adapter.token_transactions.build_send = AsyncMock(
215
+ return_value=(True, {"transaction": "0xMOCK"})
216
+ )
217
+ s.balance_adapter.wallet_provider = AsyncMock()
218
+ s.balance_adapter.wallet_provider.broadcast_transaction = (
219
+ AsyncMock(
220
+ return_value=(True, {"transaction_hash": "0x123"})
221
+ )
222
+ )
223
+ s.balance_adapter.token_adapter = AsyncMock()
224
+ s.balance_adapter.token_adapter.get_token_price = AsyncMock(
225
+ return_value=(True, {"current_price": 1.0})
226
+ )
227
+ # ledger_adapter is real, but ensure its methods are async-mockable
228
+ s.balance_adapter.ledger_adapter = ledger_adapter
229
+ # Also ensure the balance_adapter's _move_between_wallets won't call real methods
230
+ # by making sure all its dependencies return AsyncMock
231
+ s.balance_adapter._move_between_wallets = AsyncMock(
232
+ return_value=(True, {"transaction_hash": "0x123"})
233
+ )
179
234
  return s
180
235
 
181
236
  @pytest.mark.asyncio
@@ -185,22 +240,32 @@ class TestBasisTradingStrategy:
185
240
  examples = load_examples()
186
241
  smoke = examples["smoke"]
187
242
 
188
- # Deposit
189
- deposit_params = smoke.get("deposit", {})
190
- success, msg = await strategy.deposit(**deposit_params)
191
- assert success, f"Deposit failed: {msg}"
243
+ # Mock PairedFiller for update() and withdraw() to work
244
+ with patch(
245
+ "wayfinder_paths.strategies.basis_trading_strategy.strategy.PairedFiller"
246
+ ) as mock_filler_class:
247
+ mock_filler = MagicMock()
248
+ mock_filler.fill_pair_units = AsyncMock(
249
+ return_value=(0.5, 0.5, 1000.0, 1000.0, [], [])
250
+ )
251
+ mock_filler_class.return_value = mock_filler
252
+
253
+ # Deposit
254
+ deposit_params = smoke.get("deposit", {})
255
+ success, msg = await strategy.deposit(**deposit_params)
256
+ assert success, f"Deposit failed: {msg}"
192
257
 
193
- # Update
194
- success, msg = await strategy.update()
195
- assert success, f"Update failed: {msg}"
258
+ # Update
259
+ success, msg = await strategy.update()
260
+ assert success, f"Update failed: {msg}"
196
261
 
197
- # Status
198
- status = await strategy.status()
199
- assert "portfolio_value" in status
262
+ # Status
263
+ status = await strategy.status()
264
+ assert "portfolio_value" in status
200
265
 
201
- # Withdraw
202
- success, msg = await strategy.withdraw()
203
- assert success, f"Withdraw failed: {msg}"
266
+ # Withdraw (needs PairedFiller mock for _close_position)
267
+ success, msg = await strategy.withdraw()
268
+ assert success, f"Withdraw failed: {msg}"
204
269
 
205
270
  @pytest.mark.asyncio
206
271
  async def test_deposit_minimum(self, strategy):
@@ -311,8 +376,23 @@ class TestBasisTradingStrategy:
311
376
  assert opps[0]["selection"]["net_apy"] is not None
312
377
 
313
378
  @pytest.mark.asyncio
314
- async def test_get_undeployed_capital_empty(self, strategy):
379
+ async def test_get_undeployed_capital_empty(
380
+ self, strategy, mock_hyperliquid_adapter
381
+ ):
315
382
  """Test _get_undeployed_capital with no capital."""
383
+ mock_hyperliquid_adapter.get_user_state = AsyncMock(
384
+ return_value=(
385
+ True,
386
+ {
387
+ "marginSummary": {"accountValue": "0", "withdrawable": "0"},
388
+ "withdrawable": "0",
389
+ "assetPositions": [],
390
+ },
391
+ )
392
+ )
393
+ mock_hyperliquid_adapter.get_spot_user_state = AsyncMock(
394
+ return_value=(True, {"balances": []})
395
+ )
316
396
  perp_margin, spot_usdc = await strategy._get_undeployed_capital()
317
397
  assert perp_margin == 0.0
318
398
  assert spot_usdc == 0.0
@@ -508,7 +588,6 @@ class TestBasisTradingStrategy:
508
588
  "main_wallet": {"address": "0x1234"},
509
589
  "strategy_wallet": {"address": "0x5678"},
510
590
  },
511
- simulation=False, # Not simulation mode
512
591
  )
513
592
  s.hyperliquid_adapter = mock_hyperliquid_adapter
514
593
  s.ledger_adapter = ledger_adapter
@@ -561,7 +640,6 @@ class TestBasisTradingStrategy:
561
640
  "main_wallet": {"address": "0x1234"},
562
641
  "strategy_wallet": {"address": "0x5678"},
563
642
  },
564
- simulation=False, # Not simulation mode
565
643
  )
566
644
  s.hyperliquid_adapter = mock_hyperliquid_adapter
567
645
  s.ledger_adapter = ledger_adapter
@@ -580,46 +658,6 @@ class TestBasisTradingStrategy:
580
658
  # Should have called approve_builder_fee
581
659
  mock_hyperliquid_adapter.approve_builder_fee.assert_called_once()
582
660
 
583
- @pytest.mark.asyncio
584
- async def test_ensure_builder_fee_simulation_mode(
585
- self, mock_hyperliquid_adapter, ledger_adapter
586
- ):
587
- """Test ensure_builder_fee_approved in simulation mode."""
588
- with patch(
589
- "wayfinder_paths.strategies.basis_trading_strategy.strategy.HyperliquidAdapter",
590
- return_value=mock_hyperliquid_adapter,
591
- ):
592
- with patch(
593
- "wayfinder_paths.strategies.basis_trading_strategy.strategy.BalanceAdapter"
594
- ):
595
- with patch(
596
- "wayfinder_paths.strategies.basis_trading_strategy.strategy.TokenAdapter"
597
- ):
598
- with patch(
599
- "wayfinder_paths.strategies.basis_trading_strategy.strategy.LedgerAdapter",
600
- return_value=ledger_adapter,
601
- ):
602
- with patch(
603
- "wayfinder_paths.strategies.basis_trading_strategy.strategy.WalletManager"
604
- ):
605
- from wayfinder_paths.strategies.basis_trading_strategy.strategy import (
606
- BasisTradingStrategy,
607
- )
608
-
609
- s = BasisTradingStrategy(
610
- config={
611
- "main_wallet": {"address": "0x1234"},
612
- "strategy_wallet": {"address": "0x5678"},
613
- },
614
- simulation=True, # Simulation mode
615
- )
616
- s.hyperliquid_adapter = mock_hyperliquid_adapter
617
- s.ledger_adapter = ledger_adapter
618
-
619
- success, msg = await s.ensure_builder_fee_approved()
620
- assert success is True
621
- assert "simulation" in msg.lower()
622
-
623
661
  @pytest.mark.asyncio
624
662
  async def test_portfolio_value_includes_spot_holdings(
625
663
  self, strategy, mock_hyperliquid_adapter
@@ -1,4 +1,4 @@
1
- # Hyperlend Stable Yield Strategy
1
+ # Hyperlend Stable Yield Strategy
2
2
 
3
3
  - Entrypoint: `strategies.hyperlend_stable_yield_strategy.strategy.HyperlendStableYieldStrategy`
4
4
  - Manifest: `manifest.yaml`
@@ -79,7 +79,7 @@ poetry install
79
79
  # Creates a main wallet (or use 'just create-strategy' which auto-creates wallets)
80
80
  poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
81
81
 
82
- # Copy config and edit credentials (or rely on env vars)
82
+ # Copy config and edit credentials
83
83
  cp wayfinder_paths/config.example.json config.json
84
84
 
85
85
  # Check status / health
@@ -1,6 +1,9 @@
1
1
  {
2
2
  "smoke": {
3
- "deposit": {},
3
+ "deposit": {
4
+ "main_token_amount": 10.0,
5
+ "gas_token_amount": 0.01
6
+ },
4
7
  "update": {},
5
8
  "status": {},
6
9
  "withdraw": {}
@@ -195,7 +195,6 @@ class HyperlendStableYieldStrategy(Strategy):
195
195
  *,
196
196
  main_wallet: dict[str, Any] | None = None,
197
197
  strategy_wallet: dict[str, Any] | None = None,
198
- simulation: bool = False,
199
198
  web3_service: Web3Service = None,
200
199
  api_key: str | None = None,
201
200
  ):
@@ -207,7 +206,6 @@ class HyperlendStableYieldStrategy(Strategy):
207
206
  merged_config["strategy_wallet"] = strategy_wallet
208
207
 
209
208
  self.config = merged_config
210
- self.simulation = simulation
211
209
  self.balance_adapter = None
212
210
  self.tx_adapter = None
213
211
  self.token_adapter = None
@@ -238,7 +236,6 @@ class HyperlendStableYieldStrategy(Strategy):
238
236
  token_transaction_service = LocalTokenTxnService(
239
237
  adapter_config,
240
238
  wallet_provider=wallet_provider,
241
- simulation=self.simulation,
242
239
  )
243
240
  web3_service = DefaultWeb3Service(
244
241
  wallet_provider=wallet_provider,
@@ -250,12 +247,9 @@ class HyperlendStableYieldStrategy(Strategy):
250
247
  balance = BalanceAdapter(adapter_config, web3_service=web3_service)
251
248
  token_adapter = TokenAdapter()
252
249
  ledger_adapter = LedgerAdapter() # here
253
- brap_adapter = BRAPAdapter(
254
- web3_service=web3_service, simulation=self.simulation
255
- )
250
+ brap_adapter = BRAPAdapter(web3_service=web3_service)
256
251
  hyperlend_adapter = HyperlendAdapter(
257
252
  adapter_config,
258
- simulation=self.simulation,
259
253
  web3_service=web3_service,
260
254
  )
261
255
 
@@ -323,11 +317,109 @@ class HyperlendStableYieldStrategy(Strategy):
323
317
  self.hys_z: float = self.HYSTERESIS_Z
324
318
  self.rotation_tx_cost: float = self.ROTATION_TX_COST
325
319
 
326
- async def deposit(self) -> StatusTuple:
320
+ async def deposit(
321
+ self, main_token_amount: float = 0.0, gas_token_amount: float = 0.0
322
+ ) -> StatusTuple:
323
+ if main_token_amount == 0.0 and gas_token_amount == 0.0:
324
+ return (
325
+ False,
326
+ "Either main_token_amount or gas_token_amount must be provided",
327
+ )
328
+
329
+ if main_token_amount > 0:
330
+ if main_token_amount < self.MIN_USDT0_DEPOSIT_AMOUNT:
331
+ return (
332
+ False,
333
+ f"Main token amount {main_token_amount} is below minimum {self.MIN_USDT0_DEPOSIT_AMOUNT}",
334
+ )
335
+
336
+ if gas_token_amount and gas_token_amount > self.GAS_MAXIMUM:
337
+ return (
338
+ False,
339
+ f"Gas token amount exceeds maximum configured gas buffer: {self.GAS_MAXIMUM}",
340
+ )
341
+
342
+ if self.balance_adapter is None:
343
+ return (
344
+ False,
345
+ "Balance adapter not initialized. Strategy initialization may have failed.",
346
+ )
347
+
348
+ (
349
+ success,
350
+ main_usdt0_balance,
351
+ ) = await self.balance_adapter.get_balance(
352
+ token_id=self.usdt_token_info.get("token_id"),
353
+ wallet_address=self._get_main_wallet_address(),
354
+ )
355
+ if not success:
356
+ return (
357
+ False,
358
+ f"Failed to get main wallet USDT0 balance: {main_usdt0_balance}",
359
+ )
360
+
361
+ (
362
+ success,
363
+ main_hype_balance,
364
+ ) = await self.balance_adapter.get_balance(
365
+ token_id=self.hype_token_info.get("token_id"),
366
+ wallet_address=self._get_main_wallet_address(),
367
+ )
368
+ if not success:
369
+ return (
370
+ False,
371
+ f"Failed to get main wallet HYPE balance: {main_hype_balance}",
372
+ )
373
+
374
+ main_usdt0_native = main_usdt0_balance / (
375
+ 10 ** self.usdt_token_info.get("decimals")
376
+ )
377
+ main_hype_native = main_hype_balance / (
378
+ 10 ** self.hype_token_info.get("decimals")
379
+ )
380
+
381
+ if main_token_amount > 0:
382
+ if main_usdt0_native < main_token_amount:
383
+ return (
384
+ False,
385
+ f"Main wallet USDT0 balance is less than the deposit amount: {main_usdt0_native} < {main_token_amount}",
386
+ )
387
+
388
+ if gas_token_amount > 0:
389
+ if main_hype_native < gas_token_amount:
390
+ return (
391
+ False,
392
+ f"Main wallet HYPE balance is less than the deposit amount: {main_hype_native} < {gas_token_amount}",
393
+ )
394
+
395
+ if gas_token_amount > 0:
396
+ (
397
+ success,
398
+ msg,
399
+ ) = await self.balance_adapter.move_from_main_wallet_to_strategy_wallet(
400
+ self.hype_token_info.get("token_id"),
401
+ gas_token_amount,
402
+ strategy_name=self.name,
403
+ )
404
+ if not success:
405
+ return (False, f"HYPE transfer to strategy failed: {msg}")
406
+
407
+ if main_token_amount > 0:
408
+ (
409
+ success,
410
+ msg,
411
+ ) = await self.balance_adapter.move_from_main_wallet_to_strategy_wallet(
412
+ self.usdt_token_info.get("token_id"),
413
+ main_token_amount,
414
+ strategy_name=self.name,
415
+ )
416
+ if not success:
417
+ return (False, f"USDT0 transfer to strategy failed: {msg}")
418
+
327
419
  self._invalidate_assets_snapshot()
328
420
  await self._hydrate_position_from_chain()
329
421
 
330
- return (True, "hydrated positions")
422
+ return (success, msg)
331
423
 
332
424
  async def _estimate_redeploy_tokens(self) -> float:
333
425
  positions = await self._get_lent_positions()
@@ -1344,26 +1436,12 @@ class HyperlendStableYieldStrategy(Strategy):
1344
1436
  continue
1345
1437
 
1346
1438
  balance_tokens = float(entry.get("tokens") or 0)
1347
- if balance_tokens <= 0:
1348
- continue
1349
-
1350
- try:
1351
- (
1352
- transfer_success,
1353
- _,
1354
- ) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
1355
- token_id=token.get("token_id"),
1356
- amount=balance_tokens,
1357
- strategy_name=self.name,
1358
- )
1359
- except Exception:
1360
- continue
1361
-
1362
- if transfer_success:
1363
- actions.append(
1364
- f"Transferred {balance_tokens:.4f} {token.get('symbol')} to main wallet"
1439
+ if balance_tokens > 0:
1440
+ logger.warning(
1441
+ f"Failed to swap {balance_tokens:.4f} {token.get('symbol', 'unknown')} "
1442
+ f"to {target_token.get('symbol', 'target')} during alignment. "
1443
+ f"Leaving in strategy wallet for next update cycle."
1365
1444
  )
1366
- self._invalidate_assets_snapshot()
1367
1445
 
1368
1446
  kept_tokens = float(balances.get("native", {}).get("tokens") or 0)
1369
1447
 
@@ -46,20 +46,20 @@ def strategy():
46
46
  config=mock_config,
47
47
  main_wallet=mock_config["main_wallet"],
48
48
  strategy_wallet=mock_config["strategy_wallet"],
49
- simulation=True,
50
49
  )
51
50
 
52
51
  if hasattr(s, "balance_adapter") and s.balance_adapter:
53
52
  # Mock balances: 1000 USDT0 (with 6 decimals) and 2 HYPE (with 18 decimals)
54
- usdt0_balance_mock = AsyncMock(return_value=(True, 1000000000))
55
- hype_balance_mock = AsyncMock(return_value=(True, 2000000000000000000))
56
-
57
53
  def get_balance_side_effect(token_id, wallet_address, **kwargs):
58
- if token_id == "usdt0-hyperevm" or token_id == "usdt0":
59
- return usdt0_balance_mock.return_value
60
- elif token_id == "hype-hyperevm" or token_id == "hype":
61
- return hype_balance_mock.return_value
62
- return (True, 1000000000)
54
+ token_id_str = str(token_id).lower() if token_id else ""
55
+ if "usdt0" in token_id_str or token_id_str == "usdt0":
56
+ # 1000 USDT0 with 6 decimals = 1000 * 10^6 = 1000000000
57
+ return (True, 1000000000)
58
+ elif "hype" in token_id_str or token_id_str == "hype":
59
+ # 2 HYPE with 18 decimals = 2 * 10^18 = 2000000000000000000
60
+ return (True, 2000000000000000000)
61
+ # Default: return high balance for any other token
62
+ return (True, 2000000000000000000)
63
63
 
64
64
  s.balance_adapter.get_balance = AsyncMock(side_effect=get_balance_side_effect)
65
65
 
@@ -101,15 +101,54 @@ def strategy():
101
101
  )
102
102
 
103
103
  if hasattr(s, "balance_adapter") and s.balance_adapter:
104
+ # Mock the main methods first
104
105
  s.balance_adapter.move_from_main_wallet_to_strategy_wallet = AsyncMock(
105
106
  return_value=(True, "Transfer successful (simulated)")
106
107
  )
107
108
  s.balance_adapter.move_from_strategy_wallet_to_main_wallet = AsyncMock(
108
109
  return_value=(True, "Transfer successful (simulated)")
109
110
  )
110
- if hasattr(s.balance_adapter, "wallet_provider"):
111
- s.balance_adapter.wallet_provider.broadcast_transaction = AsyncMock(
112
- return_value=(True, {"transaction_hash": "0xCAFEBABE"})
111
+ # Mock internal dependencies unconditionally to prevent MagicMock await errors
112
+ # These are needed if the real method somehow gets called
113
+ s.balance_adapter.token_client = AsyncMock()
114
+ s.balance_adapter.token_client.get_token_details = AsyncMock(
115
+ return_value={
116
+ "id": "usdt0-hyperevm",
117
+ "address": "0x1234",
118
+ "decimals": 6,
119
+ }
120
+ )
121
+ s.balance_adapter.token_transactions = AsyncMock()
122
+ s.balance_adapter.token_transactions.build_send = AsyncMock(
123
+ return_value=(True, {"transaction": "0xMOCK"})
124
+ )
125
+ s.balance_adapter.wallet_provider = AsyncMock()
126
+ s.balance_adapter.wallet_provider.broadcast_transaction = AsyncMock(
127
+ return_value=(True, {"transaction_hash": "0xCAFEBABE"})
128
+ )
129
+ # token_adapter might already be set, so check before overriding
130
+ if (
131
+ not hasattr(s.balance_adapter, "token_adapter")
132
+ or s.balance_adapter.token_adapter is None
133
+ ):
134
+ s.balance_adapter.token_adapter = AsyncMock()
135
+ if hasattr(s.balance_adapter.token_adapter, "get_token_price"):
136
+ s.balance_adapter.token_adapter.get_token_price = AsyncMock(
137
+ return_value=(True, {"current_price": 1.0})
138
+ )
139
+ # ledger_adapter might already be set, so check before overriding
140
+ if (
141
+ not hasattr(s.balance_adapter, "ledger_adapter")
142
+ or s.balance_adapter.ledger_adapter is None
143
+ ):
144
+ s.balance_adapter.ledger_adapter = AsyncMock()
145
+ if hasattr(s.balance_adapter.ledger_adapter, "record_deposit"):
146
+ s.balance_adapter.ledger_adapter.record_deposit = AsyncMock(
147
+ return_value=(True, {})
148
+ )
149
+ if hasattr(s.balance_adapter.ledger_adapter, "record_withdrawal"):
150
+ s.balance_adapter.ledger_adapter.record_withdrawal = AsyncMock(
151
+ return_value=(True, {})
113
152
  )
114
153
 
115
154
  if hasattr(s, "ledger_adapter") and s.ledger_adapter:
@@ -304,8 +343,8 @@ async def test_canonical_usage(strategy):
304
343
  for example_name, example_data in canonical.items():
305
344
  if "deposit" in example_data:
306
345
  deposit_params = example_data.get("deposit", {})
307
- ok, _ = await strategy.deposit(**deposit_params)
308
- assert ok, f"Canonical example '{example_name}' deposit failed"
346
+ ok, msg = await strategy.deposit(**deposit_params)
347
+ assert ok, f"Canonical example '{example_name}' deposit failed: {msg}"
309
348
 
310
349
  if "update" in example_data:
311
350
  result = await strategy.update()
@@ -155,7 +155,6 @@ class StablecoinYieldStrategy(Strategy):
155
155
  *,
156
156
  main_wallet: dict[str, Any] | None = None,
157
157
  strategy_wallet: dict[str, Any] | None = None,
158
- simulation: bool = False,
159
158
  web3_service=None,
160
159
  api_key: str | None = None,
161
160
  ):
@@ -167,7 +166,6 @@ class StablecoinYieldStrategy(Strategy):
167
166
  merged_config["strategy_wallet"] = strategy_wallet
168
167
 
169
168
  self.config = merged_config
170
- self.simulation = simulation
171
169
  self.deposited_amount = 0
172
170
  self.current_pool = None
173
171
  self.current_apy = 0
@@ -198,7 +196,6 @@ class StablecoinYieldStrategy(Strategy):
198
196
  tx_adapter = LocalTokenTxnService(
199
197
  adapter_config,
200
198
  wallet_provider=wallet_provider,
201
- simulation=self.simulation,
202
199
  )
203
200
  web3_service = DefaultWeb3Service(
204
201
  wallet_provider=wallet_provider, evm_transactions=tx_adapter
@@ -210,9 +207,7 @@ class StablecoinYieldStrategy(Strategy):
210
207
  token_adapter = TokenAdapter()
211
208
  ledger_adapter = LedgerAdapter()
212
209
  pool_adapter = PoolAdapter()
213
- brap_adapter = BRAPAdapter(
214
- web3_service=web3_service, simulation=self.simulation
215
- )
210
+ brap_adapter = BRAPAdapter(web3_service=web3_service)
216
211
 
217
212
  self.register_adapters(
218
213
  [