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.
- wayfinder_paths/CONFIG_GUIDE.md +5 -14
- wayfinder_paths/adapters/brap_adapter/README.md +1 -1
- wayfinder_paths/adapters/brap_adapter/adapter.py +1 -53
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +5 -7
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
- wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +3 -0
- wayfinder_paths/adapters/pool_adapter/README.md +3 -104
- wayfinder_paths/adapters/pool_adapter/adapter.py +0 -194
- wayfinder_paths/adapters/pool_adapter/examples.json +0 -100
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -134
- wayfinder_paths/adapters/token_adapter/README.md +1 -1
- wayfinder_paths/core/clients/AuthClient.py +0 -3
- wayfinder_paths/core/clients/BRAPClient.py +1 -0
- wayfinder_paths/core/clients/ClientManager.py +1 -22
- wayfinder_paths/core/clients/PoolClient.py +0 -16
- wayfinder_paths/core/clients/WalletClient.py +0 -8
- wayfinder_paths/core/clients/WayfinderClient.py +9 -14
- wayfinder_paths/core/clients/__init__.py +0 -8
- wayfinder_paths/core/clients/protocols.py +0 -64
- wayfinder_paths/core/config.py +5 -45
- wayfinder_paths/core/engine/StrategyJob.py +0 -3
- wayfinder_paths/core/services/base.py +0 -49
- wayfinder_paths/core/services/local_evm_txn.py +3 -82
- wayfinder_paths/core/services/local_token_txn.py +61 -70
- wayfinder_paths/core/services/web3_service.py +0 -2
- wayfinder_paths/core/settings.py +8 -8
- wayfinder_paths/core/strategies/Strategy.py +1 -5
- wayfinder_paths/core/utils/evm_helpers.py +7 -12
- wayfinder_paths/core/wallets/README.md +3 -6
- wayfinder_paths/run_strategy.py +29 -32
- wayfinder_paths/scripts/make_wallets.py +1 -25
- wayfinder_paths/scripts/run_strategy.py +0 -2
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +86 -137
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +106 -28
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1 -6
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +40 -17
- wayfinder_paths/templates/strategy/test_strategy.py +0 -4
- {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/METADATA +33 -15
- {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/RECORD +49 -51
- wayfinder_paths/core/clients/SimulationClient.py +0 -192
- wayfinder_paths/core/clients/TransactionClient.py +0 -63
- {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/LICENSE +0 -0
- {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"
|
|
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
|
-
#
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
258
|
+
# Update
|
|
259
|
+
success, msg = await strategy.update()
|
|
260
|
+
assert success, f"Update failed: {msg}"
|
|
196
261
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
262
|
+
# Status
|
|
263
|
+
status = await strategy.status()
|
|
264
|
+
assert "portfolio_value" in status
|
|
200
265
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
|
82
|
+
# Copy config and edit credentials
|
|
83
83
|
cp wayfinder_paths/config.example.json config.json
|
|
84
84
|
|
|
85
85
|
# Check status / health
|
|
@@ -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(
|
|
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 (
|
|
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
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
return
|
|
62
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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,
|
|
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
|
[
|