wayfinder-paths 0.1.13__py3-none-any.whl → 0.1.15__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 +73 -32
- 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 +144 -78
- wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +127 -65
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +30 -14
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +121 -67
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +12 -12
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +332 -9
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +13 -13
- wayfinder_paths/adapters/pool_adapter/README.md +9 -10
- wayfinder_paths/adapters/pool_adapter/adapter.py +9 -10
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +2 -2
- 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 +9 -7
- wayfinder_paths/core/clients/BRAPClient.py +102 -61
- wayfinder_paths/core/clients/ClientManager.py +1 -68
- wayfinder_paths/core/clients/HyperlendClient.py +125 -64
- wayfinder_paths/core/clients/LedgerClient.py +1 -4
- wayfinder_paths/core/clients/PoolClient.py +122 -48
- wayfinder_paths/core/clients/TokenClient.py +91 -36
- wayfinder_paths/core/clients/WalletClient.py +26 -56
- wayfinder_paths/core/clients/WayfinderClient.py +28 -160
- wayfinder_paths/core/clients/__init__.py +0 -2
- wayfinder_paths/core/clients/protocols.py +35 -46
- wayfinder_paths/core/clients/sdk_example.py +37 -22
- wayfinder_paths/core/constants/erc20_abi.py +0 -11
- wayfinder_paths/core/engine/StrategyJob.py +10 -56
- wayfinder_paths/core/services/base.py +1 -0
- wayfinder_paths/core/services/local_evm_txn.py +25 -9
- wayfinder_paths/core/services/local_token_txn.py +2 -6
- wayfinder_paths/core/services/test_local_evm_txn.py +145 -0
- wayfinder_paths/core/strategies/Strategy.py +16 -4
- wayfinder_paths/core/utils/evm_helpers.py +2 -9
- wayfinder_paths/policies/erc20.py +1 -1
- wayfinder_paths/run_strategy.py +13 -19
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +77 -11
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +6 -6
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +107 -23
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +6 -5
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2246 -1279
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +276 -109
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +1 -1
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +153 -56
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +16 -12
- 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.13.dist-info → wayfinder_paths-0.1.15.dist-info}/METADATA +14 -49
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/RECORD +59 -60
- wayfinder_paths/abis/generic/erc20.json +0 -383
- wayfinder_paths/core/clients/AuthClient.py +0 -83
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/WHEEL +0 -0
|
@@ -157,9 +157,8 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
157
157
|
main_wallet: dict[str, Any] | None = None,
|
|
158
158
|
strategy_wallet: dict[str, Any] | None = None,
|
|
159
159
|
web3_service=None,
|
|
160
|
-
api_key: str | None = None,
|
|
161
160
|
):
|
|
162
|
-
super().__init__(
|
|
161
|
+
super().__init__()
|
|
163
162
|
merged_config: dict[str, Any] = dict(config or {})
|
|
164
163
|
if main_wallet is not None:
|
|
165
164
|
merged_config["main_wallet"] = main_wallet
|
|
@@ -271,7 +270,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
271
270
|
for token_id in self.tracked_token_ids:
|
|
272
271
|
try:
|
|
273
272
|
success, balance_wei = await self.balance_adapter.get_balance(
|
|
274
|
-
|
|
273
|
+
query=token_id,
|
|
275
274
|
wallet_address=strategy_address,
|
|
276
275
|
)
|
|
277
276
|
if success and balance_wei:
|
|
@@ -360,8 +359,8 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
360
359
|
f"Gas token loaded: {gas_token_data.get('symbol', 'Unknown')}"
|
|
361
360
|
)
|
|
362
361
|
# Track gas token (but don't count it as a strategy asset)
|
|
363
|
-
if self.gas_token.get("
|
|
364
|
-
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"))
|
|
365
364
|
else:
|
|
366
365
|
logger.warning("Failed to fetch gas token info, using empty dict")
|
|
367
366
|
self.gas_token = {}
|
|
@@ -446,16 +445,28 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
446
445
|
|
|
447
446
|
llama_report = None
|
|
448
447
|
if pool_ids:
|
|
449
|
-
success,
|
|
448
|
+
success, pool_list_response = await self.pool_adapter.get_pools_by_ids(
|
|
450
449
|
pool_ids=pool_ids
|
|
451
450
|
)
|
|
452
|
-
if success:
|
|
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
|
|
453
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:
|
|
@@ -471,7 +482,14 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
471
482
|
elif llama_report and llama_report.get("apy") is not None:
|
|
472
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:
|
|
@@ -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
|
|
@@ -1071,6 +1089,71 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1071
1089
|
f"Successfully withdrew {amount} USDC from strategy: {breakdown_msg}",
|
|
1072
1090
|
)
|
|
1073
1091
|
|
|
1092
|
+
async def exit(self, **kwargs) -> StatusTuple:
|
|
1093
|
+
"""Transfer funds from strategy wallet to main wallet."""
|
|
1094
|
+
logger.info("EXIT: Transferring remaining funds to main wallet")
|
|
1095
|
+
|
|
1096
|
+
strategy_address = self._get_strategy_wallet_address()
|
|
1097
|
+
main_address = self._get_main_wallet_address()
|
|
1098
|
+
|
|
1099
|
+
if strategy_address.lower() == main_address.lower():
|
|
1100
|
+
return (True, "Main wallet is strategy wallet, no transfer needed")
|
|
1101
|
+
|
|
1102
|
+
transferred_items = []
|
|
1103
|
+
|
|
1104
|
+
# Transfer USDC to main wallet
|
|
1105
|
+
usdc_ok, usdc_raw = await self.balance_adapter.get_balance(
|
|
1106
|
+
token_id="usd-coin-base",
|
|
1107
|
+
wallet_address=strategy_address,
|
|
1108
|
+
)
|
|
1109
|
+
if usdc_ok and usdc_raw:
|
|
1110
|
+
usdc_balance = float(usdc_raw.get("balance", 0))
|
|
1111
|
+
if usdc_balance > 1.0:
|
|
1112
|
+
logger.info(f"Transferring {usdc_balance:.2f} USDC to main wallet")
|
|
1113
|
+
(
|
|
1114
|
+
success,
|
|
1115
|
+
msg,
|
|
1116
|
+
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
1117
|
+
query="usd-coin-base",
|
|
1118
|
+
amount=usdc_balance,
|
|
1119
|
+
strategy_name=self.name,
|
|
1120
|
+
skip_ledger=False,
|
|
1121
|
+
)
|
|
1122
|
+
if success:
|
|
1123
|
+
transferred_items.append(f"{usdc_balance:.2f} USDC")
|
|
1124
|
+
else:
|
|
1125
|
+
logger.warning(f"USDC transfer failed: {msg}")
|
|
1126
|
+
|
|
1127
|
+
# Transfer ETH (minus reserve for tx fees) to main wallet
|
|
1128
|
+
eth_ok, eth_raw = await self.balance_adapter.get_balance(
|
|
1129
|
+
token_id="ethereum-base",
|
|
1130
|
+
wallet_address=strategy_address,
|
|
1131
|
+
)
|
|
1132
|
+
if eth_ok and eth_raw:
|
|
1133
|
+
eth_balance = float(eth_raw.get("balance", 0))
|
|
1134
|
+
tx_fee_reserve = 0.0002
|
|
1135
|
+
transferable_eth = eth_balance - tx_fee_reserve
|
|
1136
|
+
if transferable_eth > 0.0001:
|
|
1137
|
+
logger.info(f"Transferring {transferable_eth:.6f} ETH to main wallet")
|
|
1138
|
+
(
|
|
1139
|
+
success,
|
|
1140
|
+
msg,
|
|
1141
|
+
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
1142
|
+
query="ethereum-base",
|
|
1143
|
+
amount=transferable_eth,
|
|
1144
|
+
strategy_name=self.name,
|
|
1145
|
+
skip_ledger=False,
|
|
1146
|
+
)
|
|
1147
|
+
if success:
|
|
1148
|
+
transferred_items.append(f"{transferable_eth:.6f} ETH")
|
|
1149
|
+
else:
|
|
1150
|
+
logger.warning(f"ETH transfer failed: {msg}")
|
|
1151
|
+
|
|
1152
|
+
if not transferred_items:
|
|
1153
|
+
return (True, "No funds to transfer to main wallet")
|
|
1154
|
+
|
|
1155
|
+
return (True, f"Transferred to main wallet: {', '.join(transferred_items)}")
|
|
1156
|
+
|
|
1074
1157
|
async def _get_last_rotation_time(self, wallet_address: str) -> datetime | None:
|
|
1075
1158
|
success, data = await self.ledger_adapter.get_strategy_latest_transactions(
|
|
1076
1159
|
wallet_address=self._get_strategy_wallet_address(),
|
|
@@ -1206,7 +1289,14 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1206
1289
|
else:
|
|
1207
1290
|
self.current_pool_data = None
|
|
1208
1291
|
if self.current_pool_data:
|
|
1209
|
-
|
|
1292
|
+
apy_pct = self.current_pool_data.get("combined_apy_pct")
|
|
1293
|
+
if apy_pct is not None:
|
|
1294
|
+
self.current_combined_apy_pct = float(apy_pct) / 100
|
|
1295
|
+
else:
|
|
1296
|
+
apy_val = self.current_pool_data.get("apy", 0)
|
|
1297
|
+
self.current_combined_apy_pct = (
|
|
1298
|
+
float(apy_val) / 100 if apy_val is not None else 0
|
|
1299
|
+
)
|
|
1210
1300
|
else:
|
|
1211
1301
|
self.current_combined_apy_pct = (
|
|
1212
1302
|
target_pool_data.get("combined_apy_pct", 0) / 100
|
|
@@ -1242,10 +1332,10 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1242
1332
|
(
|
|
1243
1333
|
_,
|
|
1244
1334
|
refreshed_pool_balance,
|
|
1245
|
-
) = await self.balance_adapter.
|
|
1246
|
-
|
|
1335
|
+
) = await self.balance_adapter.get_balance(
|
|
1336
|
+
query=pool.get("address"),
|
|
1337
|
+
wallet_address=strategy_address,
|
|
1247
1338
|
chain_id=pool.get("chain").get("id"),
|
|
1248
|
-
user_address=strategy_address,
|
|
1249
1339
|
)
|
|
1250
1340
|
self.current_pool_balance = int(refreshed_pool_balance)
|
|
1251
1341
|
except Exception:
|
|
@@ -1259,7 +1349,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1259
1349
|
target_token_id = target_token.get("token_id")
|
|
1260
1350
|
target_chain = target_token.get("chain").get("code", "").lower()
|
|
1261
1351
|
target_address = target_token.get("address", "").lower()
|
|
1262
|
-
gas_token_id = self.gas_token.get("
|
|
1352
|
+
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
1263
1353
|
|
|
1264
1354
|
# Swap all non-target, non-gas tokens to the target
|
|
1265
1355
|
for token_id, balance_wei in list(self.tracked_balances.items()):
|
|
@@ -1278,7 +1368,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1278
1368
|
# Get fresh balance to ensure accuracy
|
|
1279
1369
|
try:
|
|
1280
1370
|
success, fresh_balance = await self.balance_adapter.get_balance(
|
|
1281
|
-
|
|
1371
|
+
query=token_id,
|
|
1282
1372
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1283
1373
|
)
|
|
1284
1374
|
if not success or not fresh_balance or int(fresh_balance) <= 0:
|
|
@@ -1318,7 +1408,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1318
1408
|
# Refresh target token balance
|
|
1319
1409
|
try:
|
|
1320
1410
|
success, target_balance = await self.balance_adapter.get_balance(
|
|
1321
|
-
|
|
1411
|
+
query=target_token_id,
|
|
1322
1412
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1323
1413
|
)
|
|
1324
1414
|
if success and target_balance:
|
|
@@ -1335,7 +1425,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1335
1425
|
|
|
1336
1426
|
required_gas = int(self.MIN_GAS * (10 ** self.gas_token.get("decimals")))
|
|
1337
1427
|
_, current_gas = await self.balance_adapter.get_balance(
|
|
1338
|
-
|
|
1428
|
+
query=self.gas_token.get("token_id"),
|
|
1339
1429
|
wallet_address=strategy_address,
|
|
1340
1430
|
)
|
|
1341
1431
|
if current_gas >= required_gas:
|
|
@@ -1395,7 +1485,11 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1395
1485
|
if not success:
|
|
1396
1486
|
return 0.0
|
|
1397
1487
|
|
|
1398
|
-
best_quote =
|
|
1488
|
+
best_quote = (
|
|
1489
|
+
exit_quotes.get("best_quote") if isinstance(exit_quotes, dict) else None
|
|
1490
|
+
)
|
|
1491
|
+
if not best_quote:
|
|
1492
|
+
return None
|
|
1399
1493
|
current_pool_usd_value = best_quote.get("output_amount")
|
|
1400
1494
|
|
|
1401
1495
|
return float(
|
|
@@ -1407,7 +1501,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1407
1501
|
# Refresh tracked balances
|
|
1408
1502
|
await self._refresh_tracked_balances()
|
|
1409
1503
|
|
|
1410
|
-
gas_token_id = self.gas_token.get("
|
|
1504
|
+
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
1411
1505
|
results = []
|
|
1412
1506
|
|
|
1413
1507
|
for token_id, balance_wei in self.tracked_balances.items():
|
|
@@ -1440,7 +1534,10 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1440
1534
|
return results
|
|
1441
1535
|
|
|
1442
1536
|
async def _find_best_pool(self) -> tuple[bool, dict[str, Any]]:
|
|
1443
|
-
|
|
1537
|
+
chain_id = (self.usdc_token_info or {}).get("chain", {}).get("id")
|
|
1538
|
+
if chain_id is None:
|
|
1539
|
+
chain_id = 8453
|
|
1540
|
+
success, llama_data = await self.pool_adapter.get_pools(chain_id=chain_id)
|
|
1444
1541
|
if not success:
|
|
1445
1542
|
return False, {"message": f"Failed to fetch Llama data: {llama_data}"}
|
|
1446
1543
|
|
|
@@ -1469,11 +1566,11 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1469
1566
|
)
|
|
1470
1567
|
if not target_status and candidate.get("token_id"):
|
|
1471
1568
|
target_status, target_pool = await self.token_adapter.get_token(
|
|
1472
|
-
|
|
1569
|
+
query=candidate.get("token_id")
|
|
1473
1570
|
)
|
|
1474
1571
|
if not target_status and candidate.get("pool_id"):
|
|
1475
1572
|
target_status, target_pool = await self.token_adapter.get_token(
|
|
1476
|
-
|
|
1573
|
+
query=candidate.get("pool_id")
|
|
1477
1574
|
)
|
|
1478
1575
|
if not target_status:
|
|
1479
1576
|
continue
|
|
@@ -1515,7 +1612,8 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1515
1612
|
return None
|
|
1516
1613
|
|
|
1517
1614
|
try:
|
|
1518
|
-
|
|
1615
|
+
apy_pct = pool_data.get("combined_apy_pct") or pool_data.get("apy") or 0
|
|
1616
|
+
combined_apy_pct = float(apy_pct) / 100
|
|
1519
1617
|
success, quotes = await self.brap_adapter.get_swap_quote(
|
|
1520
1618
|
from_token_address=current_token.get("address"),
|
|
1521
1619
|
to_token_address=token.get("address"),
|
|
@@ -1527,10 +1625,9 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1527
1625
|
)
|
|
1528
1626
|
if not success:
|
|
1529
1627
|
return None
|
|
1530
|
-
|
|
1531
|
-
if not isinstance(quotes_data, dict):
|
|
1628
|
+
if not isinstance(quotes, dict):
|
|
1532
1629
|
return None
|
|
1533
|
-
best_quote =
|
|
1630
|
+
best_quote = quotes.get("best_quote")
|
|
1534
1631
|
if not best_quote:
|
|
1535
1632
|
return None
|
|
1536
1633
|
|
|
@@ -1582,7 +1679,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1582
1679
|
async def _status(self) -> StatusDict:
|
|
1583
1680
|
# Get ETH gas balance
|
|
1584
1681
|
gas_success, gas_balance_wei = await self.balance_adapter.get_balance(
|
|
1585
|
-
|
|
1682
|
+
query=self.gas_token.get("token_id"),
|
|
1586
1683
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1587
1684
|
)
|
|
1588
1685
|
gas_balance = (
|
|
@@ -1611,7 +1708,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1611
1708
|
|
|
1612
1709
|
# Calculate total value from tracked non-gas balances
|
|
1613
1710
|
total_value = 0.0
|
|
1614
|
-
gas_token_id = self.gas_token.get("
|
|
1711
|
+
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
1615
1712
|
|
|
1616
1713
|
for token_id, balance_wei in self.tracked_balances.items():
|
|
1617
1714
|
if token_id == gas_token_id:
|
|
@@ -1680,7 +1777,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1680
1777
|
|
|
1681
1778
|
usdc_token_id = self.usdc_token_info.get("token_id")
|
|
1682
1779
|
usdc_decimals = self.usdc_token_info.get("decimals")
|
|
1683
|
-
gas_token_id = self.gas_token.get("
|
|
1780
|
+
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
1684
1781
|
|
|
1685
1782
|
# Check current USDC balance
|
|
1686
1783
|
available_usdc_wei = self.tracked_balances.get(usdc_token_id, 0)
|
|
@@ -1745,7 +1842,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1745
1842
|
|
|
1746
1843
|
# Refresh USDC balance after swaps
|
|
1747
1844
|
success, usdc_wei = await self.balance_adapter.get_balance(
|
|
1748
|
-
|
|
1845
|
+
query=usdc_token_id,
|
|
1749
1846
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1750
1847
|
)
|
|
1751
1848
|
if success and usdc_wei:
|
|
@@ -1790,7 +1887,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1790
1887
|
|
|
1791
1888
|
# Refresh USDC balance again
|
|
1792
1889
|
success, usdc_wei = await self.balance_adapter.get_balance(
|
|
1793
|
-
|
|
1890
|
+
query=usdc_token_id,
|
|
1794
1891
|
wallet_address=self._get_strategy_wallet_address(),
|
|
1795
1892
|
)
|
|
1796
1893
|
if success and usdc_wei:
|
|
@@ -52,7 +52,10 @@ def strategy():
|
|
|
52
52
|
usdc_balance_mock = AsyncMock(return_value=(True, 60000000))
|
|
53
53
|
gas_balance_mock = AsyncMock(return_value=(True, 2000000000000000))
|
|
54
54
|
|
|
55
|
-
def get_balance_side_effect(
|
|
55
|
+
def get_balance_side_effect(query, wallet_address, **kwargs):
|
|
56
|
+
token_id = (
|
|
57
|
+
query if isinstance(query, str) else (query or {}).get("token_id")
|
|
58
|
+
)
|
|
56
59
|
if token_id == "usd-coin-base" or token_id == "usd-coin":
|
|
57
60
|
return usdc_balance_mock.return_value
|
|
58
61
|
elif token_id == "ethereum-base" or token_id == "ethereum":
|
|
@@ -449,7 +452,8 @@ async def test_sweep_wallet_uses_tracked_tokens(strategy):
|
|
|
449
452
|
assert "token-2" in strategy.tracked_token_ids
|
|
450
453
|
|
|
451
454
|
# Mock balance adapter to return fresh balances
|
|
452
|
-
async def get_balance_mock(
|
|
455
|
+
async def get_balance_mock(query, **kwargs):
|
|
456
|
+
token_id = query if isinstance(query, str) else (query or {}).get("token_id")
|
|
453
457
|
balance = strategy.tracked_balances.get(token_id, 0)
|
|
454
458
|
# Return the balance, ensuring it's an int
|
|
455
459
|
return (True, int(balance) if balance else 0)
|
|
@@ -495,12 +499,11 @@ async def test_get_non_gas_balances_uses_tracked_state(strategy):
|
|
|
495
499
|
strategy._track_token(pool_token_id, 50000000000000000000)
|
|
496
500
|
|
|
497
501
|
# Mock refresh
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
)
|
|
502
|
+
def _get_balance_effect(query, **kwargs):
|
|
503
|
+
token_id = query if isinstance(query, str) else (query or {}).get("token_id")
|
|
504
|
+
return (True, strategy.tracked_balances.get(token_id, 0))
|
|
505
|
+
|
|
506
|
+
strategy.balance_adapter.get_balance = AsyncMock(side_effect=_get_balance_effect)
|
|
504
507
|
|
|
505
508
|
# Get non-gas balances
|
|
506
509
|
balances = await strategy._get_non_gas_balances()
|
|
@@ -519,11 +522,12 @@ async def test_partial_liquidate_uses_tracked_tokens(strategy):
|
|
|
519
522
|
strategy._track_token("test-pool-base", 100000000000000000000) # 100 POOL tokens
|
|
520
523
|
|
|
521
524
|
# Mock balance and token adapters
|
|
525
|
+
def _get_balance_effect_partial(query, **kwargs):
|
|
526
|
+
token_id = query if isinstance(query, str) else (query or {}).get("token_id")
|
|
527
|
+
return (True, strategy.tracked_balances.get(token_id, 0))
|
|
528
|
+
|
|
522
529
|
strategy.balance_adapter.get_balance = AsyncMock(
|
|
523
|
-
side_effect=
|
|
524
|
-
True,
|
|
525
|
-
strategy.tracked_balances.get(token_id, 0),
|
|
526
|
-
)
|
|
530
|
+
side_effect=_get_balance_effect_partial
|
|
527
531
|
)
|
|
528
532
|
|
|
529
533
|
strategy.token_adapter.get_token_price = AsyncMock(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# Strategy Template
|
|
2
2
|
|
|
3
3
|
This template provides the scaffolding for a new strategy. It mirrors the structure in `wayfinder_paths/strategies/...`.
|
|
4
4
|
|
|
@@ -70,7 +70,7 @@ class MyStrategy(Strategy):
|
|
|
70
70
|
return (False, "Nothing to deposit")
|
|
71
71
|
|
|
72
72
|
success, _ = await self.balance_adapter.get_balance(
|
|
73
|
-
|
|
73
|
+
query=self.config.get("token_id"),
|
|
74
74
|
wallet_address=self.config.get("main_wallet", {}).get("address"),
|
|
75
75
|
)
|
|
76
76
|
if not success:
|
|
@@ -87,7 +87,7 @@ class MyStrategy(Strategy):
|
|
|
87
87
|
async def _status(self) -> StatusDict:
|
|
88
88
|
"""Surface state back to run_strategy.py."""
|
|
89
89
|
success, balance = await self.balance_adapter.get_balance(
|
|
90
|
-
|
|
90
|
+
query=self.config.get("token_id"),
|
|
91
91
|
wallet_address=self.config.get("strategy_wallet", {}).get("address"),
|
|
92
92
|
)
|
|
93
93
|
return {
|
|
@@ -66,14 +66,15 @@ def strategy():
|
|
|
66
66
|
# usdc_balance_mock = AsyncMock(return_value=(True, 60000000))
|
|
67
67
|
# gas_balance_mock = AsyncMock(return_value=(True, 2000000000000000))
|
|
68
68
|
#
|
|
69
|
-
# def get_balance_side_effect(
|
|
69
|
+
# def get_balance_side_effect(query, wallet_address, **kwargs):
|
|
70
|
+
# token_id = query if isinstance(query, str) else (query or {}).get("token_id")
|
|
70
71
|
# if token_id == "usd-coin-base" or token_id == "usd-coin":
|
|
71
72
|
# return usdc_balance_mock.return_value
|
|
72
73
|
# elif token_id == "ethereum-base" or token_id == "ethereum":
|
|
73
74
|
# return gas_balance_mock.return_value
|
|
74
75
|
# return (True, 1000000000)
|
|
75
76
|
#
|
|
76
|
-
# s.balance_adapter.
|
|
77
|
+
# s.balance_adapter.get_balance = AsyncMock(
|
|
77
78
|
# side_effect=get_balance_side_effect
|
|
78
79
|
# )
|
|
79
80
|
|