wayfinder-paths 0.1.11__py3-none-any.whl → 0.1.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of wayfinder-paths might be problematic. Click here for more details.
- wayfinder_paths/adapters/balance_adapter/README.md +13 -14
- wayfinder_paths/adapters/balance_adapter/adapter.py +36 -39
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +123 -0
- wayfinder_paths/adapters/brap_adapter/README.md +11 -16
- wayfinder_paths/adapters/brap_adapter/adapter.py +87 -75
- wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +121 -59
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +22 -23
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +114 -60
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1 -1
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +44 -5
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +104 -0
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +0 -3
- wayfinder_paths/adapters/pool_adapter/README.md +11 -27
- wayfinder_paths/adapters/pool_adapter/adapter.py +11 -37
- wayfinder_paths/adapters/pool_adapter/examples.json +6 -7
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +8 -8
- wayfinder_paths/adapters/token_adapter/README.md +2 -14
- wayfinder_paths/adapters/token_adapter/adapter.py +16 -10
- wayfinder_paths/adapters/token_adapter/examples.json +4 -8
- wayfinder_paths/adapters/token_adapter/test_adapter.py +5 -3
- wayfinder_paths/core/clients/BRAPClient.py +103 -62
- wayfinder_paths/core/clients/ClientManager.py +1 -68
- wayfinder_paths/core/clients/HyperlendClient.py +127 -66
- wayfinder_paths/core/clients/LedgerClient.py +1 -4
- wayfinder_paths/core/clients/PoolClient.py +126 -88
- wayfinder_paths/core/clients/TokenClient.py +92 -37
- wayfinder_paths/core/clients/WalletClient.py +28 -58
- wayfinder_paths/core/clients/WayfinderClient.py +33 -166
- wayfinder_paths/core/clients/__init__.py +0 -2
- wayfinder_paths/core/clients/protocols.py +35 -52
- wayfinder_paths/core/clients/sdk_example.py +37 -22
- wayfinder_paths/core/config.py +60 -224
- wayfinder_paths/core/engine/StrategyJob.py +7 -55
- wayfinder_paths/core/services/local_evm_txn.py +28 -10
- wayfinder_paths/core/services/local_token_txn.py +1 -1
- wayfinder_paths/core/strategies/Strategy.py +3 -5
- wayfinder_paths/core/strategies/descriptors.py +7 -0
- wayfinder_paths/core/utils/evm_helpers.py +7 -3
- wayfinder_paths/core/utils/wallets.py +12 -19
- wayfinder_paths/core/wallets/README.md +1 -1
- wayfinder_paths/run_strategy.py +8 -17
- wayfinder_paths/scripts/create_strategy.py +5 -5
- wayfinder_paths/scripts/make_wallets.py +5 -5
- wayfinder_paths/scripts/run_strategy.py +3 -3
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -1
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +206 -526
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +228 -11
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +41 -25
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +1 -1
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +10 -9
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +12 -6
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +3 -3
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +110 -78
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +44 -21
- wayfinder_paths/templates/adapter/README.md +1 -1
- wayfinder_paths/templates/strategy/README.md +3 -3
- wayfinder_paths/templates/strategy/test_strategy.py +3 -2
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/METADATA +21 -59
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/RECORD +64 -65
- wayfinder_paths/core/clients/AuthClient.py +0 -83
- wayfinder_paths/core/settings.py +0 -61
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/WHEEL +0 -0
|
@@ -523,7 +523,8 @@ async def test_atomic_deposit_iteration_swaps_from_eth_when_borrow_surfaces_as_e
|
|
|
523
523
|
WSTETH_TOKEN_ID: 0,
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
-
async def get_balance_side_effect(*,
|
|
526
|
+
async def get_balance_side_effect(*, query: str, wallet_address: str, **_):
|
|
527
|
+
token_id = query if isinstance(query, str) else (query or {}).get("token_id")
|
|
527
528
|
return (True, balances.get(token_id, 0))
|
|
528
529
|
|
|
529
530
|
strategy.balance_adapter.get_balance = AsyncMock(
|
|
@@ -587,7 +588,8 @@ async def test_complete_unpaired_weth_borrow_uses_eth_inventory(
|
|
|
587
588
|
WSTETH_TOKEN_ID: 0,
|
|
588
589
|
}
|
|
589
590
|
|
|
590
|
-
async def get_balance_side_effect(*,
|
|
591
|
+
async def get_balance_side_effect(*, query: str, wallet_address: str):
|
|
592
|
+
token_id = query if isinstance(query, str) else (query or {}).get("token_id")
|
|
591
593
|
return (True, balances.get(token_id, 0))
|
|
592
594
|
|
|
593
595
|
strategy.balance_adapter.get_balance = AsyncMock(
|
|
@@ -631,8 +633,10 @@ async def test_sweep_token_balances_sweeps_tokens(strategy, mock_adapter_respons
|
|
|
631
633
|
strategy.min_withdraw_usd = 1.0
|
|
632
634
|
|
|
633
635
|
# Mock balance returns (has some WETH dust)
|
|
634
|
-
def balance_side_effect(
|
|
635
|
-
if
|
|
636
|
+
def balance_side_effect(*, query, wallet_address, **_):
|
|
637
|
+
token_id = query if isinstance(query, str) else (query or {}).get("token_id")
|
|
638
|
+
token_id_str = (token_id or "").lower()
|
|
639
|
+
if "weth" in token_id_str:
|
|
636
640
|
return (True, 100 * 10**18) # 100 WETH
|
|
637
641
|
return (True, 0)
|
|
638
642
|
|
|
@@ -751,7 +755,8 @@ async def test_partial_liquidate_prefers_wsteth_when_excess(strategy):
|
|
|
751
755
|
# Wallet balances (raw)
|
|
752
756
|
balances: dict[str, int] = {USDC_TOKEN_ID: 0, WSTETH_TOKEN_ID: 0}
|
|
753
757
|
|
|
754
|
-
async def mock_get_balance(*,
|
|
758
|
+
async def mock_get_balance(*, query: str, wallet_address: str):
|
|
759
|
+
token_id = query if isinstance(query, str) else (query or {}).get("token_id")
|
|
755
760
|
return (True, balances.get(token_id, 0))
|
|
756
761
|
|
|
757
762
|
strategy.balance_adapter.get_balance = AsyncMock(side_effect=mock_get_balance)
|
|
@@ -839,7 +844,8 @@ async def test_partial_liquidate_uses_usdc_collateral_when_no_wsteth_excess(stra
|
|
|
839
844
|
|
|
840
845
|
balances: dict[str, int] = {USDC_TOKEN_ID: 0}
|
|
841
846
|
|
|
842
|
-
async def mock_get_balance(*,
|
|
847
|
+
async def mock_get_balance(*, query: str, wallet_address: str):
|
|
848
|
+
token_id = query if isinstance(query, str) else (query or {}).get("token_id")
|
|
843
849
|
return (True, balances.get(token_id, 0))
|
|
844
850
|
|
|
845
851
|
strategy.balance_adapter.get_balance = AsyncMock(side_effect=mock_get_balance)
|
|
@@ -62,7 +62,7 @@ Transactions are scoped to the strategy wallet and Enso Router approval/swap cal
|
|
|
62
62
|
### Withdraw
|
|
63
63
|
|
|
64
64
|
- Requires a prior deposit (the strategy tracks `self.DEPOSIT_USDC`).
|
|
65
|
-
- Reads the pool balance via `BalanceAdapter.
|
|
65
|
+
- Reads the pool balance via `BalanceAdapter.get_balance` (with pool address and chain_id), unwinds via BRAP swaps back to USDC, and moves USDC from the strategy wallet to the main wallet via `BalanceAdapter.move_from_strategy_wallet_to_main_wallet`.
|
|
66
66
|
- Updates the ledger and clears cached pool state.
|
|
67
67
|
|
|
68
68
|
## Running locally
|
|
@@ -71,7 +71,7 @@ Transactions are scoped to the strategy wallet and Enso Router approval/swap cal
|
|
|
71
71
|
# Install dependencies
|
|
72
72
|
poetry install
|
|
73
73
|
|
|
74
|
-
# Generate main wallet (writes
|
|
74
|
+
# Generate main wallet (writes config.json)
|
|
75
75
|
# Creates a main wallet (or use 'just create-strategy' which auto-creates wallets)
|
|
76
76
|
poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
|
|
77
77
|
|
|
@@ -86,4 +86,4 @@ poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy --ac
|
|
|
86
86
|
poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy --action update --config $(pwd)/config.json
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
-
Wallet addresses are auto-populated from `
|
|
89
|
+
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.
|
|
@@ -65,6 +65,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
65
65
|
f"Continuously optimizes positions for maximum stable yield while avoiding impermanent loss. "
|
|
66
66
|
f"Min: {MIN_AMOUNT_USDC} USDC + ETH gas. Filters for ${MIN_TVL:,}+ TVL protocols."
|
|
67
67
|
),
|
|
68
|
+
risk_description=f"Protocol risk is always present when engaging with DeFi strategies, this includes underlying DeFi protocols and Wayfinder itself. Additional risks include temporary yield fluctuations, gas costs during rebalancing, and potential brief capital lock-up during protocol transitions. Strategy filters for protocols with a minimum TVL of ${MIN_TVL:,} to ensure low-risk exposure.",
|
|
68
69
|
gas_token_symbol="ETH",
|
|
69
70
|
gas_token_id="ethereum-base",
|
|
70
71
|
deposit_token_id="usd-coin-base",
|
|
@@ -156,9 +157,8 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
156
157
|
main_wallet: dict[str, Any] | None = None,
|
|
157
158
|
strategy_wallet: dict[str, Any] | None = None,
|
|
158
159
|
web3_service=None,
|
|
159
|
-
api_key: str | None = None,
|
|
160
160
|
):
|
|
161
|
-
super().__init__(
|
|
161
|
+
super().__init__()
|
|
162
162
|
merged_config: dict[str, Any] = dict(config or {})
|
|
163
163
|
if main_wallet is not None:
|
|
164
164
|
merged_config["main_wallet"] = main_wallet
|
|
@@ -270,7 +270,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
270
270
|
for token_id in self.tracked_token_ids:
|
|
271
271
|
try:
|
|
272
272
|
success, balance_wei = await self.balance_adapter.get_balance(
|
|
273
|
-
|
|
273
|
+
query=token_id,
|
|
274
274
|
wallet_address=strategy_address,
|
|
275
275
|
)
|
|
276
276
|
if success and balance_wei:
|
|
@@ -303,8 +303,8 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
303
303
|
success, deposit_data = await self.ledger_adapter.get_strategy_net_deposit(
|
|
304
304
|
wallet_address=strategy_address,
|
|
305
305
|
)
|
|
306
|
-
if success:
|
|
307
|
-
self.DEPOSIT_USDC = deposit_data
|
|
306
|
+
if success and deposit_data is not None:
|
|
307
|
+
self.DEPOSIT_USDC = float(deposit_data)
|
|
308
308
|
logger.info(f"Strategy net deposit: {self.DEPOSIT_USDC} USDC")
|
|
309
309
|
else:
|
|
310
310
|
logger.error(f"Failed to fetch strategy net deposit: {deposit_data}")
|
|
@@ -359,8 +359,8 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
359
359
|
f"Gas token loaded: {gas_token_data.get('symbol', 'Unknown')}"
|
|
360
360
|
)
|
|
361
361
|
# Track gas token (but don't count it as a strategy asset)
|
|
362
|
-
if self.gas_token.get("
|
|
363
|
-
self._track_token(self.gas_token.get("
|
|
362
|
+
if self.gas_token.get("token_id"):
|
|
363
|
+
self._track_token(self.gas_token.get("token_id"))
|
|
364
364
|
else:
|
|
365
365
|
logger.warning("Failed to fetch gas token info, using empty dict")
|
|
366
366
|
self.gas_token = {}
|
|
@@ -427,35 +427,46 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
427
427
|
self._track_token(token_info.get("token_id"))
|
|
428
428
|
|
|
429
429
|
success, reports = await self.pool_adapter.get_pools_by_ids(
|
|
430
|
-
pool_ids=[self.current_pool.get("token_id")]
|
|
431
|
-
merge_external=False,
|
|
430
|
+
pool_ids=[self.current_pool.get("token_id")]
|
|
432
431
|
)
|
|
433
432
|
if success and reports.get("pools"):
|
|
434
433
|
self.current_pool_data = reports.get("pools", [])[0]
|
|
435
434
|
|
|
436
|
-
|
|
435
|
+
pool_ids = []
|
|
437
436
|
pool_id = self.current_pool.get("token_id", None)
|
|
438
437
|
if isinstance(pool_id, str):
|
|
439
|
-
|
|
438
|
+
pool_ids.append(pool_id)
|
|
440
439
|
|
|
441
440
|
pool_address = self.current_pool.get("address", None)
|
|
442
441
|
pool_chain = self.current_pool.get("chain", None)
|
|
443
442
|
chain_code = ((pool_chain or {}).get("code")) or None
|
|
444
443
|
if isinstance(pool_address, str) and isinstance(chain_code, str):
|
|
445
|
-
|
|
444
|
+
pool_ids.append(f"{chain_code.lower()}_{pool_address.lower()}")
|
|
446
445
|
|
|
447
446
|
llama_report = None
|
|
448
|
-
if
|
|
449
|
-
success,
|
|
450
|
-
|
|
447
|
+
if pool_ids:
|
|
448
|
+
success, pool_list_response = await self.pool_adapter.get_pools_by_ids(
|
|
449
|
+
pool_ids=pool_ids
|
|
451
450
|
)
|
|
452
|
-
if success:
|
|
453
|
-
|
|
451
|
+
if success and isinstance(pool_list_response, dict):
|
|
452
|
+
pools = pool_list_response.get("pools", [])
|
|
453
|
+
# Search for matching pool by id or constructed identifier
|
|
454
|
+
for identifier in pool_ids:
|
|
454
455
|
if not isinstance(identifier, str):
|
|
455
456
|
continue
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
457
|
+
identifier_lower = identifier.lower()
|
|
458
|
+
for pool in pools:
|
|
459
|
+
pool_id = pool.get("id", "").lower()
|
|
460
|
+
pool_address = pool.get("address", "").lower()
|
|
461
|
+
pool_chain_code = pool.get("chain_code", "").lower()
|
|
462
|
+
constructed_id = f"{pool_chain_code}_{pool_address}"
|
|
463
|
+
if (
|
|
464
|
+
pool_id == identifier_lower
|
|
465
|
+
or constructed_id == identifier_lower
|
|
466
|
+
):
|
|
467
|
+
llama_report = pool
|
|
468
|
+
break
|
|
469
|
+
if llama_report:
|
|
459
470
|
break
|
|
460
471
|
|
|
461
472
|
if self.current_pool_data is None and llama_report:
|
|
@@ -464,14 +475,21 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
464
475
|
"llama_report": llama_report,
|
|
465
476
|
}
|
|
466
477
|
|
|
467
|
-
if llama_report and llama_report.get("
|
|
478
|
+
if llama_report and llama_report.get("combined_apy_pct") is not None:
|
|
468
479
|
self.current_combined_apy_pct = (
|
|
469
|
-
llama_report.get("
|
|
480
|
+
llama_report.get("combined_apy_pct", 0) / 100
|
|
470
481
|
)
|
|
471
|
-
elif llama_report and llama_report.get("
|
|
472
|
-
self.current_combined_apy_pct = llama_report.get("
|
|
482
|
+
elif llama_report and llama_report.get("apy") is not None:
|
|
483
|
+
self.current_combined_apy_pct = llama_report.get("apy", 0) / 100
|
|
473
484
|
elif self.current_pool_data:
|
|
474
|
-
|
|
485
|
+
apy_pct = self.current_pool_data.get("combined_apy_pct")
|
|
486
|
+
if apy_pct is not None:
|
|
487
|
+
self.current_combined_apy_pct = float(apy_pct) / 100
|
|
488
|
+
else:
|
|
489
|
+
apy_val = self.current_pool_data.get("apy", 0)
|
|
490
|
+
self.current_combined_apy_pct = (
|
|
491
|
+
float(apy_val) / 100 if apy_val is not None else 0
|
|
492
|
+
)
|
|
475
493
|
|
|
476
494
|
pool_address = self.current_pool.get("address")
|
|
477
495
|
chain_id = self.current_pool.get("chain", {}).get("id")
|
|
@@ -486,10 +504,10 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
486
504
|
(
|
|
487
505
|
success,
|
|
488
506
|
current_pool_balance_raw,
|
|
489
|
-
) = await self.balance_adapter.
|
|
490
|
-
|
|
507
|
+
) = await self.balance_adapter.get_balance(
|
|
508
|
+
query=pool_address,
|
|
509
|
+
wallet_address=user_address,
|
|
491
510
|
chain_id=chain_id,
|
|
492
|
-
user_address=user_address,
|
|
493
511
|
)
|
|
494
512
|
self.current_pool_balance = current_pool_balance_raw if success else 0
|
|
495
513
|
except Exception as e:
|
|
@@ -525,8 +543,8 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
525
543
|
self.current_pool_balance = inferred_balance
|
|
526
544
|
if inferred_entry:
|
|
527
545
|
self.current_pool_data = inferred_entry
|
|
528
|
-
llama_combined = inferred_entry.get("
|
|
529
|
-
llama_apy = inferred_entry.get("
|
|
546
|
+
llama_combined = inferred_entry.get("combined_apy_pct")
|
|
547
|
+
llama_apy = inferred_entry.get("apy")
|
|
530
548
|
if llama_combined is not None:
|
|
531
549
|
self.current_combined_apy_pct = float(llama_combined) / 100
|
|
532
550
|
elif llama_apy is not None:
|
|
@@ -535,7 +553,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
535
553
|
|
|
536
554
|
if self.usdc_token_info:
|
|
537
555
|
status, raw_balance = await self.balance_adapter.get_balance(
|
|
538
|
-
|
|
556
|
+
query=self.usdc_token_info.get("token_id"),
|
|
539
557
|
wallet_address=self._get_strategy_wallet_address(),
|
|
540
558
|
)
|
|
541
559
|
if not status or not raw_balance:
|
|
@@ -578,7 +596,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
578
596
|
await self._refresh_tracked_balances()
|
|
579
597
|
|
|
580
598
|
usdc_token_id = self.usdc_token_info.get("token_id")
|
|
581
|
-
gas_token_id = self.gas_token.get("
|
|
599
|
+
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
582
600
|
|
|
583
601
|
best_token_id = None
|
|
584
602
|
best_balance_wei = 0
|
|
@@ -609,7 +627,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
609
627
|
strategy_address = self._get_strategy_wallet_address()
|
|
610
628
|
try:
|
|
611
629
|
success, onchain_balance = await self.balance_adapter.get_balance(
|
|
612
|
-
|
|
630
|
+
query=token.get("token_id"),
|
|
613
631
|
wallet_address=strategy_address,
|
|
614
632
|
)
|
|
615
633
|
if success and onchain_balance:
|
|
@@ -635,7 +653,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
635
653
|
token_id = balance.get("token_id")
|
|
636
654
|
if (
|
|
637
655
|
isinstance(token_id, str)
|
|
638
|
-
and token_id.lower() == self.gas_token.get("
|
|
656
|
+
and token_id.lower() == self.gas_token.get("token_id", "").lower()
|
|
639
657
|
):
|
|
640
658
|
return True
|
|
641
659
|
|
|
@@ -680,7 +698,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
680
698
|
"address": token_info.get("address"),
|
|
681
699
|
"chain": token_info.get("chain"),
|
|
682
700
|
}
|
|
683
|
-
gas_token_id = self.gas_token.get("
|
|
701
|
+
gas_token_id = self.gas_token.get("token_id")
|
|
684
702
|
logger.info(
|
|
685
703
|
f"Current pool set to: {token_info.get('symbol')} on {token_info.get('chain', {}).get('name')}"
|
|
686
704
|
)
|
|
@@ -692,7 +710,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
692
710
|
main_usdc_status,
|
|
693
711
|
main_usdc_balance,
|
|
694
712
|
) = await self.balance_adapter.get_balance(
|
|
695
|
-
|
|
713
|
+
query=token_info.get("token_id"),
|
|
696
714
|
wallet_address=self._get_main_wallet_address(),
|
|
697
715
|
)
|
|
698
716
|
if main_usdc_status and main_usdc_balance is not None:
|
|
@@ -737,7 +755,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
737
755
|
_,
|
|
738
756
|
main_gas_raw,
|
|
739
757
|
) = await self.balance_adapter.get_balance(
|
|
740
|
-
|
|
758
|
+
query=gas_token_id,
|
|
741
759
|
wallet_address=self._get_main_wallet_address(),
|
|
742
760
|
)
|
|
743
761
|
main_gas_int = (
|
|
@@ -762,14 +780,14 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
762
780
|
_,
|
|
763
781
|
main_gas_raw,
|
|
764
782
|
) = await self.balance_adapter.get_balance(
|
|
765
|
-
|
|
783
|
+
query=gas_token_id,
|
|
766
784
|
wallet_address=self._get_main_wallet_address(),
|
|
767
785
|
)
|
|
768
786
|
(
|
|
769
787
|
_,
|
|
770
788
|
strategy_gas_raw,
|
|
771
789
|
) = await self.balance_adapter.get_balance(
|
|
772
|
-
|
|
790
|
+
query=gas_token_id,
|
|
773
791
|
wallet_address=self._get_strategy_wallet_address(),
|
|
774
792
|
)
|
|
775
793
|
main_gas_int = (
|
|
@@ -895,10 +913,10 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
895
913
|
(
|
|
896
914
|
_,
|
|
897
915
|
self.current_pool_balance,
|
|
898
|
-
) = await self.balance_adapter.
|
|
899
|
-
|
|
916
|
+
) = await self.balance_adapter.get_balance(
|
|
917
|
+
query=self.current_pool.get("address"),
|
|
918
|
+
wallet_address=self._get_strategy_wallet_address(),
|
|
900
919
|
chain_id=self.current_pool.get("chain").get("id"),
|
|
901
|
-
user_address=self._get_strategy_wallet_address(),
|
|
902
920
|
)
|
|
903
921
|
logger.info(f"Current pool balance: {self.current_pool_balance}")
|
|
904
922
|
except Exception as e:
|
|
@@ -931,8 +949,8 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
931
949
|
)
|
|
932
950
|
if (
|
|
933
951
|
success
|
|
934
|
-
and
|
|
935
|
-
and quotes.get("
|
|
952
|
+
and isinstance(quotes, dict)
|
|
953
|
+
and quotes.get("best_quote")
|
|
936
954
|
):
|
|
937
955
|
logger.info("Successfully obtained swap quote")
|
|
938
956
|
break
|
|
@@ -941,7 +959,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
941
959
|
if attempt == 3: # Last attempt
|
|
942
960
|
logger.error("All quote attempts failed")
|
|
943
961
|
|
|
944
|
-
best_quote = quotes.get("
|
|
962
|
+
best_quote = quotes.get("best_quote") if isinstance(quotes, dict) else None
|
|
945
963
|
if not best_quote:
|
|
946
964
|
return (
|
|
947
965
|
False,
|
|
@@ -998,7 +1016,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
998
1016
|
if self.usdc_token_info.get("token_id") in withdrawn_token_ids:
|
|
999
1017
|
pass
|
|
1000
1018
|
status, raw_balance = await self.balance_adapter.get_balance(
|
|
1001
|
-
|
|
1019
|
+
query=self.usdc_token_info.get("token_id"),
|
|
1002
1020
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1003
1021
|
)
|
|
1004
1022
|
if not status or not raw_balance:
|
|
@@ -1025,9 +1043,9 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1025
1043
|
)
|
|
1026
1044
|
withdrawn_token_ids.add(self.usdc_token_info.get("token_id"))
|
|
1027
1045
|
|
|
1028
|
-
if self.gas_token and self.gas_token.get("
|
|
1046
|
+
if self.gas_token and self.gas_token.get("token_id") not in withdrawn_token_ids:
|
|
1029
1047
|
status, raw_gas = await self.balance_adapter.get_balance(
|
|
1030
|
-
|
|
1048
|
+
query=self.gas_token.get("token_id"),
|
|
1031
1049
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1032
1050
|
)
|
|
1033
1051
|
if status and raw_gas:
|
|
@@ -1039,7 +1057,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1039
1057
|
move_gas_status,
|
|
1040
1058
|
move_gas_message,
|
|
1041
1059
|
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
1042
|
-
self.gas_token.get("
|
|
1060
|
+
self.gas_token.get("token_id"),
|
|
1043
1061
|
gas_amount,
|
|
1044
1062
|
strategy_name=self.name,
|
|
1045
1063
|
)
|
|
@@ -1051,7 +1069,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1051
1069
|
float(gas_amount),
|
|
1052
1070
|
)
|
|
1053
1071
|
)
|
|
1054
|
-
withdrawn_token_ids.add(self.gas_token.get("
|
|
1072
|
+
withdrawn_token_ids.add(self.gas_token.get("token_id"))
|
|
1055
1073
|
|
|
1056
1074
|
self.DEPOSIT_USDC = 0
|
|
1057
1075
|
self.current_pool_balance = 0
|
|
@@ -1206,10 +1224,17 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1206
1224
|
else:
|
|
1207
1225
|
self.current_pool_data = None
|
|
1208
1226
|
if self.current_pool_data:
|
|
1209
|
-
|
|
1227
|
+
apy_pct = self.current_pool_data.get("combined_apy_pct")
|
|
1228
|
+
if apy_pct is not None:
|
|
1229
|
+
self.current_combined_apy_pct = float(apy_pct) / 100
|
|
1230
|
+
else:
|
|
1231
|
+
apy_val = self.current_pool_data.get("apy", 0)
|
|
1232
|
+
self.current_combined_apy_pct = (
|
|
1233
|
+
float(apy_val) / 100 if apy_val is not None else 0
|
|
1234
|
+
)
|
|
1210
1235
|
else:
|
|
1211
1236
|
self.current_combined_apy_pct = (
|
|
1212
|
-
target_pool_data.get("
|
|
1237
|
+
target_pool_data.get("combined_apy_pct", 0) / 100
|
|
1213
1238
|
if target_pool_data
|
|
1214
1239
|
else 0
|
|
1215
1240
|
)
|
|
@@ -1242,10 +1267,10 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1242
1267
|
(
|
|
1243
1268
|
_,
|
|
1244
1269
|
refreshed_pool_balance,
|
|
1245
|
-
) = await self.balance_adapter.
|
|
1246
|
-
|
|
1270
|
+
) = await self.balance_adapter.get_balance(
|
|
1271
|
+
query=pool.get("address"),
|
|
1272
|
+
wallet_address=strategy_address,
|
|
1247
1273
|
chain_id=pool.get("chain").get("id"),
|
|
1248
|
-
user_address=strategy_address,
|
|
1249
1274
|
)
|
|
1250
1275
|
self.current_pool_balance = int(refreshed_pool_balance)
|
|
1251
1276
|
except Exception:
|
|
@@ -1259,7 +1284,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1259
1284
|
target_token_id = target_token.get("token_id")
|
|
1260
1285
|
target_chain = target_token.get("chain").get("code", "").lower()
|
|
1261
1286
|
target_address = target_token.get("address", "").lower()
|
|
1262
|
-
gas_token_id = self.gas_token.get("
|
|
1287
|
+
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
1263
1288
|
|
|
1264
1289
|
# Swap all non-target, non-gas tokens to the target
|
|
1265
1290
|
for token_id, balance_wei in list(self.tracked_balances.items()):
|
|
@@ -1278,7 +1303,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1278
1303
|
# Get fresh balance to ensure accuracy
|
|
1279
1304
|
try:
|
|
1280
1305
|
success, fresh_balance = await self.balance_adapter.get_balance(
|
|
1281
|
-
|
|
1306
|
+
query=token_id,
|
|
1282
1307
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1283
1308
|
)
|
|
1284
1309
|
if not success or not fresh_balance or int(fresh_balance) <= 0:
|
|
@@ -1318,7 +1343,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1318
1343
|
# Refresh target token balance
|
|
1319
1344
|
try:
|
|
1320
1345
|
success, target_balance = await self.balance_adapter.get_balance(
|
|
1321
|
-
|
|
1346
|
+
query=target_token_id,
|
|
1322
1347
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1323
1348
|
)
|
|
1324
1349
|
if success and target_balance:
|
|
@@ -1335,7 +1360,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1335
1360
|
|
|
1336
1361
|
required_gas = int(self.MIN_GAS * (10 ** self.gas_token.get("decimals")))
|
|
1337
1362
|
_, current_gas = await self.balance_adapter.get_balance(
|
|
1338
|
-
|
|
1363
|
+
query=self.gas_token.get("token_id"),
|
|
1339
1364
|
wallet_address=strategy_address,
|
|
1340
1365
|
)
|
|
1341
1366
|
if current_gas >= required_gas:
|
|
@@ -1395,7 +1420,11 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1395
1420
|
if not success:
|
|
1396
1421
|
return 0.0
|
|
1397
1422
|
|
|
1398
|
-
best_quote =
|
|
1423
|
+
best_quote = (
|
|
1424
|
+
exit_quotes.get("best_quote") if isinstance(exit_quotes, dict) else None
|
|
1425
|
+
)
|
|
1426
|
+
if not best_quote:
|
|
1427
|
+
return None
|
|
1399
1428
|
current_pool_usd_value = best_quote.get("output_amount")
|
|
1400
1429
|
|
|
1401
1430
|
return float(
|
|
@@ -1407,7 +1436,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1407
1436
|
# Refresh tracked balances
|
|
1408
1437
|
await self._refresh_tracked_balances()
|
|
1409
1438
|
|
|
1410
|
-
gas_token_id = self.gas_token.get("
|
|
1439
|
+
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
1411
1440
|
results = []
|
|
1412
1441
|
|
|
1413
1442
|
for token_id, balance_wei in self.tracked_balances.items():
|
|
@@ -1440,21 +1469,24 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1440
1469
|
return results
|
|
1441
1470
|
|
|
1442
1471
|
async def _find_best_pool(self) -> tuple[bool, dict[str, Any]]:
|
|
1443
|
-
|
|
1472
|
+
chain_id = (self.usdc_token_info or {}).get("chain", {}).get("id")
|
|
1473
|
+
if chain_id is None:
|
|
1474
|
+
chain_id = 8453
|
|
1475
|
+
success, llama_data = await self.pool_adapter.get_pools(chain_id=chain_id)
|
|
1444
1476
|
if not success:
|
|
1445
1477
|
return False, {"message": f"Failed to fetch Llama data: {llama_data}"}
|
|
1446
1478
|
|
|
1447
1479
|
llama_pools = [
|
|
1448
1480
|
pool
|
|
1449
1481
|
for pool in llama_data.get("matches", [])
|
|
1450
|
-
if pool.get("
|
|
1451
|
-
and pool.get("
|
|
1452
|
-
and pool.get("
|
|
1453
|
-
and pool.get("
|
|
1482
|
+
if pool.get("stablecoin")
|
|
1483
|
+
and pool.get("ilRisk") == "no"
|
|
1484
|
+
and pool.get("tvlUsd") > self.MIN_TVL
|
|
1485
|
+
and pool.get("combined_apy_pct") > self.DUST_APY
|
|
1454
1486
|
and pool.get("network", "").lower() in self.SUPPORTED_NETWORK_CODES
|
|
1455
1487
|
]
|
|
1456
1488
|
llama_pools = sorted(
|
|
1457
|
-
llama_pools, key=lambda pool: pool.get("
|
|
1489
|
+
llama_pools, key=lambda pool: pool.get("combined_apy_pct"), reverse=True
|
|
1458
1490
|
)
|
|
1459
1491
|
if not llama_pools:
|
|
1460
1492
|
return False, {"message": "No suitable pools found."}
|
|
@@ -1469,11 +1501,11 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1469
1501
|
)
|
|
1470
1502
|
if not target_status and candidate.get("token_id"):
|
|
1471
1503
|
target_status, target_pool = await self.token_adapter.get_token(
|
|
1472
|
-
|
|
1504
|
+
query=candidate.get("token_id")
|
|
1473
1505
|
)
|
|
1474
1506
|
if not target_status and candidate.get("pool_id"):
|
|
1475
1507
|
target_status, target_pool = await self.token_adapter.get_token(
|
|
1476
|
-
|
|
1508
|
+
query=candidate.get("pool_id")
|
|
1477
1509
|
)
|
|
1478
1510
|
if not target_status:
|
|
1479
1511
|
continue
|
|
@@ -1515,7 +1547,8 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1515
1547
|
return None
|
|
1516
1548
|
|
|
1517
1549
|
try:
|
|
1518
|
-
|
|
1550
|
+
apy_pct = pool_data.get("combined_apy_pct") or pool_data.get("apy") or 0
|
|
1551
|
+
combined_apy_pct = float(apy_pct) / 100
|
|
1519
1552
|
success, quotes = await self.brap_adapter.get_swap_quote(
|
|
1520
1553
|
from_token_address=current_token.get("address"),
|
|
1521
1554
|
to_token_address=token.get("address"),
|
|
@@ -1527,10 +1560,9 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1527
1560
|
)
|
|
1528
1561
|
if not success:
|
|
1529
1562
|
return None
|
|
1530
|
-
|
|
1531
|
-
if not isinstance(quotes_data, dict):
|
|
1563
|
+
if not isinstance(quotes, dict):
|
|
1532
1564
|
return None
|
|
1533
|
-
best_quote =
|
|
1565
|
+
best_quote = quotes.get("best_quote")
|
|
1534
1566
|
if not best_quote:
|
|
1535
1567
|
return None
|
|
1536
1568
|
|
|
@@ -1582,7 +1614,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1582
1614
|
async def _status(self) -> StatusDict:
|
|
1583
1615
|
# Get ETH gas balance
|
|
1584
1616
|
gas_success, gas_balance_wei = await self.balance_adapter.get_balance(
|
|
1585
|
-
|
|
1617
|
+
query=self.gas_token.get("token_id"),
|
|
1586
1618
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1587
1619
|
)
|
|
1588
1620
|
gas_balance = (
|
|
@@ -1611,7 +1643,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1611
1643
|
|
|
1612
1644
|
# Calculate total value from tracked non-gas balances
|
|
1613
1645
|
total_value = 0.0
|
|
1614
|
-
gas_token_id = self.gas_token.get("
|
|
1646
|
+
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
1615
1647
|
|
|
1616
1648
|
for token_id, balance_wei in self.tracked_balances.items():
|
|
1617
1649
|
if token_id == gas_token_id:
|
|
@@ -1680,7 +1712,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1680
1712
|
|
|
1681
1713
|
usdc_token_id = self.usdc_token_info.get("token_id")
|
|
1682
1714
|
usdc_decimals = self.usdc_token_info.get("decimals")
|
|
1683
|
-
gas_token_id = self.gas_token.get("
|
|
1715
|
+
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
1684
1716
|
|
|
1685
1717
|
# Check current USDC balance
|
|
1686
1718
|
available_usdc_wei = self.tracked_balances.get(usdc_token_id, 0)
|
|
@@ -1745,7 +1777,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1745
1777
|
|
|
1746
1778
|
# Refresh USDC balance after swaps
|
|
1747
1779
|
success, usdc_wei = await self.balance_adapter.get_balance(
|
|
1748
|
-
|
|
1780
|
+
query=usdc_token_id,
|
|
1749
1781
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1750
1782
|
)
|
|
1751
1783
|
if success and usdc_wei:
|
|
@@ -1790,7 +1822,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1790
1822
|
|
|
1791
1823
|
# Refresh USDC balance again
|
|
1792
1824
|
success, usdc_wei = await self.balance_adapter.get_balance(
|
|
1793
|
-
|
|
1825
|
+
query=usdc_token_id,
|
|
1794
1826
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1795
1827
|
)
|
|
1796
1828
|
if success and usdc_wei:
|