wayfinder-paths 0.1.2__py3-none-any.whl → 0.1.4__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/CONFIG_GUIDE.md +23 -18
- wayfinder_paths/abis/generic/erc20.json +383 -0
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/README.md +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/test_adapter.py +12 -6
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/README.md +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/test_adapter.py +2 -2
- wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
- wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/test_adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/README.md +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/test_adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/README.md +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/test_adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/token_adapter/README.md +1 -1
- wayfinder_paths/adapters/token_adapter/examples.json +26 -0
- wayfinder_paths/{vaults/adapters → adapters}/token_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/token_adapter/test_adapter.py +1 -1
- wayfinder_paths/config.example.json +3 -1
- wayfinder_paths/core/engine/manifest.py +1 -1
- wayfinder_paths/core/strategies/Strategy.py +3 -3
- wayfinder_paths/core/strategies/descriptors.py +80 -0
- wayfinder_paths/core/utils/evm_helpers.py +39 -0
- wayfinder_paths/policies/enso.py +17 -0
- wayfinder_paths/policies/erc20.py +34 -0
- wayfinder_paths/policies/evm.py +21 -0
- wayfinder_paths/policies/hyper_evm.py +19 -0
- wayfinder_paths/policies/hyperlend.py +12 -0
- wayfinder_paths/policies/hyperliquid.py +30 -0
- wayfinder_paths/policies/moonwell.py +54 -0
- wayfinder_paths/policies/prjx.py +30 -0
- wayfinder_paths/policies/util.py +27 -0
- wayfinder_paths/run_strategy.py +3 -3
- wayfinder_paths/scripts/create_strategy.py +3 -3
- wayfinder_paths/scripts/validate_manifests.py +2 -2
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/README.md +4 -3
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/manifest.yaml +1 -1
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/strategy.py +143 -81
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/test_strategy.py +4 -2
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/README.md +4 -3
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/manifest.yaml +1 -1
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/strategy.py +117 -80
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/test_strategy.py +2 -2
- wayfinder_paths/{vaults/templates → templates}/adapter/README.md +4 -4
- wayfinder_paths/{vaults/templates → templates}/adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/templates → templates}/strategy/README.md +6 -5
- wayfinder_paths/{vaults/templates → templates}/strategy/manifest.yaml +1 -1
- wayfinder_paths/{vaults/templates → templates}/strategy/test_strategy.py +1 -1
- {wayfinder_paths-0.1.2.dist-info → wayfinder_paths-0.1.4.dist-info}/METADATA +101 -23
- wayfinder_paths-0.1.4.dist-info/RECORD +125 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +0 -7
- wayfinder_paths/vaults/adapters/token_adapter/examples.json +0 -26
- wayfinder_paths/vaults/strategies/__init__.py +0 -0
- wayfinder_paths-0.1.2.dist-info/RECORD +0 -115
- /wayfinder_paths/{vaults → adapters}/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/token_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/token_adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/adapters → strategies}/__init__.py +0 -0
- /wayfinder_paths/{vaults/strategies → strategies}/config.py +0 -0
- /wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/examples.json +0 -0
- /wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/examples.json +0 -0
- /wayfinder_paths/{vaults/templates → templates}/adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/templates → templates}/adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/templates → templates}/adapter/test_adapter.py +0 -0
- /wayfinder_paths/{vaults/templates → templates}/strategy/examples.json +0 -0
- /wayfinder_paths/{vaults/templates → templates}/strategy/strategy.py +0 -0
- {wayfinder_paths-0.1.2.dist-info → wayfinder_paths-0.1.4.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.2.dist-info → wayfinder_paths-0.1.4.dist-info}/WHEEL +0 -0
wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/manifest.yaml
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
schema_version: "0.1"
|
|
2
|
-
entrypoint: "
|
|
2
|
+
entrypoint: "strategies.hyperlend_stable_yield_strategy.strategy.HyperlendStableYieldStrategy"
|
|
3
3
|
permissions:
|
|
4
4
|
policy: "(wallet.id == 'FORMAT_WALLET_ID')"
|
|
5
5
|
adapters:
|
wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/strategy.py
RENAMED
|
@@ -11,19 +11,42 @@ import pandas as pd
|
|
|
11
11
|
from loguru import logger
|
|
12
12
|
from web3 import Web3
|
|
13
13
|
|
|
14
|
+
from wayfinder_paths.adapters.balance_adapter.adapter import BalanceAdapter
|
|
15
|
+
from wayfinder_paths.adapters.brap_adapter.adapter import BRAPAdapter
|
|
16
|
+
from wayfinder_paths.adapters.hyperlend_adapter.adapter import HyperlendAdapter
|
|
17
|
+
from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
|
|
18
|
+
from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
|
|
14
19
|
from wayfinder_paths.core.constants.base import DEFAULT_SLIPPAGE
|
|
15
20
|
from wayfinder_paths.core.services.base import Web3Service
|
|
16
21
|
from wayfinder_paths.core.services.local_token_txn import (
|
|
17
22
|
LocalTokenTxnService,
|
|
18
23
|
)
|
|
19
24
|
from wayfinder_paths.core.services.web3_service import DefaultWeb3Service
|
|
25
|
+
from wayfinder_paths.core.strategies.descriptors import (
|
|
26
|
+
Complexity,
|
|
27
|
+
Directionality,
|
|
28
|
+
Frequency,
|
|
29
|
+
StratDescriptor,
|
|
30
|
+
TokenExposure,
|
|
31
|
+
Volatility,
|
|
32
|
+
)
|
|
20
33
|
from wayfinder_paths.core.strategies.Strategy import StatusDict, StatusTuple, Strategy
|
|
21
34
|
from wayfinder_paths.core.wallets.WalletManager import WalletManager
|
|
22
|
-
from wayfinder_paths.
|
|
23
|
-
from wayfinder_paths.
|
|
24
|
-
from wayfinder_paths.
|
|
25
|
-
|
|
26
|
-
|
|
35
|
+
from wayfinder_paths.policies.enso import ENSO_ROUTER, enso_swap
|
|
36
|
+
from wayfinder_paths.policies.erc20 import erc20_spender_for_any_token
|
|
37
|
+
from wayfinder_paths.policies.hyper_evm import (
|
|
38
|
+
hypecore_sentinel_deposit,
|
|
39
|
+
whype_deposit_and_withdraw,
|
|
40
|
+
)
|
|
41
|
+
from wayfinder_paths.policies.hyperlend import (
|
|
42
|
+
HYPERLEND_POOL,
|
|
43
|
+
hyperlend_supply_and_withdraw,
|
|
44
|
+
)
|
|
45
|
+
from wayfinder_paths.policies.hyperliquid import (
|
|
46
|
+
any_hyperliquid_l1_payload,
|
|
47
|
+
any_hyperliquid_user_payload,
|
|
48
|
+
)
|
|
49
|
+
from wayfinder_paths.policies.prjx import PRJX_ROUTER, prjx_swap
|
|
27
50
|
|
|
28
51
|
SYMBOL_TRANSLATION_TABLE = str.maketrans(
|
|
29
52
|
{
|
|
@@ -36,7 +59,7 @@ WRAPPED_HYPE_ADDRESS = "0x5555555555555555555555555555555555555555"
|
|
|
36
59
|
|
|
37
60
|
|
|
38
61
|
class HyperlendStableYieldStrategy(Strategy):
|
|
39
|
-
name = "
|
|
62
|
+
name = "HyperLend Stable Optimizer"
|
|
40
63
|
|
|
41
64
|
# Strategy parameters
|
|
42
65
|
APY_SHORT_CIRCUIT_THRESHOLD = None
|
|
@@ -61,82 +84,110 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
61
84
|
P_BEST_ROTATION_THRESHOLD = 0.4
|
|
62
85
|
MAX_CANDIDATES = 5
|
|
63
86
|
MIN_STABLE_SWAP_TOKENS = 1e-3
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
"
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
87
|
+
MAX_GAS = 0.1 # hype float
|
|
88
|
+
|
|
89
|
+
INFO = StratDescriptor(
|
|
90
|
+
description=f"""Multi-strategy allocator that converts USDT0 into the most consistently rewarding HyperLend stablecoin and continuously checks if a rotation is justified.
|
|
91
|
+
**What it does:** Pulls USDT0 from the main wallet, ensures a small HYPE safety buffer for gas, swaps the remaining stable balance into candidate markets, and supplies
|
|
92
|
+
liquidity to HyperLend. Hourly rate histories are aggregated into a 7-day panel and routed through a block-bootstrap tournament (horizon {HORIZON_HOURS}h, block length {BLOCK_LEN}, {TRIALS:,}
|
|
93
|
+
trials, {HALFLIFE_DAYS}-day half-life weighting) to estimate which asset has the highest probability of outperforming peers. USDT0 is the LayerZero bridgable stablecoin for USDT.
|
|
94
|
+
**Exposure type:** Market-neutral stablecoin lending on HyperEVM with automated rotation into whichever pool offers the strongest risk-adjusted lending yield.
|
|
95
|
+
**Chains:** HyperEVM only (HyperLend pool suite).
|
|
96
|
+
**Deposit/Withdrawal:** Deposits move USDT0 from the main wallet into the strategy wallet, top up a minimal HYPE gas buffer, rotate into the selected stable, and lend it via HyperLend.
|
|
97
|
+
Withdrawals unwind the lend position, convert balances back to USDT, and return funds (plus residual HYPE) to the main wallet.
|
|
98
|
+
**Gas**: Requires HYPE on HypeEVM. Arbitrary amount of funding gas is accepted via vault wallet transfers.
|
|
99
|
+
""",
|
|
100
|
+
summary=(
|
|
101
|
+
"Recency-weighted HyperLend stablecoin optimizer that bootstraps 7-day rate history "
|
|
102
|
+
f"(horizon {HORIZON_HOURS}h, {BLOCK_LEN}-hour blocks, {TRIALS:,} simulations) to pick the top "
|
|
103
|
+
"performer, funds with USDT0, tops up a small HYPE gas buffer, and defaults to a hysteresis "
|
|
104
|
+
f"rotation band (dwell={HYSTERESIS_DWELL_HOURS}h, z={HYSTERESIS_Z:.2f}) to avoid churn while still "
|
|
105
|
+
"short-circuiting when yield gaps are extreme."
|
|
106
|
+
),
|
|
107
|
+
gas_token_symbol="HYPE",
|
|
108
|
+
gas_token_id="hyperliquid-hyperevm",
|
|
109
|
+
deposit_token_id="usdt0-hyperevm",
|
|
110
|
+
minimum_net_deposit=10,
|
|
111
|
+
gas_maximum=MAX_GAS, # hype float
|
|
112
|
+
gas_threshold=MAX_GAS / 3,
|
|
113
|
+
# risk indicators
|
|
114
|
+
volatility=Volatility.LOW,
|
|
115
|
+
volatility_description_short=(
|
|
116
|
+
"Pure HyperLend stablecoin lending keeps NAV steady aside from rate drift."
|
|
117
|
+
),
|
|
118
|
+
directionality=Directionality.MARKET_NEUTRAL,
|
|
119
|
+
directionality_description=(
|
|
120
|
+
"Rotates capital between USD stables so exposure stays market neutral."
|
|
121
|
+
),
|
|
122
|
+
complexity=Complexity.LOW,
|
|
123
|
+
complexity_description="Agent handles optimal pool finding, swaps, and lend transactions automatically.",
|
|
124
|
+
token_exposure=TokenExposure.STABLECOINS,
|
|
125
|
+
token_exposure_description=(
|
|
126
|
+
"Only HyperEVM USD stables (USDT0 and peers), no volatile tokens."
|
|
127
|
+
),
|
|
128
|
+
frequency=Frequency.LOW,
|
|
129
|
+
frequency_description=(
|
|
130
|
+
"Updates every 2 hours; rotations infrequent (weekly cooldowns)."
|
|
131
|
+
),
|
|
132
|
+
return_drivers=["lend APY", "pool yield"],
|
|
133
|
+
config={
|
|
134
|
+
"deposit": {
|
|
135
|
+
"description": "Move USDT0 into the vault, ensure a small HYPE gas buffer, and supply the best HyperLend stable.",
|
|
136
|
+
"parameters": {
|
|
137
|
+
"main_token_amount": {
|
|
138
|
+
"type": "float",
|
|
139
|
+
"unit": "USDT0 tokens",
|
|
140
|
+
"description": "Amount of USDT0 to allocate to HyperLend.",
|
|
141
|
+
"minimum": 1.0, # TODO: 10
|
|
142
|
+
"examples": ["100.0", "250.5"],
|
|
143
|
+
},
|
|
144
|
+
"gas_token_amount": {
|
|
145
|
+
"type": "float",
|
|
146
|
+
"unit": "HYPE tokens",
|
|
147
|
+
"description": "Amount of HYPE to top up into the vault wallet to cover gas costs.",
|
|
148
|
+
"minimum": 0.0,
|
|
149
|
+
"maximum": GAS_MAXIMUM,
|
|
150
|
+
"recommended": GAS_MAXIMUM,
|
|
151
|
+
},
|
|
101
152
|
},
|
|
153
|
+
"result": "USDT0 converted into the top-performing HyperLend stablecoin and supplied on-chain.",
|
|
154
|
+
},
|
|
155
|
+
"withdraw": {
|
|
156
|
+
"description": "Unwinds the position, converts balances to USDT0, and returns funds (plus HYPE buffer) to the main wallet.",
|
|
157
|
+
"parameters": {},
|
|
158
|
+
"result": "Principal and accrued gains returned in USDT0; residual HYPE buffer swept home.",
|
|
159
|
+
},
|
|
160
|
+
"update": {
|
|
161
|
+
"description": (
|
|
162
|
+
"Evaluates tournament projections and rotates when the hysteresis band is breached "
|
|
163
|
+
f"(dwell={HYSTERESIS_DWELL_HOURS}h, z={HYSTERESIS_Z:.2f}) or when a short-circuit gap is hit "
|
|
164
|
+
"(set HYPERLEND_ROTATION_POLICY=cooldown to restore the legacy threshold/cooldown rule)."
|
|
165
|
+
),
|
|
166
|
+
"parameters": {},
|
|
167
|
+
},
|
|
168
|
+
"status": {
|
|
169
|
+
"description": "Summarises current lend position, APY, and chosen asset.",
|
|
170
|
+
"provides": [
|
|
171
|
+
"lent_asset",
|
|
172
|
+
"lent_balance",
|
|
173
|
+
"current_apy",
|
|
174
|
+
"best_candidate",
|
|
175
|
+
"best_candidate_apy",
|
|
176
|
+
],
|
|
177
|
+
},
|
|
178
|
+
"points": {
|
|
179
|
+
"description": "Fetch the HyperLend points account snapshot for this vault wallet using a signed login.",
|
|
180
|
+
"parameters": {},
|
|
181
|
+
"result": "Returns the HyperLend points API payload for the strategy wallet.",
|
|
182
|
+
},
|
|
183
|
+
"technical_details": {
|
|
184
|
+
"rotation_policy": ROTATION_POLICY.lower(),
|
|
185
|
+
"hysteresis_dwell_hours": HYSTERESIS_DWELL_HOURS,
|
|
186
|
+
"hysteresis_z": HYSTERESIS_Z,
|
|
187
|
+
"rotation_tx_cost": ROTATION_TX_COST,
|
|
102
188
|
},
|
|
103
|
-
"result": "USDT0 converted into the top-performing HyperLend stablecoin and supplied on-chain.",
|
|
104
|
-
},
|
|
105
|
-
"withdraw": {
|
|
106
|
-
"description": "Unwinds the position, converts balances to USDT0, and returns funds (plus HYPE buffer) to the main wallet.",
|
|
107
|
-
"parameters": {},
|
|
108
|
-
"result": "Principal and accrued gains returned in USDT0; residual HYPE buffer swept home.",
|
|
109
|
-
},
|
|
110
|
-
"update": {
|
|
111
|
-
"description": (
|
|
112
|
-
"Evaluates tournament projections and rotates when the hysteresis band is breached "
|
|
113
|
-
f"(dwell={HYSTERESIS_DWELL_HOURS}h, z={HYSTERESIS_Z:.2f}) or when a short-circuit gap is hit "
|
|
114
|
-
"(set HYPERLEND_ROTATION_POLICY=cooldown to restore the legacy threshold/cooldown rule)."
|
|
115
|
-
),
|
|
116
|
-
"parameters": {},
|
|
117
|
-
},
|
|
118
|
-
"status": {
|
|
119
|
-
"description": "Summarises current lend position, APY, and chosen asset.",
|
|
120
|
-
"provides": [
|
|
121
|
-
"lent_asset",
|
|
122
|
-
"lent_balance",
|
|
123
|
-
"current_apy",
|
|
124
|
-
"best_candidate",
|
|
125
|
-
"best_candidate_apy",
|
|
126
|
-
],
|
|
127
|
-
},
|
|
128
|
-
"points": {
|
|
129
|
-
"description": "Fetch the HyperLend points account snapshot for this vault wallet using a signed login.",
|
|
130
|
-
"parameters": {},
|
|
131
|
-
"result": "Returns the HyperLend points API payload for the strategy wallet.",
|
|
132
|
-
},
|
|
133
|
-
"technical_details": {
|
|
134
|
-
"rotation_policy": ROTATION_POLICY.lower(),
|
|
135
|
-
"hysteresis_dwell_hours": HYSTERESIS_DWELL_HOURS,
|
|
136
|
-
"hysteresis_z": HYSTERESIS_Z,
|
|
137
|
-
"rotation_tx_cost": ROTATION_TX_COST,
|
|
138
189
|
},
|
|
139
|
-
|
|
190
|
+
)
|
|
140
191
|
|
|
141
192
|
def __init__(
|
|
142
193
|
self,
|
|
@@ -2323,6 +2374,17 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
2323
2374
|
}
|
|
2324
2375
|
|
|
2325
2376
|
@staticmethod
|
|
2326
|
-
def policies() -> list[str]:
|
|
2377
|
+
async def policies() -> list[str]:
|
|
2327
2378
|
"""Return policy strings used to scope on-chain permissions."""
|
|
2328
|
-
return [
|
|
2379
|
+
return [
|
|
2380
|
+
any_hyperliquid_l1_payload(),
|
|
2381
|
+
any_hyperliquid_user_payload(),
|
|
2382
|
+
hypecore_sentinel_deposit(),
|
|
2383
|
+
await whype_deposit_and_withdraw(),
|
|
2384
|
+
erc20_spender_for_any_token(HYPERLEND_POOL),
|
|
2385
|
+
await hyperlend_supply_and_withdraw(),
|
|
2386
|
+
erc20_spender_for_any_token(ENSO_ROUTER),
|
|
2387
|
+
await enso_swap(),
|
|
2388
|
+
erc20_spender_for_any_token(PRJX_ROUTER),
|
|
2389
|
+
await prjx_swap(),
|
|
2390
|
+
]
|
wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/test_strategy.py
RENAMED
|
@@ -4,7 +4,7 @@ from unittest.mock import AsyncMock
|
|
|
4
4
|
|
|
5
5
|
# Ensure wayfinder-paths is on path for tests.test_utils import
|
|
6
6
|
# This is a workaround until conftest loading order is resolved
|
|
7
|
-
_wayfinder_path_dir = Path(__file__).parent.parent.parent.
|
|
7
|
+
_wayfinder_path_dir = Path(__file__).parent.parent.parent.resolve()
|
|
8
8
|
_wayfinder_path_str = str(_wayfinder_path_dir)
|
|
9
9
|
if _wayfinder_path_str not in sys.path:
|
|
10
10
|
sys.path.insert(0, _wayfinder_path_str)
|
|
@@ -29,7 +29,7 @@ except ImportError:
|
|
|
29
29
|
get_canonical_examples = test_utils.get_canonical_examples
|
|
30
30
|
load_strategy_examples = test_utils.load_strategy_examples
|
|
31
31
|
|
|
32
|
-
from wayfinder_paths.
|
|
32
|
+
from wayfinder_paths.strategies.hyperlend_stable_yield_strategy.strategy import ( # noqa: E402
|
|
33
33
|
HyperlendStableYieldStrategy,
|
|
34
34
|
)
|
|
35
35
|
|
|
@@ -244,6 +244,8 @@ async def test_smoke(strategy):
|
|
|
244
244
|
examples = load_strategy_examples(Path(__file__))
|
|
245
245
|
smoke_data = examples["smoke"]
|
|
246
246
|
|
|
247
|
+
await strategy.setup()
|
|
248
|
+
|
|
247
249
|
st = await strategy.status()
|
|
248
250
|
assert isinstance(st, dict)
|
|
249
251
|
assert "portfolio_value" in st or "net_deposit" in st or "strategy_status" in st
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Stablecoin Yield Strategy
|
|
2
2
|
|
|
3
|
-
- Entrypoint: `
|
|
3
|
+
- Entrypoint: `strategies.stablecoin_yield_strategy.strategy.StablecoinYieldStrategy`
|
|
4
4
|
- Manifest: `manifest.yaml`
|
|
5
5
|
- Examples: `examples.json`
|
|
6
6
|
- Tests: `test_strategy.py`
|
|
@@ -72,8 +72,9 @@ Transactions are scoped to the vault wallet and Enso Router approval/swap calls:
|
|
|
72
72
|
# Install dependencies
|
|
73
73
|
poetry install
|
|
74
74
|
|
|
75
|
-
# Generate
|
|
76
|
-
|
|
75
|
+
# Generate main wallet (writes wallets.json)
|
|
76
|
+
# Creates a main wallet (or use 'just create-strategy' which auto-creates wallets)
|
|
77
|
+
poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
|
|
77
78
|
|
|
78
79
|
# Copy the example config and set credentials if needed
|
|
79
80
|
cp wayfinder_paths/config.example.json config.json
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
schema_version: "0.1"
|
|
2
|
-
entrypoint: "
|
|
2
|
+
entrypoint: "strategies.stablecoin_yield_strategy.strategy.StablecoinYieldStrategy"
|
|
3
3
|
permissions:
|
|
4
4
|
policy: "(wallet.id == 'FORMAT_WALLET_ID') && ((eth.tx.data[0..10] == '0x095ea7b3' && eth.tx.data[34..74] == 'f75584ef6673ad213a685a1b58cc0330b8ea22cf') || (eth.tx.to == '0xF75584eF6673aD213a685a1B58Cc0330B8eA22Cf'))"
|
|
5
5
|
adapters:
|
|
@@ -6,18 +6,26 @@ from typing import Any
|
|
|
6
6
|
|
|
7
7
|
from loguru import logger
|
|
8
8
|
|
|
9
|
+
from wayfinder_paths.adapters.balance_adapter.adapter import BalanceAdapter
|
|
10
|
+
from wayfinder_paths.adapters.brap_adapter.adapter import BRAPAdapter
|
|
11
|
+
from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
|
|
12
|
+
from wayfinder_paths.adapters.pool_adapter.adapter import PoolAdapter
|
|
13
|
+
from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
|
|
9
14
|
from wayfinder_paths.core.constants.base import DEFAULT_SLIPPAGE
|
|
10
15
|
from wayfinder_paths.core.services.local_token_txn import (
|
|
11
16
|
LocalTokenTxnService,
|
|
12
17
|
)
|
|
13
18
|
from wayfinder_paths.core.services.web3_service import DefaultWeb3Service
|
|
19
|
+
from wayfinder_paths.core.strategies.descriptors import (
|
|
20
|
+
Complexity,
|
|
21
|
+
Directionality,
|
|
22
|
+
Frequency,
|
|
23
|
+
StratDescriptor,
|
|
24
|
+
TokenExposure,
|
|
25
|
+
Volatility,
|
|
26
|
+
)
|
|
14
27
|
from wayfinder_paths.core.strategies.Strategy import StatusDict, StatusTuple, Strategy
|
|
15
28
|
from wayfinder_paths.core.wallets.WalletManager import WalletManager
|
|
16
|
-
from wayfinder_paths.vaults.adapters.balance_adapter.adapter import BalanceAdapter
|
|
17
|
-
from wayfinder_paths.vaults.adapters.brap_adapter.adapter import BRAPAdapter
|
|
18
|
-
from wayfinder_paths.vaults.adapters.ledger_adapter.adapter import LedgerAdapter
|
|
19
|
-
from wayfinder_paths.vaults.adapters.pool_adapter.adapter import PoolAdapter
|
|
20
|
-
from wayfinder_paths.vaults.adapters.token_adapter.adapter import TokenAdapter
|
|
21
29
|
|
|
22
30
|
|
|
23
31
|
class StablecoinYieldStrategy(Strategy):
|
|
@@ -33,84 +41,113 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
33
41
|
SUPPORTED_NETWORK_CODES = {"base"}
|
|
34
42
|
ROTATION_MIN_INTERVAL = timedelta(days=14)
|
|
35
43
|
MINIMUM_APY_IMPROVEMENT = 0.01
|
|
36
|
-
GAS_MAXIMUM =
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
44
|
+
GAS_MAXIMUM = 10e-4 # ethereum float
|
|
45
|
+
GAS_SAFETY_FRACTION = 1 / 3
|
|
46
|
+
|
|
47
|
+
INFO = StratDescriptor(
|
|
48
|
+
description=(
|
|
49
|
+
"An automated yield optimization strategy that maximizes returns on USDC deposits on Base.\n\n"
|
|
50
|
+
"What it does: Continuously scans and evaluates yield opportunities across Base-based DeFi protocols to find the "
|
|
51
|
+
"highest-yielding, low-risk positions for USDC. Automatically rebalances positions when better opportunities "
|
|
52
|
+
"emerge to maintain optimal yield generation.\n\n"
|
|
53
|
+
"Exposure type: Stable USD-denominated exposure with minimal impermanent loss risk. Focuses exclusively on USDC "
|
|
54
|
+
"and operations on the Base network to preserve capital and maximize yield.\n\n"
|
|
55
|
+
"Chains: Operates solely on the Base network.\n\n"
|
|
56
|
+
f"Deposit/Withdrawal: Accepts deposits only in USDC on Base with a minimum of {MIN_AMOUNT_USDC} USDC. Gas: Requires Base ETH "
|
|
57
|
+
"for gas fees during position entry, rebalancing, and exit (~0.001-0.02 ETH per rebalance cycle). Strategy automatically "
|
|
58
|
+
"deploys funds to an optimal yield farming position on Base. Withdrawals exit current positions and return USDC to the "
|
|
59
|
+
"user wallet.\n\n"
|
|
60
|
+
f"Risks: Primary risks include smart contract vulnerabilities in underlying Base DeFi protocols, temporary yield fluctuations, "
|
|
61
|
+
f"gas costs during rebalancing, and potential brief capital lock-up during protocol transitions. Strategy filters for a minimum TVL of ${MIN_TVL:,}."
|
|
62
|
+
),
|
|
63
|
+
summary=(
|
|
64
|
+
"Automated stablecoin yield farming across DeFi protocols on Base. "
|
|
65
|
+
f"Continuously optimizes positions for maximum stable yield while avoiding impermanent loss. "
|
|
66
|
+
f"Min: {MIN_AMOUNT_USDC} USDC + ETH gas. Filters for ${MIN_TVL:,}+ TVL protocols."
|
|
67
|
+
),
|
|
68
|
+
gas_token_symbol="ETH",
|
|
69
|
+
gas_token_id="ethereum-base",
|
|
70
|
+
deposit_token_id="usd-coin-base",
|
|
71
|
+
minimum_net_deposit=50,
|
|
72
|
+
gas_maximum=GAS_MAXIMUM,
|
|
73
|
+
# Anything below this level triggers a gas top-up
|
|
74
|
+
gas_threshold=GAS_MAXIMUM * GAS_SAFETY_FRACTION,
|
|
75
|
+
# risk indicators
|
|
76
|
+
volatility=Volatility.LOW,
|
|
77
|
+
volatility_description_short=(
|
|
78
|
+
"Capital sits in Base stablecoin lending pools, so price swings are minimal."
|
|
79
|
+
),
|
|
80
|
+
directionality=Directionality.MARKET_NEUTRAL,
|
|
81
|
+
directionality_description=(
|
|
82
|
+
"Fully USD-denominated yield farming with no directional crypto beta."
|
|
83
|
+
),
|
|
84
|
+
complexity=Complexity.LOW,
|
|
85
|
+
complexity_description="Agent handles optimal pool finding and rebalancing",
|
|
86
|
+
token_exposure=TokenExposure.STABLECOINS,
|
|
87
|
+
token_exposure_description=(
|
|
88
|
+
"Only Base USDC (and occasional stable swaps) with no volatile assets."
|
|
89
|
+
),
|
|
90
|
+
frequency=Frequency.LOW,
|
|
91
|
+
frequency_description=(
|
|
92
|
+
"Updates every 2 hours; rebalances infrequent (bi-weekly cooldowns)."
|
|
93
|
+
),
|
|
94
|
+
return_drivers=["pool yield"],
|
|
95
|
+
# config metadata for UIs/agents
|
|
96
|
+
config={
|
|
97
|
+
"deposit": {
|
|
98
|
+
"parameters": {
|
|
99
|
+
"main_token_amount": {
|
|
100
|
+
"type": "float",
|
|
101
|
+
"description": "amount of Base USDC (token id: usd-coin-base) to deposit",
|
|
102
|
+
},
|
|
103
|
+
"gas_token_amount": {
|
|
104
|
+
"type": "float",
|
|
105
|
+
"description": "amount of Base ETH (token id: ethereum-base) to deposit for gas fees",
|
|
106
|
+
"minimum": 0,
|
|
107
|
+
"maximum": GAS_MAXIMUM,
|
|
108
|
+
},
|
|
72
109
|
},
|
|
110
|
+
"process": "Deposits USDC on Base and searches for the highest yield opportunities among Base-based DeFi protocols",
|
|
111
|
+
"requirements": [
|
|
112
|
+
"Sufficient USDC balance on Base",
|
|
113
|
+
"Base ETH available for gas",
|
|
114
|
+
],
|
|
115
|
+
"result": "Funds deployed to a yield farming position on Base",
|
|
116
|
+
},
|
|
117
|
+
"withdraw": {
|
|
118
|
+
"parameters": {},
|
|
119
|
+
"process": "Exits yield positions on Base and returns USDC to the user wallet",
|
|
120
|
+
"requirements": [
|
|
121
|
+
"Active positions to exit",
|
|
122
|
+
"Gas for transactions on Base",
|
|
123
|
+
],
|
|
124
|
+
"result": "USDC returned to wallet and positions closed on Base",
|
|
125
|
+
},
|
|
126
|
+
"update": {
|
|
127
|
+
"parameters": {},
|
|
128
|
+
"process": "Scans for better yield opportunities on Base and rebalances positions automatically",
|
|
129
|
+
"frequency": "Call daily or when significant yield changes occur",
|
|
130
|
+
"requirements": [
|
|
131
|
+
"Active strategy positions on Base",
|
|
132
|
+
"Sufficient Base gas for rebalancing",
|
|
133
|
+
],
|
|
134
|
+
"result": "Positions optimized for maximum yield on Base",
|
|
135
|
+
},
|
|
136
|
+
"technical_details": {
|
|
137
|
+
"wallet_structure": "Uses strategy subwallet for isolation",
|
|
138
|
+
"chains": ["Base"],
|
|
139
|
+
"protocols": ["Various Base DeFi yield protocols"],
|
|
140
|
+
"tokens": ["USDC"],
|
|
141
|
+
"gas_requirements": "~0.001-0.02 ETH per rebalance on Base",
|
|
142
|
+
"search_depth": SEARCH_DEPTH,
|
|
143
|
+
"minimum_tvl": MIN_TVL,
|
|
144
|
+
"dust_apy_threshold": DUST_APY,
|
|
145
|
+
"minimum_apy_edge": MINIMUM_APY_IMPROVEMENT,
|
|
146
|
+
"rotation_cooldown_days": ROTATION_MIN_INTERVAL.days,
|
|
147
|
+
"profit_horizon_days": MINIMUM_DAYS_UNTIL_PROFIT,
|
|
73
148
|
},
|
|
74
|
-
"process": "Deposits USDC on Base and searches for the highest yield opportunities among Base-based DeFi protocols",
|
|
75
|
-
"requirements": [
|
|
76
|
-
"Sufficient USDC balance on Base",
|
|
77
|
-
"Base ETH available for gas",
|
|
78
|
-
],
|
|
79
|
-
"result": "Funds deployed to a yield farming position on Base",
|
|
80
|
-
},
|
|
81
|
-
"withdraw": {
|
|
82
|
-
"parameters": {},
|
|
83
|
-
"process": "Exits yield positions on Base and returns USDC to the user wallet",
|
|
84
|
-
"requirements": [
|
|
85
|
-
"Active positions to exit",
|
|
86
|
-
"Gas for transactions on Base",
|
|
87
|
-
],
|
|
88
|
-
"result": "USDC returned to wallet and positions closed on Base",
|
|
89
|
-
},
|
|
90
|
-
"update": {
|
|
91
|
-
"parameters": {},
|
|
92
|
-
"process": "Scans for better yield opportunities on Base and rebalances positions automatically",
|
|
93
|
-
"frequency": "Call daily or when significant yield changes occur",
|
|
94
|
-
"requirements": [
|
|
95
|
-
"Active strategy positions on Base",
|
|
96
|
-
"Sufficient Base gas for rebalancing",
|
|
97
|
-
],
|
|
98
|
-
"result": "Positions optimized for maximum yield on Base",
|
|
99
|
-
},
|
|
100
|
-
"technical_details": {
|
|
101
|
-
"wallet_structure": "Uses strategy subwallet for isolation",
|
|
102
|
-
"chains": ["Base"],
|
|
103
|
-
"protocols": ["Various Base DeFi yield protocols"],
|
|
104
|
-
"tokens": ["USDC"],
|
|
105
|
-
"gas_requirements": "~0.001-0.02 ETH per rebalance on Base",
|
|
106
|
-
"search_depth": SEARCH_DEPTH,
|
|
107
|
-
"minimum_tvl": MIN_TVL,
|
|
108
|
-
"dust_apy_threshold": DUST_APY,
|
|
109
|
-
"minimum_apy_edge": MINIMUM_APY_IMPROVEMENT,
|
|
110
|
-
"rotation_cooldown_days": ROTATION_MIN_INTERVAL.days,
|
|
111
|
-
"profit_horizon_days": MINIMUM_DAYS_UNTIL_PROFIT,
|
|
112
149
|
},
|
|
113
|
-
|
|
150
|
+
)
|
|
114
151
|
|
|
115
152
|
def __init__(
|
|
116
153
|
self,
|
|
@@ -4,7 +4,7 @@ from unittest.mock import AsyncMock
|
|
|
4
4
|
|
|
5
5
|
# Ensure wayfinder-paths is on path for tests.test_utils import
|
|
6
6
|
# This is a workaround until conftest loading order is resolved
|
|
7
|
-
_wayfinder_path_dir = Path(__file__).parent.parent.parent.
|
|
7
|
+
_wayfinder_path_dir = Path(__file__).parent.parent.parent.resolve()
|
|
8
8
|
_wayfinder_path_str = str(_wayfinder_path_dir)
|
|
9
9
|
if _wayfinder_path_str not in sys.path:
|
|
10
10
|
sys.path.insert(0, _wayfinder_path_str)
|
|
@@ -29,7 +29,7 @@ except ImportError:
|
|
|
29
29
|
get_canonical_examples = test_utils.get_canonical_examples
|
|
30
30
|
load_strategy_examples = test_utils.load_strategy_examples
|
|
31
31
|
|
|
32
|
-
from wayfinder_paths.
|
|
32
|
+
from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import ( # noqa: E402
|
|
33
33
|
StablecoinYieldStrategy,
|
|
34
34
|
)
|
|
35
35
|
|
|
@@ -8,7 +8,7 @@ Adapters expose protocol-specific capabilities to strategies. They should be thi
|
|
|
8
8
|
```
|
|
9
9
|
cp -r wayfinder_paths/vaults/templates/adapter wayfinder_paths/vaults/adapters/my_adapter
|
|
10
10
|
```
|
|
11
|
-
2. Rename `MyAdapter` in `adapter.py` and update `manifest.yaml` so the `entrypoint` matches (`
|
|
11
|
+
2. Rename `MyAdapter` in `adapter.py` and update `manifest.yaml` so the `entrypoint` matches (`adapters.my_adapter.adapter.MyAdapter`).
|
|
12
12
|
3. Declare the capabilities your adapter will provide and list any client dependencies (e.g., `PoolClient`, `LedgerClient`).
|
|
13
13
|
4. Implement the public methods that fulfill those capabilities.
|
|
14
14
|
|
|
@@ -63,7 +63,7 @@ Every adapter needs a manifest describing its import path, declared capabilities
|
|
|
63
63
|
|
|
64
64
|
```yaml
|
|
65
65
|
schema_version: "0.1"
|
|
66
|
-
entrypoint: "
|
|
66
|
+
entrypoint: "adapters.my_adapter.adapter.MyAdapter"
|
|
67
67
|
capabilities:
|
|
68
68
|
- "pool.read"
|
|
69
69
|
dependencies:
|
|
@@ -80,13 +80,13 @@ The `dependencies` list is informational today but helps reviewers understand wh
|
|
|
80
80
|
import pytest
|
|
81
81
|
from unittest.mock import AsyncMock, patch
|
|
82
82
|
|
|
83
|
-
from wayfinder_paths.
|
|
83
|
+
from wayfinder_paths.adapters.my_adapter.adapter import MyAdapter
|
|
84
84
|
|
|
85
85
|
|
|
86
86
|
@pytest.mark.asyncio
|
|
87
87
|
async def test_get_pools():
|
|
88
88
|
with patch(
|
|
89
|
-
"wayfinder_paths.
|
|
89
|
+
"wayfinder_paths.adapters.my_adapter.adapter.PoolClient",
|
|
90
90
|
return_value=AsyncMock(
|
|
91
91
|
get_pools_by_ids=AsyncMock(return_value={"pools": []})
|
|
92
92
|
),
|