wayfinder-paths 0.1.21__py3-none-any.whl → 0.1.23__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/__init__.py +0 -4
- wayfinder_paths/adapters/balance_adapter/README.md +0 -1
- wayfinder_paths/adapters/balance_adapter/adapter.py +65 -169
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +41 -113
- wayfinder_paths/adapters/brap_adapter/README.md +22 -75
- wayfinder_paths/adapters/brap_adapter/adapter.py +187 -576
- wayfinder_paths/adapters/brap_adapter/examples.json +21 -140
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +6 -234
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +39 -86
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +5 -1
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +6 -5
- wayfinder_paths/adapters/ledger_adapter/README.md +4 -1
- wayfinder_paths/adapters/ledger_adapter/adapter.py +3 -3
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +108 -198
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +37 -23
- wayfinder_paths/adapters/token_adapter/adapter.py +14 -0
- wayfinder_paths/core/__init__.py +0 -3
- wayfinder_paths/core/clients/BRAPClient.py +3 -0
- wayfinder_paths/core/clients/ClientManager.py +0 -7
- wayfinder_paths/core/clients/LedgerClient.py +196 -172
- wayfinder_paths/core/clients/WayfinderClient.py +0 -1
- wayfinder_paths/core/clients/__init__.py +0 -5
- wayfinder_paths/core/clients/protocols.py +0 -13
- wayfinder_paths/core/config.py +0 -164
- wayfinder_paths/core/constants/__init__.py +58 -2
- wayfinder_paths/core/constants/base.py +8 -22
- wayfinder_paths/core/constants/chains.py +36 -0
- wayfinder_paths/core/constants/contracts.py +39 -0
- wayfinder_paths/core/constants/tokens.py +9 -0
- wayfinder_paths/core/strategies/Strategy.py +0 -10
- wayfinder_paths/core/utils/evm_helpers.py +5 -15
- wayfinder_paths/core/utils/tokens.py +28 -0
- wayfinder_paths/core/utils/transaction.py +13 -7
- wayfinder_paths/core/utils/web3.py +5 -3
- wayfinder_paths/policies/enso.py +1 -2
- wayfinder_paths/policies/hyper_evm.py +6 -3
- wayfinder_paths/policies/hyperlend.py +1 -2
- wayfinder_paths/policies/moonwell.py +12 -7
- wayfinder_paths/policies/prjx.py +1 -3
- wayfinder_paths/run_strategy.py +97 -300
- wayfinder_paths/strategies/basis_trading_strategy/constants.py +3 -1
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +19 -14
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +12 -11
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +20 -33
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +21 -18
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +69 -130
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +32 -42
- {wayfinder_paths-0.1.21.dist-info → wayfinder_paths-0.1.23.dist-info}/METADATA +3 -4
- {wayfinder_paths-0.1.21.dist-info → wayfinder_paths-0.1.23.dist-info}/RECORD +51 -60
- {wayfinder_paths-0.1.21.dist-info → wayfinder_paths-0.1.23.dist-info}/WHEEL +1 -1
- wayfinder_paths/core/clients/WalletClient.py +0 -41
- wayfinder_paths/core/engine/StrategyJob.py +0 -110
- wayfinder_paths/core/services/test_local_evm_txn.py +0 -145
- wayfinder_paths/templates/adapter/README.md +0 -150
- wayfinder_paths/templates/adapter/adapter.py +0 -16
- wayfinder_paths/templates/adapter/examples.json +0 -8
- wayfinder_paths/templates/adapter/test_adapter.py +0 -30
- wayfinder_paths/templates/strategy/README.md +0 -186
- wayfinder_paths/templates/strategy/examples.json +0 -11
- wayfinder_paths/templates/strategy/strategy.py +0 -35
- wayfinder_paths/templates/strategy/test_strategy.py +0 -166
- wayfinder_paths/tests/test_smoke_manifest.py +0 -63
- {wayfinder_paths-0.1.21.dist-info → wayfinder_paths-0.1.23.dist-info}/LICENSE +0 -0
wayfinder_paths/run_strategy.py
CHANGED
|
@@ -2,345 +2,142 @@
|
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
4
|
import asyncio
|
|
5
|
+
import importlib
|
|
6
|
+
import inspect
|
|
5
7
|
import json
|
|
6
8
|
import sys
|
|
7
|
-
from
|
|
9
|
+
from typing import Any
|
|
8
10
|
|
|
9
11
|
from loguru import logger
|
|
10
12
|
|
|
11
|
-
from wayfinder_paths.core.config import
|
|
12
|
-
from wayfinder_paths.core.
|
|
13
|
+
from wayfinder_paths.core.config import CONFIG
|
|
14
|
+
from wayfinder_paths.core.strategies.Strategy import Strategy
|
|
13
15
|
from wayfinder_paths.core.utils.evm_helpers import resolve_private_key_for_from_address
|
|
14
16
|
from wayfinder_paths.core.utils.web3 import get_transaction_chain_id, web3_from_chain_id
|
|
15
17
|
|
|
16
18
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
config: StrategyJobConfig,
|
|
21
|
-
):
|
|
22
|
-
strategies_dir = Path(__file__).parent / "strategies"
|
|
23
|
-
strategy_dir = strategies_dir / strategy_name
|
|
19
|
+
def get_strategy_config(strategy_name: str) -> dict[str, Any]:
|
|
20
|
+
config = dict(CONFIG.get("strategy", {}))
|
|
21
|
+
wallets = {w["label"]: w for w in CONFIG.get("wallets", [])}
|
|
24
22
|
|
|
25
|
-
if not
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
for path in strategies_dir.iterdir():
|
|
30
|
-
if path.is_dir() and (path / "strategy.py").exists():
|
|
31
|
-
available.append(path.name)
|
|
32
|
-
available_str = ", ".join(available) if available else "none"
|
|
33
|
-
raise ValueError(
|
|
34
|
-
f"Unknown strategy: {strategy_name}. Available strategies: {available_str}"
|
|
35
|
-
)
|
|
36
|
-
|
|
37
|
-
module_path = f"strategies.{strategy_name}.strategy"
|
|
38
|
-
module = __import__(module_path, fromlist=[""])
|
|
39
|
-
|
|
40
|
-
# Find the Strategy subclass in the module
|
|
41
|
-
from wayfinder_paths.core.strategies.Strategy import Strategy
|
|
42
|
-
|
|
43
|
-
strategy_class = None
|
|
44
|
-
for attr_name in dir(module):
|
|
45
|
-
attr = getattr(module, attr_name)
|
|
46
|
-
if (
|
|
47
|
-
isinstance(attr, type)
|
|
48
|
-
and issubclass(attr, Strategy)
|
|
49
|
-
and attr is not Strategy
|
|
50
|
-
):
|
|
51
|
-
strategy_class = attr
|
|
52
|
-
break
|
|
23
|
+
if "main_wallet" not in config and "main" in wallets:
|
|
24
|
+
config["main_wallet"] = {"address": wallets["main"]["address"]}
|
|
25
|
+
if "strategy_wallet" not in config and strategy_name in wallets:
|
|
26
|
+
config["strategy_wallet"] = {"address": wallets[strategy_name]["address"]}
|
|
53
27
|
|
|
54
|
-
|
|
55
|
-
|
|
28
|
+
by_addr = {w["address"].lower(): w for w in CONFIG.get("wallets", [])}
|
|
29
|
+
for key in ("main_wallet", "strategy_wallet"):
|
|
30
|
+
if wallet := config.get(key):
|
|
31
|
+
if entry := by_addr.get(wallet.get("address", "").lower()):
|
|
32
|
+
if pk := entry.get("private_key") or entry.get("private_key_hex"):
|
|
33
|
+
wallet["private_key_hex"] = pk
|
|
34
|
+
return config
|
|
56
35
|
|
|
57
|
-
main_wallet = config.strategy_config.get("main_wallet") or {}
|
|
58
|
-
strategy_wallet = config.strategy_config.get("strategy_wallet") or {}
|
|
59
|
-
main_wallet_address = main_wallet.get("address")
|
|
60
|
-
strategy_wallet_address = strategy_wallet.get("address")
|
|
61
36
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
)
|
|
37
|
+
def create_signing_callback(address: str, config: dict[str, Any]):
|
|
38
|
+
async def sign(transaction: dict) -> str:
|
|
39
|
+
pk = resolve_private_key_for_from_address(address, config)
|
|
66
40
|
async with web3_from_chain_id(get_transaction_chain_id(transaction)) as web3:
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
async def strategy_wallet_signing_callback(transaction):
|
|
71
|
-
private_key = resolve_private_key_for_from_address(
|
|
72
|
-
strategy_wallet_address,
|
|
73
|
-
config.strategy_config,
|
|
74
|
-
)
|
|
75
|
-
async with web3_from_chain_id(get_transaction_chain_id(transaction)) as web3:
|
|
76
|
-
signed = web3.eth.account.sign_transaction(transaction, private_key)
|
|
77
|
-
return signed.raw_transaction.hex()
|
|
78
|
-
|
|
79
|
-
return strategy_class(
|
|
80
|
-
config=config.strategy_config,
|
|
81
|
-
main_wallet_signing_callback=main_wallet_signing_callback,
|
|
82
|
-
strategy_wallet_signing_callback=strategy_wallet_signing_callback,
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def load_config(
|
|
87
|
-
config_path: str | None = None, strategy_name: str | None = None
|
|
88
|
-
) -> StrategyJobConfig:
|
|
89
|
-
# Default to config.json if not provided
|
|
90
|
-
if not config_path:
|
|
91
|
-
config_path = "config.json"
|
|
41
|
+
return web3.eth.account.sign_transaction(
|
|
42
|
+
transaction, pk
|
|
43
|
+
).raw_transaction.hex()
|
|
92
44
|
|
|
93
|
-
|
|
94
|
-
if not config_file.exists():
|
|
95
|
-
raise FileNotFoundError(
|
|
96
|
-
f"Config file not found: {config_path}. "
|
|
97
|
-
"Please create config.json (see wayfinder_paths/config.example.json for template)"
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
logger.info(f"Loading config from {config_path}")
|
|
101
|
-
with open(config_file) as f:
|
|
102
|
-
config_data = json.load(f)
|
|
45
|
+
return sign
|
|
103
46
|
|
|
104
|
-
config = StrategyJobConfig.from_dict(config_data, strategy_name=strategy_name)
|
|
105
|
-
if strategy_name:
|
|
106
|
-
config.strategy_config["_strategy_name"] = strategy_name
|
|
107
|
-
config.__post_init__()
|
|
108
|
-
return config
|
|
109
47
|
|
|
48
|
+
def find_strategy_class(module) -> type[Strategy]:
|
|
49
|
+
for _, obj in inspect.getmembers(module, inspect.isclass):
|
|
50
|
+
if issubclass(obj, Strategy) and obj is not Strategy:
|
|
51
|
+
return obj
|
|
52
|
+
raise ValueError(f"No Strategy subclass found in {module.__name__}")
|
|
110
53
|
|
|
111
|
-
async def run_strategy(
|
|
112
|
-
strategy_name: str | None = None,
|
|
113
|
-
config_path: str | None = None,
|
|
114
|
-
action: str = "run",
|
|
115
|
-
**kwargs,
|
|
116
|
-
):
|
|
117
|
-
try:
|
|
118
|
-
if not strategy_name:
|
|
119
|
-
raise ValueError("strategy_name is required")
|
|
120
54
|
|
|
121
|
-
|
|
55
|
+
async def run_strategy(strategy_name: str, action: str = "status", **kw):
|
|
56
|
+
config = get_strategy_config(strategy_name)
|
|
122
57
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
logger.debug(
|
|
128
|
-
"Loaded config: wallets(main={} strategy={})",
|
|
129
|
-
main_wallet_cfg.get("address") or "none",
|
|
130
|
-
strategy_wallet_cfg.get("address") or "none",
|
|
131
|
-
)
|
|
58
|
+
def signing_cb(key: str):
|
|
59
|
+
if addr := config.get(key, {}).get("address"):
|
|
60
|
+
return create_signing_callback(addr, config)
|
|
61
|
+
return None
|
|
132
62
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
63
|
+
module = importlib.import_module(
|
|
64
|
+
f"wayfinder_paths.strategies.{strategy_name}.strategy"
|
|
65
|
+
)
|
|
66
|
+
strategy_cls = find_strategy_class(module)
|
|
67
|
+
strategy = strategy_cls(
|
|
68
|
+
config,
|
|
69
|
+
main_wallet_signing_callback=signing_cb("main_wallet"),
|
|
70
|
+
strategy_wallet_signing_callback=signing_cb("strategy_wallet"),
|
|
71
|
+
)
|
|
72
|
+
await strategy.setup()
|
|
73
|
+
|
|
74
|
+
if action == "policy":
|
|
75
|
+
policies = strategy.policies() if hasattr(strategy, "policies") else []
|
|
76
|
+
if wallet_id := kw.get("wallet_id"):
|
|
77
|
+
policies = [p.replace("FORMAT_WALLET_ID", wallet_id) for p in policies]
|
|
78
|
+
result = {"policies": policies}
|
|
79
|
+
elif action == "status":
|
|
80
|
+
result = await strategy.status()
|
|
81
|
+
elif action == "deposit":
|
|
82
|
+
result = await strategy.deposit(
|
|
83
|
+
main_token_amount=kw.get("main_token_amount", 0.0),
|
|
84
|
+
gas_token_amount=kw.get("gas_token_amount", 0.0),
|
|
138
85
|
)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
# Execute action
|
|
148
|
-
if action == "run":
|
|
149
|
-
logger.info("Starting continuous execution...")
|
|
150
|
-
await strategy_job.run_continuous(interval_seconds=kwargs.get("interval"))
|
|
151
|
-
|
|
152
|
-
elif action == "deposit":
|
|
153
|
-
main_token_amount = kwargs.get("main_token_amount")
|
|
154
|
-
gas_token_amount = kwargs.get("gas_token_amount")
|
|
155
|
-
|
|
156
|
-
if main_token_amount is None and gas_token_amount is None:
|
|
157
|
-
raise ValueError(
|
|
158
|
-
"Either main token amount or gas token amount required for deposit (use --main-token-amount and/or --gas-token-amount)"
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
# Default to 0.0 if not provided
|
|
162
|
-
if main_token_amount is None:
|
|
163
|
-
main_token_amount = 0.0
|
|
164
|
-
if gas_token_amount is None:
|
|
165
|
-
gas_token_amount = 0.0
|
|
166
|
-
|
|
167
|
-
result = await strategy_job.execute_strategy(
|
|
168
|
-
"deposit",
|
|
169
|
-
main_token_amount=main_token_amount,
|
|
170
|
-
gas_token_amount=gas_token_amount,
|
|
171
|
-
)
|
|
172
|
-
logger.info(f"Deposit result: {result}")
|
|
173
|
-
|
|
174
|
-
elif action == "withdraw":
|
|
175
|
-
amount = kwargs.get("amount")
|
|
176
|
-
result = await strategy_job.execute_strategy("withdraw", amount=amount)
|
|
177
|
-
logger.info(f"Withdraw result: {result}")
|
|
178
|
-
|
|
179
|
-
elif action == "status":
|
|
180
|
-
result = await strategy_job.execute_strategy("status")
|
|
181
|
-
logger.info(f"Status: {json.dumps(result, indent=2)}")
|
|
182
|
-
|
|
183
|
-
elif action == "update":
|
|
184
|
-
result = await strategy_job.execute_strategy("update")
|
|
185
|
-
logger.info(f"Update result: {result}")
|
|
186
|
-
|
|
187
|
-
elif action == "exit":
|
|
188
|
-
result = await strategy_job.execute_strategy("exit")
|
|
189
|
-
logger.info(f"Exit result: {result}")
|
|
190
|
-
|
|
191
|
-
elif action == "partial-liquidate":
|
|
192
|
-
usd_value = kwargs.get("amount")
|
|
193
|
-
if not usd_value:
|
|
194
|
-
raise ValueError("Amount (USD value) required for partial-liquidate")
|
|
195
|
-
result = await strategy_job.execute_strategy(
|
|
196
|
-
"partial_liquidate", usd_value=usd_value
|
|
197
|
-
)
|
|
198
|
-
logger.info(f"Partial liquidation result: {result}")
|
|
199
|
-
|
|
200
|
-
elif action == "policy":
|
|
201
|
-
policies: list[str] = []
|
|
202
|
-
|
|
86
|
+
elif action == "withdraw":
|
|
87
|
+
result = await strategy.withdraw(amount=kw.get("amount"))
|
|
88
|
+
elif action == "update":
|
|
89
|
+
result = await strategy.update()
|
|
90
|
+
elif action == "exit":
|
|
91
|
+
result = await strategy.exit()
|
|
92
|
+
elif action == "run":
|
|
93
|
+
while True:
|
|
203
94
|
try:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if isinstance(result, list) and result:
|
|
208
|
-
policies = [p for p in result if isinstance(p, str)]
|
|
209
|
-
except Exception:
|
|
210
|
-
pass
|
|
211
|
-
|
|
212
|
-
seen = set()
|
|
213
|
-
deduped: list[str] = []
|
|
214
|
-
for p in policies:
|
|
215
|
-
if p not in seen:
|
|
216
|
-
seen.add(p)
|
|
217
|
-
deduped.append(p)
|
|
218
|
-
|
|
219
|
-
wallet_id = kwargs.get("wallet_id")
|
|
220
|
-
if not wallet_id:
|
|
221
|
-
wallet_id = config.strategy_config.get("wallet_id")
|
|
222
|
-
if not wallet_id:
|
|
223
|
-
wallet_id = config.system.wallet_id
|
|
224
|
-
|
|
225
|
-
# Render policies with wallet_id if available
|
|
226
|
-
if wallet_id:
|
|
227
|
-
rendered = [
|
|
228
|
-
p.replace("FORMAT_WALLET_ID", str(wallet_id)) for p in deduped
|
|
229
|
-
]
|
|
230
|
-
else:
|
|
231
|
-
rendered = deduped
|
|
232
|
-
logger.info(
|
|
233
|
-
"Policy rendering without wallet_id - policies contain FORMAT_WALLET_ID placeholder"
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
logger.info(json.dumps({"policies": rendered}, indent=2))
|
|
237
|
-
|
|
238
|
-
elif action == "script":
|
|
239
|
-
duration = kwargs.get("duration") or 300
|
|
240
|
-
logger.info(f"Running script mode for {duration}s...")
|
|
241
|
-
task = asyncio.create_task(
|
|
242
|
-
strategy_job.run_continuous(
|
|
243
|
-
interval_seconds=kwargs.get("interval") or 60
|
|
244
|
-
)
|
|
245
|
-
)
|
|
246
|
-
await asyncio.sleep(duration)
|
|
247
|
-
task.cancel()
|
|
248
|
-
try:
|
|
249
|
-
await task
|
|
95
|
+
result = await strategy.update()
|
|
96
|
+
logger.info(f"Update: {result}")
|
|
97
|
+
await asyncio.sleep(kw.get("interval", 60))
|
|
250
98
|
except asyncio.CancelledError:
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
finally:
|
|
262
|
-
if "strategy_job" in locals():
|
|
263
|
-
await strategy_job.stop()
|
|
99
|
+
result = (True, "stopped")
|
|
100
|
+
break
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError(f"Unknown action: {action}")
|
|
103
|
+
|
|
104
|
+
print(
|
|
105
|
+
json.dumps(result, indent=2)
|
|
106
|
+
if isinstance(result, dict)
|
|
107
|
+
else f"{action}: {result}"
|
|
108
|
+
)
|
|
264
109
|
|
|
265
110
|
|
|
266
111
|
def main():
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
help="Strategy to run (stablecoin_yield_strategy)",
|
|
271
|
-
)
|
|
272
|
-
parser.add_argument(
|
|
273
|
-
"--config", help="Path to config file (defaults to config.json)"
|
|
274
|
-
)
|
|
275
|
-
parser.add_argument(
|
|
112
|
+
p = argparse.ArgumentParser()
|
|
113
|
+
p.add_argument("strategy")
|
|
114
|
+
p.add_argument(
|
|
276
115
|
"--action",
|
|
277
|
-
default="
|
|
278
|
-
choices=[
|
|
279
|
-
"run",
|
|
280
|
-
"deposit",
|
|
281
|
-
"withdraw",
|
|
282
|
-
"status",
|
|
283
|
-
"update",
|
|
284
|
-
"exit",
|
|
285
|
-
"policy",
|
|
286
|
-
"script",
|
|
287
|
-
"partial-liquidate",
|
|
288
|
-
],
|
|
289
|
-
help="Action to perform (default: run)",
|
|
116
|
+
default="status",
|
|
117
|
+
choices=["run", "deposit", "withdraw", "status", "update", "exit", "policy"],
|
|
290
118
|
)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
119
|
+
p.add_argument("--amount", type=float)
|
|
120
|
+
p.add_argument("--main-token-amount", type=float, dest="main_token_amount")
|
|
121
|
+
p.add_argument(
|
|
122
|
+
"--gas-token-amount", type=float, dest="gas_token_amount", default=0.0
|
|
295
123
|
)
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
dest="main_token_amount",
|
|
301
|
-
help="Main token amount for deposit action",
|
|
302
|
-
)
|
|
303
|
-
parser.add_argument(
|
|
304
|
-
"--gas-token-amount",
|
|
305
|
-
"--gas_token_amount",
|
|
306
|
-
type=float,
|
|
307
|
-
dest="gas_token_amount",
|
|
308
|
-
default=0.0,
|
|
309
|
-
help="Gas token amount for deposit action (default: 0.0)",
|
|
310
|
-
)
|
|
311
|
-
parser.add_argument(
|
|
312
|
-
"--interval",
|
|
313
|
-
type=int,
|
|
314
|
-
help="Update interval in seconds for continuous/script modes",
|
|
315
|
-
)
|
|
316
|
-
parser.add_argument(
|
|
317
|
-
"--duration", type=int, help="Duration in seconds for script action"
|
|
318
|
-
)
|
|
319
|
-
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
|
|
320
|
-
parser.add_argument(
|
|
321
|
-
"--wallet-id",
|
|
322
|
-
help="Wallet ID for policy rendering (replaces FORMAT_WALLET_ID in policies)",
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
args = parser.parse_args()
|
|
124
|
+
p.add_argument("--interval", type=int, default=60)
|
|
125
|
+
p.add_argument("--wallet-id", dest="wallet_id")
|
|
126
|
+
p.add_argument("--debug", action="store_true")
|
|
127
|
+
args = p.parse_args()
|
|
326
128
|
|
|
327
|
-
# Configure logging
|
|
328
|
-
log_level = "DEBUG" if args.debug else "INFO"
|
|
329
129
|
logger.remove()
|
|
330
|
-
logger.add(sys.stderr, level=
|
|
130
|
+
logger.add(sys.stderr, level="DEBUG" if args.debug else "INFO")
|
|
331
131
|
|
|
332
|
-
# Run strategy
|
|
333
132
|
asyncio.run(
|
|
334
133
|
run_strategy(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
action=args.action,
|
|
134
|
+
args.strategy,
|
|
135
|
+
args.action,
|
|
338
136
|
amount=args.amount,
|
|
339
137
|
main_token_amount=args.main_token_amount,
|
|
340
138
|
gas_token_amount=args.gas_token_amount,
|
|
341
139
|
interval=args.interval,
|
|
342
|
-
|
|
343
|
-
wallet_id=getattr(args, "wallet_id", None),
|
|
140
|
+
wallet_id=args.wallet_id,
|
|
344
141
|
)
|
|
345
142
|
)
|
|
346
143
|
|
|
@@ -446,7 +446,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
|
|
|
446
446
|
strategy_balance_ok,
|
|
447
447
|
strategy_balance,
|
|
448
448
|
) = await self.balance_adapter.get_balance(
|
|
449
|
-
|
|
449
|
+
token_id=USDC_ARBITRUM_TOKEN_ID,
|
|
450
450
|
wallet_address=strategy_address,
|
|
451
451
|
)
|
|
452
452
|
strategy_usdc = 0.0
|
|
@@ -505,7 +505,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
|
|
|
505
505
|
strategy_balance_ok,
|
|
506
506
|
strategy_balance,
|
|
507
507
|
) = await self.balance_adapter.get_balance(
|
|
508
|
-
|
|
508
|
+
token_id=USDC_ARBITRUM_TOKEN_ID,
|
|
509
509
|
wallet_address=strategy_address,
|
|
510
510
|
)
|
|
511
511
|
if strategy_balance_ok and strategy_balance:
|
|
@@ -759,7 +759,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
|
|
|
759
759
|
strategy_usdc = 0.0
|
|
760
760
|
try:
|
|
761
761
|
success, balance_data = await self.balance_adapter.get_balance(
|
|
762
|
-
|
|
762
|
+
token_id=usdc_token_id,
|
|
763
763
|
wallet_address=address,
|
|
764
764
|
)
|
|
765
765
|
if success:
|
|
@@ -921,7 +921,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
|
|
|
921
921
|
final_balance = 0.0
|
|
922
922
|
try:
|
|
923
923
|
success, balance_data = await self.balance_adapter.get_balance(
|
|
924
|
-
|
|
924
|
+
token_id=usdc_token_id,
|
|
925
925
|
wallet_address=address,
|
|
926
926
|
)
|
|
927
927
|
if success:
|
|
@@ -962,7 +962,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
|
|
|
962
962
|
success,
|
|
963
963
|
msg,
|
|
964
964
|
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
965
|
-
|
|
965
|
+
token_id=USDC_ARBITRUM_TOKEN_ID,
|
|
966
966
|
amount=usdc_balance,
|
|
967
967
|
strategy_name=self.name,
|
|
968
968
|
skip_ledger=False,
|
|
@@ -989,7 +989,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
|
|
|
989
989
|
success,
|
|
990
990
|
msg,
|
|
991
991
|
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
992
|
-
|
|
992
|
+
token_id="ethereum-arbitrum",
|
|
993
993
|
amount=transferable_eth,
|
|
994
994
|
strategy_name=self.name,
|
|
995
995
|
skip_ledger=False,
|
|
@@ -2249,17 +2249,22 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
|
|
|
2249
2249
|
if not success or not transactions:
|
|
2250
2250
|
return None
|
|
2251
2251
|
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2252
|
+
tx_list = (
|
|
2253
|
+
transactions.get("transactions", [])
|
|
2254
|
+
if isinstance(transactions, dict)
|
|
2255
|
+
else []
|
|
2256
|
+
)
|
|
2257
|
+
for txn in tx_list:
|
|
2258
|
+
op_data = txn.get("op_data", {})
|
|
2256
2259
|
if (
|
|
2257
2260
|
op_data.get("type") == "HYPE_SPOT"
|
|
2258
2261
|
and op_data.get("buy_or_sell") == "buy"
|
|
2259
2262
|
):
|
|
2260
|
-
|
|
2261
|
-
if
|
|
2262
|
-
return datetime.fromisoformat(
|
|
2263
|
+
created_str = txn.get("created")
|
|
2264
|
+
if created_str:
|
|
2265
|
+
return datetime.fromisoformat(
|
|
2266
|
+
str(created_str).replace("Z", "+00:00")
|
|
2267
|
+
)
|
|
2263
2268
|
|
|
2264
2269
|
return None
|
|
2265
2270
|
except Exception as e:
|
|
@@ -2339,7 +2344,7 @@ class BasisTradingStrategy(BasisSnapshotMixin, Strategy):
|
|
|
2339
2344
|
try:
|
|
2340
2345
|
strategy_address = self._get_strategy_wallet_address()
|
|
2341
2346
|
success, balance = await self.balance_adapter.get_balance(
|
|
2342
|
-
|
|
2347
|
+
token_id=USDC_ARBITRUM_TOKEN_ID,
|
|
2343
2348
|
wallet_address=strategy_address,
|
|
2344
2349
|
)
|
|
2345
2350
|
if success and balance:
|
|
@@ -18,6 +18,7 @@ from wayfinder_paths.adapters.hyperlend_adapter.adapter import HyperlendAdapter
|
|
|
18
18
|
from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
|
|
19
19
|
from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
|
|
20
20
|
from wayfinder_paths.core.constants.base import DEFAULT_SLIPPAGE
|
|
21
|
+
from wayfinder_paths.core.constants.contracts import HYPEREVM_WHYPE
|
|
21
22
|
from wayfinder_paths.core.strategies.descriptors import (
|
|
22
23
|
Complexity,
|
|
23
24
|
Directionality,
|
|
@@ -50,7 +51,7 @@ SYMBOL_TRANSLATION_TABLE = str.maketrans(
|
|
|
50
51
|
"Ξ": "X",
|
|
51
52
|
}
|
|
52
53
|
)
|
|
53
|
-
WRAPPED_HYPE_ADDRESS =
|
|
54
|
+
WRAPPED_HYPE_ADDRESS = HYPEREVM_WHYPE
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
class HyperlendStableYieldStrategy(Strategy):
|
|
@@ -338,7 +339,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
338
339
|
success,
|
|
339
340
|
main_usdt0_balance,
|
|
340
341
|
) = await self.balance_adapter.get_balance(
|
|
341
|
-
|
|
342
|
+
token_id=self.usdt_token_info.get("token_id"),
|
|
342
343
|
wallet_address=self._get_main_wallet_address(),
|
|
343
344
|
)
|
|
344
345
|
if not success:
|
|
@@ -351,7 +352,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
351
352
|
success,
|
|
352
353
|
main_hype_balance,
|
|
353
354
|
) = await self.balance_adapter.get_balance(
|
|
354
|
-
|
|
355
|
+
token_id=self.hype_token_info.get("token_id"),
|
|
355
356
|
wallet_address=self._get_main_wallet_address(),
|
|
356
357
|
)
|
|
357
358
|
if not success:
|
|
@@ -839,7 +840,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
839
840
|
total_usdt = 0.0
|
|
840
841
|
try:
|
|
841
842
|
_, total_usdt_wei = await self.balance_adapter.get_balance(
|
|
842
|
-
|
|
843
|
+
token_id=self.usdt_token_info.get("token_id"),
|
|
843
844
|
wallet_address=self._get_strategy_wallet_address(),
|
|
844
845
|
)
|
|
845
846
|
if total_usdt_wei and total_usdt_wei > 0:
|
|
@@ -852,7 +853,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
852
853
|
total_hype = 0.0
|
|
853
854
|
try:
|
|
854
855
|
_, total_hype_wei = await self.balance_adapter.get_balance(
|
|
855
|
-
|
|
856
|
+
token_id=self.hype_token_info.get("token_id"),
|
|
856
857
|
wallet_address=self._get_strategy_wallet_address(),
|
|
857
858
|
)
|
|
858
859
|
if total_hype_wei and total_hype_wei > 0:
|
|
@@ -908,7 +909,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
908
909
|
wallet_address=strategy_address,
|
|
909
910
|
)
|
|
910
911
|
if usdt_ok and usdt_raw:
|
|
911
|
-
usdt_balance = float(usdt_raw
|
|
912
|
+
usdt_balance = float(usdt_raw) / 1e6 # USDT has 6 decimals
|
|
912
913
|
if usdt_balance > 1.0:
|
|
913
914
|
self.logger.info(
|
|
914
915
|
f"Transferring {usdt_balance:.2f} USDT0 to main wallet"
|
|
@@ -917,7 +918,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
917
918
|
success,
|
|
918
919
|
msg,
|
|
919
920
|
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
920
|
-
|
|
921
|
+
token_id="usdt0-hyperevm",
|
|
921
922
|
amount=usdt_balance,
|
|
922
923
|
strategy_name=self.name,
|
|
923
924
|
skip_ledger=False,
|
|
@@ -933,7 +934,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
933
934
|
wallet_address=strategy_address,
|
|
934
935
|
)
|
|
935
936
|
if hype_ok and hype_raw:
|
|
936
|
-
hype_balance = float(hype_raw
|
|
937
|
+
hype_balance = float(hype_raw) / 1e18 # HYPE has 18 decimals
|
|
937
938
|
tx_fee_reserve = 0.1
|
|
938
939
|
transferable_hype = hype_balance - tx_fee_reserve
|
|
939
940
|
if transferable_hype > 0.01:
|
|
@@ -944,7 +945,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
944
945
|
success,
|
|
945
946
|
msg,
|
|
946
947
|
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
947
|
-
|
|
948
|
+
token_id="hyperliquid-hyperevm",
|
|
948
949
|
amount=transferable_hype,
|
|
949
950
|
strategy_name=self.name,
|
|
950
951
|
skip_ledger=False,
|
|
@@ -1014,7 +1015,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
1014
1015
|
success,
|
|
1015
1016
|
message,
|
|
1016
1017
|
) = await self.balance_adapter.move_from_strategy_wallet_to_main_wallet(
|
|
1017
|
-
|
|
1018
|
+
token_id=token_info.get("token_id"),
|
|
1018
1019
|
amount=amount_tokens,
|
|
1019
1020
|
strategy_name=self.name,
|
|
1020
1021
|
)
|
|
@@ -2275,7 +2276,7 @@ class HyperlendStableYieldStrategy(Strategy):
|
|
|
2275
2276
|
success,
|
|
2276
2277
|
strategy_hype_balance_wei,
|
|
2277
2278
|
) = await self.balance_adapter.get_balance(
|
|
2278
|
-
|
|
2279
|
+
token_id=self.hype_token_info.get("token_id"),
|
|
2279
2280
|
wallet_address=self._get_strategy_wallet_address(),
|
|
2280
2281
|
)
|
|
2281
2282
|
hype_price = asset_map.get(WRAPPED_HYPE_ADDRESS, {}).get("price_usd") or 0.0
|