wayfinder-paths 0.1.19__py3-none-any.whl → 0.1.21__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 (98) hide show
  1. wayfinder_paths/__init__.py +0 -2
  2. wayfinder_paths/adapters/balance_adapter/README.md +59 -45
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +1 -22
  4. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -14
  5. wayfinder_paths/adapters/brap_adapter/README.md +61 -184
  6. wayfinder_paths/adapters/brap_adapter/__init__.py +0 -4
  7. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -148
  8. wayfinder_paths/adapters/brap_adapter/test_adapter.py +0 -15
  9. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +0 -4
  10. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +1 -10
  11. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +0 -17
  12. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +3 -312
  13. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +1 -71
  14. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +0 -57
  15. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +0 -17
  16. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +2 -42
  17. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +1 -9
  18. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +15 -47
  19. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +0 -7
  20. wayfinder_paths/adapters/ledger_adapter/README.md +54 -74
  21. wayfinder_paths/adapters/ledger_adapter/__init__.py +0 -4
  22. wayfinder_paths/adapters/ledger_adapter/adapter.py +0 -106
  23. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +0 -12
  24. wayfinder_paths/adapters/moonwell_adapter/README.md +67 -106
  25. wayfinder_paths/adapters/moonwell_adapter/__init__.py +0 -4
  26. wayfinder_paths/adapters/moonwell_adapter/adapter.py +10 -122
  27. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +84 -83
  28. wayfinder_paths/adapters/pool_adapter/README.md +30 -51
  29. wayfinder_paths/adapters/pool_adapter/__init__.py +0 -4
  30. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -19
  31. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -8
  32. wayfinder_paths/adapters/token_adapter/README.md +41 -49
  33. wayfinder_paths/adapters/token_adapter/adapter.py +0 -32
  34. wayfinder_paths/adapters/token_adapter/test_adapter.py +1 -12
  35. wayfinder_paths/conftest.py +0 -8
  36. wayfinder_paths/core/__init__.py +0 -2
  37. wayfinder_paths/core/adapters/BaseAdapter.py +0 -22
  38. wayfinder_paths/core/adapters/__init__.py +0 -5
  39. wayfinder_paths/core/adapters/models.py +0 -5
  40. wayfinder_paths/core/analytics/__init__.py +0 -2
  41. wayfinder_paths/core/analytics/bootstrap.py +0 -16
  42. wayfinder_paths/core/analytics/stats.py +0 -7
  43. wayfinder_paths/core/analytics/test_analytics.py +5 -34
  44. wayfinder_paths/core/clients/BRAPClient.py +0 -35
  45. wayfinder_paths/core/clients/ClientManager.py +0 -51
  46. wayfinder_paths/core/clients/HyperlendClient.py +0 -77
  47. wayfinder_paths/core/clients/LedgerClient.py +2 -122
  48. wayfinder_paths/core/clients/PoolClient.py +0 -2
  49. wayfinder_paths/core/clients/TokenClient.py +0 -39
  50. wayfinder_paths/core/clients/WalletClient.py +0 -15
  51. wayfinder_paths/core/clients/WayfinderClient.py +0 -24
  52. wayfinder_paths/core/clients/__init__.py +0 -4
  53. wayfinder_paths/core/clients/protocols.py +25 -98
  54. wayfinder_paths/core/config.py +0 -24
  55. wayfinder_paths/core/constants/__init__.py +0 -7
  56. wayfinder_paths/core/constants/base.py +2 -9
  57. wayfinder_paths/core/constants/erc20_abi.py +0 -5
  58. wayfinder_paths/core/constants/hyperlend_abi.py +0 -7
  59. wayfinder_paths/core/constants/moonwell_abi.py +0 -35
  60. wayfinder_paths/core/engine/StrategyJob.py +0 -32
  61. wayfinder_paths/core/strategies/Strategy.py +0 -99
  62. wayfinder_paths/core/strategies/__init__.py +0 -2
  63. wayfinder_paths/core/utils/__init__.py +0 -1
  64. wayfinder_paths/core/utils/evm_helpers.py +0 -50
  65. wayfinder_paths/core/utils/{erc20_service.py → tokens.py} +25 -21
  66. wayfinder_paths/core/utils/transaction.py +0 -1
  67. wayfinder_paths/run_strategy.py +0 -46
  68. wayfinder_paths/scripts/create_strategy.py +0 -17
  69. wayfinder_paths/scripts/make_wallets.py +1 -4
  70. wayfinder_paths/strategies/basis_trading_strategy/README.md +71 -163
  71. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +0 -24
  72. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +36 -400
  73. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +15 -64
  74. wayfinder_paths/strategies/basis_trading_strategy/types.py +0 -4
  75. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +65 -56
  76. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +4 -27
  77. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -10
  78. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +71 -72
  79. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +23 -227
  80. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +120 -113
  81. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +64 -59
  82. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +4 -44
  83. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +2 -35
  84. wayfinder_paths/templates/adapter/README.md +107 -46
  85. wayfinder_paths/templates/adapter/adapter.py +0 -9
  86. wayfinder_paths/templates/adapter/test_adapter.py +0 -19
  87. wayfinder_paths/templates/strategy/README.md +113 -59
  88. wayfinder_paths/templates/strategy/strategy.py +0 -22
  89. wayfinder_paths/templates/strategy/test_strategy.py +0 -28
  90. wayfinder_paths/tests/test_test_coverage.py +2 -12
  91. wayfinder_paths/tests/test_utils.py +1 -31
  92. wayfinder_paths-0.1.21.dist-info/METADATA +355 -0
  93. wayfinder_paths-0.1.21.dist-info/RECORD +129 -0
  94. {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.21.dist-info}/WHEEL +1 -1
  95. wayfinder_paths/core/adapters/base.py +0 -5
  96. wayfinder_paths-0.1.19.dist-info/METADATA +0 -592
  97. wayfinder_paths-0.1.19.dist-info/RECORD +0 -130
  98. {wayfinder_paths-0.1.19.dist-info → wayfinder_paths-0.1.21.dist-info}/LICENSE +0 -0
@@ -1,88 +1,93 @@
1
1
  # Stablecoin Yield Strategy
2
2
 
3
- - Entrypoint: `strategies.stablecoin_yield_strategy.strategy.StablecoinYieldStrategy`
4
- - Examples: `examples.json`
5
- - Tests: `test_strategy.py`
3
+ Automated USDC yield optimization on Base chain.
6
4
 
7
- ## What it does
5
+ - **Module**: `wayfinder_paths.strategies.stablecoin_yield_strategy.strategy.StablecoinYieldStrategy`
6
+ - **Chain**: Base (8453)
7
+ - **Token**: USDC
8
8
 
9
- Actively manages Base USDC deposits. Deposits pull USDC (plus an ETH gas buffer) from the main wallet into the strategy wallet, then the strategy searches Base-native pools for the best USD-denominated APY. Updates monitor DeFi Llama feeds and Wayfinder pool analytics, respecting a rotation cooldown and minimum APY improvement before rebalancing via the BRAP router. Withdrawals unwind the current position, sweep residual tokens back into USDC, and return funds to the main wallet.
9
+ ## Overview
10
10
 
11
- ## On-chain policy
11
+ This strategy actively manages USDC deposits by:
12
+ 1. Transferring USDC (plus ETH gas buffer) from main wallet to strategy wallet
13
+ 2. Searching Base-native pools for the best USD-denominated APY
14
+ 3. Monitoring DeFi Llama feeds and Wayfinder pool analytics
15
+ 4. Rebalancing to higher-yield pools when APY improvements exceed thresholds
16
+ 5. Respecting rotation cooldowns to avoid excessive churn
12
17
 
13
- Transactions are scoped to the strategy wallet and Enso Router approval/swap calls:
18
+ ## Key Parameters
14
19
 
15
- ```
16
- (wallet.id == 'FORMAT_WALLET_ID') && ((eth.tx.data[0..10] == '0x095ea7b3' && eth.tx.data[34..74] == 'f75584ef6673ad213a685a1b58cc0330b8ea22cf') || (eth.tx.to == '0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf'))
17
- ```
18
-
19
- ## Key parameters (from `strategy.py`)
20
+ | Parameter | Value | Description |
21
+ |-----------|-------|-------------|
22
+ | `MIN_AMOUNT_USDC` | 2 | Minimum deposit amount |
23
+ | `MIN_TVL` | 1,000,000 | Minimum pool TVL |
24
+ | `ROTATION_MIN_INTERVAL` | 14 days | Cooldown between rotations |
25
+ | `DUST_APY` | 0.01 (1%) | APY threshold below which pools are ignored |
26
+ | `SEARCH_DEPTH` | 10 | Number of pools to examine |
27
+ | `MIN_GAS` | 0.001 ETH | Minimum gas buffer |
28
+ | `GAS_MAXIMUM` | 0.02 ETH | Maximum gas per deposit |
20
29
 
21
- - `MIN_AMOUNT_USDC = 2` → deposits smaller than 2 USDC are rejected.
22
- - `MIN_TVL = 1_000_000` → pools below $1M TVL are ignored.
23
- - `ROTATION_MIN_INTERVAL = 14 days` → once rotated, the strategy waits ~2 weeks unless the new candidate dramatically outperforms.
24
- - `DUST_APY = 0.01` (1%) → pools below this APY are treated as dust.
25
- - `SEARCH_DEPTH = 10` → how many pools to examine when selecting candidates.
26
- - `MIN_GAS = 0.001` and `GAS_MAXIMUM = 0.02` Base ETH → minimum buffer required in the strategy wallet plus the upper bound accepted per deposit.
30
+ ## Adapters Used
27
31
 
28
- ## Adapters used
29
-
30
- - `BalanceAdapter` for wallet/pool balances and orchestrating transfers between the main and strategy wallets (with ledger recording).
31
- - `PoolAdapter` for pool metadata, llama reports, and yield analytics.
32
- - `BRAPAdapter` to source swap quotes and execute rotations.
33
- - `TokenAdapter` for metadata (gas token, USDC info).
34
- - `LedgerAdapter` for net-deposit tracking and cooldown enforcement.
32
+ - **BalanceAdapter**: Wallet/pool balances, cross-wallet transfers
33
+ - **PoolAdapter**: Pool metadata, yield analytics
34
+ - **BRAPAdapter**: Swap quotes and execution
35
+ - **TokenAdapter**: Token metadata (gas token, USDC info)
36
+ - **LedgerAdapter**: Net deposit tracking, cooldown enforcement
35
37
 
36
38
  ## Actions
37
39
 
38
40
  ### Deposit
39
41
 
40
- - Validates `main_token_amount` ≥ `MIN_AMOUNT_USDC` and `gas_token_amount` ≤ `GAS_MAXIMUM`.
41
- - Confirms the main wallet holds enough USDC and Base ETH.
42
- - Moves Base ETH into the strategy wallet (when requested or when the strategy needs a top-up), then transfers the requested USDC amount via `BalanceAdapter.move_from_main_wallet_to_strategy_wallet`.
43
- - Hydrates the on-chain position snapshot so future updates know which pool is active.
42
+ ```bash
43
+ poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy \
44
+ --action deposit --main-token-amount 60 --gas-token-amount 0.001 --config config.json
45
+ ```
46
+
47
+ - Validates `main_token_amount >= MIN_AMOUNT_USDC`
48
+ - Validates `gas_token_amount <= GAS_MAXIMUM`
49
+ - Transfers ETH and USDC to strategy wallet
50
+ - Initializes position tracking
44
51
 
45
52
  ### Update
46
53
 
47
- - Fetches the latest strategy balances, idle assets, and current target pool.
48
- - Runs `_find_best_pool()` which uses `PoolAdapter` and DeFi Llama data to score up to `SEARCH_DEPTH` pools that satisfy the APY/TVL filters.
49
- - Checks `LedgerAdapter.get_strategy_latest_transactions()` to enforce the rotation cooldown, unless the new candidate clears the APY-improvement threshold.
50
- - If rotation is approved, requests a BRAP quote, ensures the strategy has enough gas, executes the swap via `BRAPAdapter.swap_from_quote`, and sweeps any idle balances back into the target token.
51
- - Records informative status messages when no better pool exists or when cooldown blocks a move.
54
+ ```bash
55
+ poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy \
56
+ --action update --config config.json
57
+ ```
58
+
59
+ - Fetches current balances and active pool
60
+ - Runs `_find_best_pool()` to score candidate pools
61
+ - Checks rotation cooldown via LedgerAdapter
62
+ - Executes rotation if APY improvement threshold met
63
+ - Sweeps idle balances into target token
52
64
 
53
65
  ### Status
54
66
 
55
- `_status()` reports:
67
+ ```bash
68
+ poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy \
69
+ --action status --config config.json
70
+ ```
56
71
 
57
- - `portfolio_value`: refreshed pool balance (in base units) converted to float.
58
- - `net_deposit`: data pulled from `LedgerAdapter.get_strategy_net_deposit`.
59
- - `strategy_status`: dictionary exposing the active pool, APY estimates, and wallet balances.
72
+ Returns:
73
+ - `portfolio_value`: Current pool balance
74
+ - `net_deposit`: From LedgerAdapter
75
+ - `strategy_status`: Active pool, APY, wallet balances
60
76
 
61
77
  ### Withdraw
62
78
 
63
- - Requires a prior deposit (the strategy tracks `self.DEPOSIT_USDC`).
64
- - 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`.
65
- - Updates the ledger and clears cached pool state.
66
-
67
- ## Running locally
68
-
69
79
  ```bash
70
- # Install dependencies
71
- poetry install
72
-
73
- # Generate main wallet (writes config.json)
74
- # Creates a main wallet (or use 'just create-strategy' which auto-creates wallets)
75
- poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
80
+ poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy \
81
+ --action withdraw --config config.json
82
+ ```
76
83
 
77
- # Copy the example config and set credentials if needed
78
- cp wayfinder_paths/config.example.json config.json
84
+ - Unwinds current position via BRAP swaps
85
+ - Converts all holdings back to USDC
86
+ - Transfers USDC to main wallet
87
+ - Clears cached pool state
79
88
 
80
- # Smoke test the strategy
81
- poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy --action status --config $(pwd)/config.json
89
+ ## Testing
82
90
 
83
- # Perform a funded deposit/update cycle
84
- poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy --action deposit --main-token-amount 60 --gas-token-amount 0.001 --config $(pwd)/config.json
85
- poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy --action update --config $(pwd)/config.json
91
+ ```bash
92
+ poetry run pytest wayfinder_paths/strategies/stablecoin_yield_strategy/ -v
86
93
  ```
87
-
88
- Wallet addresses are auto-populated from `config.json` when you run `wayfinder_paths/scripts/make_wallets.py`. Set `NETWORK=testnet` in `config.json` to dry-run operations against mocked services.
@@ -32,12 +32,12 @@ class StablecoinYieldStrategy(Strategy):
32
32
  MINIMUM_DAYS_UNTIL_PROFIT = 7
33
33
  MIN_TVL = 1_000_000
34
34
  DUST_APY = 0.01
35
- MIN_GAS = 10e-4 # ethereum float
35
+ MIN_GAS = 10e-4
36
36
  SEARCH_DEPTH = 10
37
37
  SUPPORTED_NETWORK_CODES = {"base"}
38
38
  ROTATION_MIN_INTERVAL = timedelta(days=14)
39
39
  MINIMUM_APY_IMPROVEMENT = 0.01
40
- GAS_MAXIMUM = 10e-4 # ethereum float
40
+ GAS_MAXIMUM = 10e-4
41
41
  GAS_SAFETY_FRACTION = 1 / 3
42
42
 
43
43
  INFO = StratDescriptor(
@@ -226,7 +226,6 @@ class StablecoinYieldStrategy(Strategy):
226
226
  pass
227
227
 
228
228
  def _get_strategy_wallet_address(self) -> str:
229
- """Get strategy wallet address with validation."""
230
229
  strategy_wallet = self.config.get("strategy_wallet")
231
230
  if not strategy_wallet or not isinstance(strategy_wallet, dict):
232
231
  raise ValueError("strategy_wallet not configured in strategy config")
@@ -236,7 +235,6 @@ class StablecoinYieldStrategy(Strategy):
236
235
  return str(address)
237
236
 
238
237
  def _get_main_wallet_address(self) -> str:
239
- """Get main wallet address with validation."""
240
238
  main_wallet = self.config.get("main_wallet")
241
239
  if not main_wallet or not isinstance(main_wallet, dict):
242
240
  raise ValueError("main_wallet not configured in strategy config")
@@ -246,21 +244,18 @@ class StablecoinYieldStrategy(Strategy):
246
244
  return str(address)
247
245
 
248
246
  def _track_token(self, token_id: str, balance_wei: int = 0):
249
- """Track a token that the strategy holds or might hold."""
250
247
  if token_id:
251
248
  self.tracked_token_ids.add(token_id)
252
249
  if balance_wei > 0:
253
250
  self.tracked_balances[token_id] = balance_wei
254
251
 
255
252
  def _update_balance(self, token_id: str, balance_wei: int):
256
- """Update the tracked balance for a token."""
257
253
  if token_id:
258
254
  self.tracked_balances[token_id] = balance_wei
259
255
  if balance_wei > 0:
260
256
  self.tracked_token_ids.add(token_id)
261
257
 
262
258
  async def _refresh_tracked_balances(self):
263
- """Refresh balances for all tracked tokens from on-chain data."""
264
259
  strategy_address = self._get_strategy_wallet_address()
265
260
  for token_id in self.tracked_token_ids:
266
261
  try:
@@ -277,7 +272,6 @@ class StablecoinYieldStrategy(Strategy):
277
272
  self.tracked_balances[token_id] = 0
278
273
 
279
274
  def _get_non_zero_tracked_tokens(self) -> list[tuple[str, int]]:
280
- """Get list of (token_id, balance_wei) for tokens with non-zero balances."""
281
275
  return [
282
276
  (token_id, balance)
283
277
  for token_id, balance in self.tracked_balances.items()
@@ -291,7 +285,6 @@ class StablecoinYieldStrategy(Strategy):
291
285
  await super().setup()
292
286
  self.current_combined_apy_pct = 0.0
293
287
 
294
- # Get strategy net deposit
295
288
  try:
296
289
  logger.info("Fetching strategy net deposit from ledger")
297
290
  strategy_address = self._get_strategy_wallet_address()
@@ -308,7 +301,6 @@ class StablecoinYieldStrategy(Strategy):
308
301
  logger.error(f"Failed to fetch strategy net deposit: {e}")
309
302
  self.DEPOSIT_USDC = 0
310
303
 
311
- # Get USDC token info
312
304
  try:
313
305
  logger.info("Fetching USDC token information")
314
306
  success, self.usdc_token_info = await self.token_adapter.get_token(
@@ -340,11 +332,10 @@ class StablecoinYieldStrategy(Strategy):
340
332
 
341
333
  self.current_pool_data = None
342
334
 
343
- chain_code = "base" # Default to base
335
+ chain_code = "base"
344
336
  if self.current_pool and self.current_pool.get("chain"):
345
337
  chain_code = self.current_pool.get("chain").get("code", "base")
346
338
 
347
- # Get gas token info
348
339
  try:
349
340
  logger.info(f"Fetching gas token for chain: {chain_code}")
350
341
  success, gas_token_data = await self.token_adapter.get_gas_token(chain_code)
@@ -368,7 +359,6 @@ class StablecoinYieldStrategy(Strategy):
368
359
  self.current_pool_balance = 0
369
360
  return
370
361
 
371
- # Get strategy transactions to determine current position and build tracked token set
372
362
  try:
373
363
  logger.info("Fetching strategy transaction history to build state")
374
364
  success, txns_data = await self.ledger_adapter.get_strategy_transactions(
@@ -382,7 +372,6 @@ class StablecoinYieldStrategy(Strategy):
382
372
  ]
383
373
  logger.info(f"Found {len(txns)} non-deposit transactions")
384
374
 
385
- # Build tracked token set from transaction history
386
375
  for txn in txns:
387
376
  op_data = txn.get("data", {}).get("op_data", {})
388
377
  # Track any token that was swapped TO
@@ -585,7 +574,6 @@ class StablecoinYieldStrategy(Strategy):
585
574
  return total_usd
586
575
 
587
576
  async def _infer_active_pool_from_tracked_tokens(self):
588
- """Infer the active pool from tracked tokens with non-zero balances."""
589
577
  try:
590
578
  # Refresh balances for tracked tokens
591
579
  await self._refresh_tracked_balances()
@@ -613,12 +601,10 @@ class StablecoinYieldStrategy(Strategy):
613
601
  if not best_token_id:
614
602
  return None
615
603
 
616
- # Fetch token info
617
604
  success, token = await self.token_adapter.get_token(best_token_id)
618
605
  if not success:
619
606
  return None
620
607
 
621
- # Get fresh on-chain balance
622
608
  strategy_address = self._get_strategy_wallet_address()
623
609
  try:
624
610
  success, onchain_balance = await self.balance_adapter.get_balance(
@@ -640,11 +626,9 @@ class StablecoinYieldStrategy(Strategy):
640
626
  return None
641
627
 
642
628
  def _is_gas_balance_entry(self, balance: dict[str, Any]) -> bool:
643
- """Check if a balance entry represents a gas token."""
644
629
  if not self.gas_token:
645
630
  return False
646
631
 
647
- # Check by token ID
648
632
  token_id = balance.get("token_id")
649
633
  if (
650
634
  isinstance(token_id, str)
@@ -652,13 +636,11 @@ class StablecoinYieldStrategy(Strategy):
652
636
  ):
653
637
  return True
654
638
 
655
- # Check by token address and network
656
639
  token_address = balance.get("tokenAddress")
657
640
  if isinstance(token_address, str):
658
641
  if token_address.lower() == self.gas_token.get("address", "").lower():
659
642
  return True
660
643
 
661
- # Check address + network combination
662
644
  network = (balance.get("network") or "").lower()
663
645
  chain_code = self.current_pool.get("chain", {}).get("code", "").lower()
664
646
  if (
@@ -698,7 +680,6 @@ class StablecoinYieldStrategy(Strategy):
698
680
  f"Current pool set to: {token_info.get('symbol')} on {token_info.get('chain', {}).get('name')}"
699
681
  )
700
682
 
701
- # Check main wallet USDC balance if depositing main token
702
683
  if main_token_amount > 0:
703
684
  logger.info("Checking main wallet USDC balance")
704
685
  (
@@ -735,7 +716,6 @@ class StablecoinYieldStrategy(Strategy):
735
716
  f"Minimum deposit is {self.MIN_AMOUNT_USDC} USDC on Base. Received: {main_token_amount}",
736
717
  )
737
718
 
738
- # Check gas token amount if provided
739
719
  if gas_token_amount > 0:
740
720
  if gas_token_amount > self.GAS_MAXIMUM:
741
721
  return (
@@ -766,7 +746,6 @@ class StablecoinYieldStrategy(Strategy):
766
746
  f"Main wallet {gas_symbol} balance is less than the deposit amount: {main_gas_native} < {gas_token_amount}",
767
747
  )
768
748
 
769
- # Check gas balances for minimum requirement (only if depositing main token)
770
749
  if main_token_amount > 0:
771
750
  logger.info("Checking gas token balances for operations")
772
751
  gas_decimals = self.gas_token.get("decimals")
@@ -838,7 +817,6 @@ class StablecoinYieldStrategy(Strategy):
838
817
  return (False, f"USDC transfer to strategy failed: {msg}")
839
818
  logger.info("USDC transfer completed successfully")
840
819
 
841
- # Update tracked state
842
820
  self._track_token(self.usdc_token_info.get("token_id"))
843
821
  self._update_balance(
844
822
  self.usdc_token_info.get("token_id"),
@@ -847,7 +825,6 @@ class StablecoinYieldStrategy(Strategy):
847
825
 
848
826
  # Transfer gas if provided or if strategy needs top-up
849
827
  if gas_token_amount > 0:
850
- # Get gas symbol if not already defined
851
828
  if main_token_amount == 0:
852
829
  gas_symbol = self.gas_token.get("symbol")
853
830
 
@@ -902,7 +879,6 @@ class StablecoinYieldStrategy(Strategy):
902
879
  False,
903
880
  "Nothing to withdraw from strategy, wallet should be empty already. If not, an error has happened please manually remove funds",
904
881
  )
905
- # Get current pool balance
906
882
  logger.info("Fetching current pool balance")
907
883
  try:
908
884
  (
@@ -918,7 +894,6 @@ class StablecoinYieldStrategy(Strategy):
918
894
  logger.error(f"Failed to fetch pool balance: {e}")
919
895
  self.current_pool_balance = 0
920
896
 
921
- # Check if we need to swap out of current position
922
897
  if (
923
898
  self.current_pool.get("token_id") != self.usdc_token_info.get("token_id")
924
899
  and self.current_pool_balance
@@ -951,7 +926,7 @@ class StablecoinYieldStrategy(Strategy):
951
926
  break
952
927
  except Exception as e:
953
928
  logger.warning(f"Quote attempt {attempt + 1} failed: {e}")
954
- if attempt == 3: # Last attempt
929
+ if attempt == 3:
955
930
  logger.error("All quote attempts failed")
956
931
 
957
932
  best_quote = quotes.get("best_quote") if isinstance(quotes, dict) else None
@@ -1006,7 +981,6 @@ class StablecoinYieldStrategy(Strategy):
1006
981
 
1007
982
  await self._sweep_wallet(self.usdc_token_info)
1008
983
 
1009
- # Get final USDC balance in strategy wallet
1010
984
  status, raw_balance = await self.balance_adapter.get_balance(
1011
985
  query=self.usdc_token_info.get("token_id"),
1012
986
  wallet_address=self._get_strategy_wallet_address(),
@@ -1017,7 +991,6 @@ class StablecoinYieldStrategy(Strategy):
1017
991
  "decimals"
1018
992
  )
1019
993
 
1020
- # Get gas balance in strategy wallet
1021
994
  gas_amount = 0.0
1022
995
  if self.gas_token:
1023
996
  status, raw_gas = await self.balance_adapter.get_balance(
@@ -1047,7 +1020,6 @@ class StablecoinYieldStrategy(Strategy):
1047
1020
  )
1048
1021
 
1049
1022
  async def exit(self, **kwargs) -> StatusTuple:
1050
- """Transfer funds from strategy wallet to main wallet."""
1051
1023
  logger.info("EXIT: Transferring remaining funds to main wallet")
1052
1024
 
1053
1025
  strategy_address = self._get_strategy_wallet_address()
@@ -1299,7 +1271,6 @@ class StablecoinYieldStrategy(Strategy):
1299
1271
  pass
1300
1272
 
1301
1273
  async def _sweep_wallet(self, target_token):
1302
- """Sweep all tracked non-target tokens into the target token."""
1303
1274
  # Refresh tracked balances
1304
1275
  await self._refresh_tracked_balances()
1305
1276
 
@@ -1322,7 +1293,6 @@ class StablecoinYieldStrategy(Strategy):
1322
1293
  if token_id == target_token_id:
1323
1294
  continue
1324
1295
 
1325
- # Get fresh balance to ensure accuracy
1326
1296
  try:
1327
1297
  success, fresh_balance = await self.balance_adapter.get_balance(
1328
1298
  query=token_id,
@@ -1351,7 +1321,6 @@ class StablecoinYieldStrategy(Strategy):
1351
1321
  strategy_name=self.name,
1352
1322
  )
1353
1323
  if success:
1354
- # Update tracked state: source token now has 0 balance
1355
1324
  self._update_balance(token_id, 0)
1356
1325
  logger.info(f"Successfully swept {token_id} to {target_token_id}")
1357
1326
  else:
@@ -1454,7 +1423,6 @@ class StablecoinYieldStrategy(Strategy):
1454
1423
  )
1455
1424
 
1456
1425
  async def _get_non_gas_balances(self) -> list[dict[str, Any]]:
1457
- """Get non-gas balances from tracked tokens."""
1458
1426
  # Refresh tracked balances
1459
1427
  await self._refresh_tracked_balances()
1460
1428
 
@@ -1470,7 +1438,6 @@ class StablecoinYieldStrategy(Strategy):
1470
1438
  if balance_wei <= 0:
1471
1439
  continue
1472
1440
 
1473
- # Fetch token info to get address and chain
1474
1441
  try:
1475
1442
  success, token_info = await self.token_adapter.get_token(token_id)
1476
1443
  if not success or not token_info:
@@ -1634,7 +1601,6 @@ class StablecoinYieldStrategy(Strategy):
1634
1601
  return float(gas_price) * float(amount) / (10 ** token.get("decimals"))
1635
1602
 
1636
1603
  async def _status(self) -> StatusDict:
1637
- # Get ETH gas balance
1638
1604
  gas_success, gas_balance_wei = await self.balance_adapter.get_balance(
1639
1605
  query=self.gas_token.get("token_id"),
1640
1606
  wallet_address=self._get_strategy_wallet_address(),
@@ -1663,7 +1629,6 @@ class StablecoinYieldStrategy(Strategy):
1663
1629
  # Refresh tracked balances
1664
1630
  await self._refresh_tracked_balances()
1665
1631
 
1666
- # Calculate total value from tracked non-gas balances
1667
1632
  total_value = 0.0
1668
1633
  gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
1669
1634
 
@@ -1674,7 +1639,6 @@ class StablecoinYieldStrategy(Strategy):
1674
1639
  continue
1675
1640
 
1676
1641
  try:
1677
- # Get token price to calculate USD value
1678
1642
  success, price_data = await self.token_adapter.get_token_price(token_id)
1679
1643
  if not success:
1680
1644
  continue
@@ -1728,7 +1692,6 @@ class StablecoinYieldStrategy(Strategy):
1728
1692
  return [f"({wallet_id}) && (({approve_enso}) || ({swap_enso})) "]
1729
1693
 
1730
1694
  async def partial_liquidate(self, usd_value: float) -> StatusTuple:
1731
- """Liquidate strategy assets to reach target USD value in USDC."""
1732
1695
  # Refresh tracked balances
1733
1696
  await self._refresh_tracked_balances()
1734
1697
 
@@ -1736,7 +1699,6 @@ class StablecoinYieldStrategy(Strategy):
1736
1699
  usdc_decimals = self.usdc_token_info.get("decimals")
1737
1700
  gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
1738
1701
 
1739
- # Check current USDC balance
1740
1702
  available_usdc_wei = self.tracked_balances.get(usdc_token_id, 0)
1741
1703
  available_usdc_usd = float(available_usdc_wei) / (10**usdc_decimals)
1742
1704
 
@@ -1757,7 +1719,6 @@ class StablecoinYieldStrategy(Strategy):
1757
1719
  if balance_wei <= 0:
1758
1720
  continue
1759
1721
 
1760
- # Get token info and price
1761
1722
  try:
1762
1723
  success, token_info = await self.token_adapter.get_token(token_id)
1763
1724
  if not success:
@@ -1789,7 +1750,6 @@ class StablecoinYieldStrategy(Strategy):
1789
1750
  if success:
1790
1751
  swapped_usd = (amount_to_swap / (10**decimals)) * price
1791
1752
  available_usdc_usd += swapped_usd
1792
- # Update tracked state
1793
1753
  self._update_balance(token_id, balance_wei - amount_to_swap)
1794
1754
  else:
1795
1755
  logger.warning(f"Failed to liquidate {token_id}: {msg}")
@@ -15,7 +15,6 @@ elif sys.path.index(_wayfinder_path_str) > 0:
15
15
 
16
16
  import pytest # noqa: E402
17
17
 
18
- # Import test utilities
19
18
  try:
20
19
  from tests.test_utils import get_canonical_examples, load_strategy_examples
21
20
  except ImportError:
@@ -36,7 +35,6 @@ from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import ( # n
36
35
 
37
36
  @pytest.fixture
38
37
  def strategy():
39
- """Create a strategy instance for testing with minimal config."""
40
38
  mock_config = {
41
39
  "main_wallet": {"address": "0x1234567890123456789012345678901234567890"},
42
40
  "strategy_wallet": {"address": "0xabcdefabcdefabcdefabcdefabcdefabcdefabcd"},
@@ -260,7 +258,6 @@ def strategy():
260
258
  @pytest.mark.asyncio
261
259
  @pytest.mark.smoke
262
260
  async def test_smoke(strategy):
263
- """REQUIRED: Basic smoke test - verifies strategy lifecycle."""
264
261
  examples = load_strategy_examples(Path(__file__))
265
262
  smoke_data = examples["smoke"]
266
263
 
@@ -282,11 +279,6 @@ async def test_smoke(strategy):
282
279
 
283
280
  @pytest.mark.asyncio
284
281
  async def test_canonical_usage(strategy):
285
- """REQUIRED: Test canonical usage examples from examples.json (minimum).
286
-
287
- Canonical usage = all positive usage examples (excluding error cases).
288
- This is the MINIMUM requirement - feel free to add more test cases here.
289
- """
290
282
  examples = load_strategy_examples(Path(__file__))
291
283
  canonical = get_canonical_examples(examples)
292
284
 
@@ -309,7 +301,6 @@ async def test_canonical_usage(strategy):
309
301
 
310
302
  @pytest.mark.asyncio
311
303
  async def test_error_cases(strategy):
312
- """OPTIONAL: Test error scenarios from examples.json."""
313
304
  examples = load_strategy_examples(Path(__file__))
314
305
 
315
306
  for example_name, example_data in examples.items():
@@ -341,7 +332,6 @@ async def test_error_cases(strategy):
341
332
 
342
333
  @pytest.mark.asyncio
343
334
  async def test_token_tracking_initialization(strategy):
344
- """Test that tracked_token_ids and tracked_balances are initialized."""
345
335
  assert hasattr(strategy, "tracked_token_ids")
346
336
  assert hasattr(strategy, "tracked_balances")
347
337
  assert isinstance(strategy.tracked_token_ids, set)
@@ -350,7 +340,6 @@ async def test_token_tracking_initialization(strategy):
350
340
 
351
341
  @pytest.mark.asyncio
352
342
  async def test_track_token(strategy):
353
- """Test that _track_token adds tokens to tracked state."""
354
343
  test_token_id = "test-token-base"
355
344
  test_balance = 1000000
356
345
 
@@ -362,7 +351,6 @@ async def test_track_token(strategy):
362
351
 
363
352
  @pytest.mark.asyncio
364
353
  async def test_update_balance(strategy):
365
- """Test that _update_balance updates tracked balances."""
366
354
  test_token_id = "test-token-base"
367
355
  initial_balance = 1000000
368
356
  updated_balance = 2000000
@@ -376,7 +364,6 @@ async def test_update_balance(strategy):
376
364
 
377
365
  @pytest.mark.asyncio
378
366
  async def test_get_non_zero_tracked_tokens(strategy):
379
- """Test that _get_non_zero_tracked_tokens returns only non-zero balances."""
380
367
  strategy._track_token("token-1", 1000000)
381
368
  strategy._track_token("token-2", 0)
382
369
  strategy._track_token("token-3", 5000000)
@@ -392,7 +379,6 @@ async def test_get_non_zero_tracked_tokens(strategy):
392
379
 
393
380
  @pytest.mark.asyncio
394
381
  async def test_refresh_tracked_balances(strategy):
395
- """Test that _refresh_tracked_balances updates all tracked token balances."""
396
382
  # Track some tokens
397
383
  strategy._track_token("usd-coin-base")
398
384
  strategy._track_token("ethereum-base")
@@ -407,7 +393,6 @@ async def test_refresh_tracked_balances(strategy):
407
393
 
408
394
  @pytest.mark.asyncio
409
395
  async def test_deposit_tracks_usdc(strategy):
410
- """Test that deposit operation tracks USDC token."""
411
396
  # Clear tracked state
412
397
  strategy.tracked_token_ids.clear()
413
398
  strategy.tracked_balances.clear()
@@ -423,8 +408,6 @@ async def test_deposit_tracks_usdc(strategy):
423
408
 
424
409
  @pytest.mark.asyncio
425
410
  async def test_sweep_wallet_uses_tracked_tokens(strategy):
426
- """Test that _sweep_wallet only swaps tracked tokens."""
427
- # Import the real implementation to restore it
428
411
  from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import (
429
412
  StablecoinYieldStrategy,
430
413
  )
@@ -434,7 +417,6 @@ async def test_sweep_wallet_uses_tracked_tokens(strategy):
434
417
  strategy, StablecoinYieldStrategy
435
418
  )
436
419
 
437
- # Setup: track some tokens with balances
438
420
  strategy._track_token("token-1", 1000000)
439
421
  strategy._track_token("token-2", 2000000)
440
422
 
@@ -447,10 +429,8 @@ async def test_sweep_wallet_uses_tracked_tokens(strategy):
447
429
  async def get_balance_mock(query, **kwargs):
448
430
  token_id = query if isinstance(query, str) else (query or {}).get("token_id")
449
431
  balance = strategy.tracked_balances.get(token_id, 0)
450
- # Return the balance, ensuring it's an int
451
432
  return (True, int(balance) if balance else 0)
452
433
 
453
- # Create a new AsyncMock that will track calls
454
434
  new_mock = AsyncMock(side_effect=get_balance_mock)
455
435
  strategy.balance_adapter.get_balance = new_mock
456
436
 
@@ -465,7 +445,6 @@ async def test_sweep_wallet_uses_tracked_tokens(strategy):
465
445
  "chain": {"code": "base", "name": "Base"},
466
446
  }
467
447
 
468
- # Call sweep
469
448
  await strategy._sweep_wallet(target_token)
470
449
 
471
450
  # Verify that swap was called for tracked tokens (should be called twice, once for each token)
@@ -482,8 +461,6 @@ async def test_sweep_wallet_uses_tracked_tokens(strategy):
482
461
 
483
462
  @pytest.mark.asyncio
484
463
  async def test_get_non_gas_balances_uses_tracked_state(strategy):
485
- """Test that _get_non_gas_balances only checks tracked tokens."""
486
- # Setup tracked tokens
487
464
  usdc_token_id = "usd-coin-base"
488
465
  pool_token_id = "test-pool-base"
489
466
 
@@ -497,7 +474,6 @@ async def test_get_non_gas_balances_uses_tracked_state(strategy):
497
474
 
498
475
  strategy.balance_adapter.get_balance = AsyncMock(side_effect=_get_balance_effect)
499
476
 
500
- # Get non-gas balances
501
477
  balances = await strategy._get_non_gas_balances()
502
478
 
503
479
  # Verify only tracked tokens are returned (excluding gas)
@@ -508,10 +484,8 @@ async def test_get_non_gas_balances_uses_tracked_state(strategy):
508
484
 
509
485
  @pytest.mark.asyncio
510
486
  async def test_partial_liquidate_uses_tracked_tokens(strategy):
511
- """Test that partial_liquidate only liquidates tracked tokens."""
512
- # Setup tracked tokens with balances
513
- strategy._track_token("usd-coin-base", 50000000) # 50 USDC
514
- strategy._track_token("test-pool-base", 100000000000000000000) # 100 POOL tokens
487
+ strategy._track_token("usd-coin-base", 50000000)
488
+ strategy._track_token("test-pool-base", 100000000000000000000)
515
489
 
516
490
  # Mock balance and token adapters
517
491
  def _get_balance_effect_partial(query, **kwargs):
@@ -530,7 +504,6 @@ async def test_partial_liquidate_uses_tracked_tokens(strategy):
530
504
  return_value=(True, "Swap successful")
531
505
  )
532
506
 
533
- # Call partial liquidate
534
507
  ok, msg = await strategy.partial_liquidate(usd_value=75.0)
535
508
 
536
509
  # Verify success
@@ -540,12 +513,6 @@ async def test_partial_liquidate_uses_tracked_tokens(strategy):
540
513
 
541
514
  @pytest.mark.asyncio
542
515
  async def test_setup_handles_float_net_deposit(strategy):
543
- """Test that setup() correctly handles float from get_strategy_net_deposit.
544
-
545
- The ledger adapter returns (success, float) not (success, dict).
546
- This test ensures the strategy doesn't try to call .get() on the float,
547
- which would raise "'float' object has no attribute 'get'".
548
- """
549
516
  # Mock get_strategy_net_deposit to return float (not dict)
550
517
  strategy.ledger_adapter.get_strategy_net_deposit = AsyncMock(
551
518
  return_value=(True, 1500.0)