wayfinder-paths 0.1.8__py3-none-any.whl → 0.1.9__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.
- wayfinder_paths/CONFIG_GUIDE.md +5 -14
- wayfinder_paths/adapters/brap_adapter/README.md +1 -1
- wayfinder_paths/adapters/brap_adapter/adapter.py +0 -51
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
- wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
- wayfinder_paths/adapters/pool_adapter/README.md +1 -77
- wayfinder_paths/adapters/pool_adapter/adapter.py +0 -122
- wayfinder_paths/adapters/pool_adapter/examples.json +0 -57
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -86
- wayfinder_paths/adapters/token_adapter/README.md +1 -1
- wayfinder_paths/core/clients/AuthClient.py +0 -3
- wayfinder_paths/core/clients/ClientManager.py +1 -22
- wayfinder_paths/core/clients/WalletClient.py +0 -8
- wayfinder_paths/core/clients/WayfinderClient.py +9 -14
- wayfinder_paths/core/clients/__init__.py +0 -8
- wayfinder_paths/core/clients/protocols.py +0 -60
- wayfinder_paths/core/config.py +5 -45
- wayfinder_paths/core/engine/StrategyJob.py +0 -3
- wayfinder_paths/core/services/base.py +0 -49
- wayfinder_paths/core/services/local_evm_txn.py +3 -82
- wayfinder_paths/core/services/local_token_txn.py +61 -70
- wayfinder_paths/core/services/web3_service.py +0 -2
- wayfinder_paths/core/settings.py +8 -8
- wayfinder_paths/core/strategies/Strategy.py +1 -5
- wayfinder_paths/core/utils/evm_helpers.py +7 -12
- wayfinder_paths/core/wallets/README.md +3 -6
- wayfinder_paths/run_strategy.py +29 -32
- wayfinder_paths/scripts/make_wallets.py +1 -25
- wayfinder_paths/scripts/run_strategy.py +0 -2
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +86 -137
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +106 -28
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1 -6
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -4
- wayfinder_paths/templates/strategy/test_strategy.py +0 -4
- {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/METADATA +5 -15
- {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/RECORD +45 -47
- wayfinder_paths/core/clients/SimulationClient.py +0 -192
- wayfinder_paths/core/clients/TransactionClient.py +0 -63
- {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/WHEEL +0 -0
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from decimal import ROUND_DOWN, Decimal
|
|
3
4
|
from typing import Any
|
|
4
5
|
|
|
5
6
|
from eth_utils import to_checksum_address
|
|
6
7
|
from loguru import logger
|
|
7
|
-
from web3 import AsyncWeb3
|
|
8
|
+
from web3 import AsyncWeb3, Web3
|
|
8
9
|
|
|
9
10
|
from wayfinder_paths.core.clients.TokenClient import TokenClient
|
|
10
|
-
from wayfinder_paths.core.clients.TransactionClient import TransactionClient
|
|
11
11
|
from wayfinder_paths.core.constants import ZERO_ADDRESS
|
|
12
|
-
from wayfinder_paths.core.constants.erc20_abi import ERC20_APPROVAL_ABI
|
|
12
|
+
from wayfinder_paths.core.constants.erc20_abi import ERC20_ABI, ERC20_APPROVAL_ABI
|
|
13
13
|
from wayfinder_paths.core.services.base import EvmTxn, TokenTxn
|
|
14
14
|
from wayfinder_paths.core.utils.evm_helpers import resolve_chain_id
|
|
15
15
|
|
|
@@ -22,13 +22,12 @@ class LocalTokenTxnService(TokenTxn):
|
|
|
22
22
|
config: dict[str, Any] | None,
|
|
23
23
|
*,
|
|
24
24
|
wallet_provider: EvmTxn,
|
|
25
|
-
simulation: bool = False,
|
|
26
25
|
) -> None:
|
|
27
|
-
del config
|
|
26
|
+
del config
|
|
28
27
|
self.wallet_provider = wallet_provider
|
|
29
28
|
self.logger = logger.bind(service="DefaultEvmTransactionService")
|
|
30
29
|
self.token_client = TokenClient()
|
|
31
|
-
self.builder = _EvmTransactionBuilder()
|
|
30
|
+
self.builder = _EvmTransactionBuilder(wallet_provider)
|
|
32
31
|
|
|
33
32
|
async def build_send(
|
|
34
33
|
self,
|
|
@@ -51,14 +50,24 @@ class LocalTokenTxnService(TokenTxn):
|
|
|
51
50
|
return False, f"Token {token_id} is missing a chain id"
|
|
52
51
|
|
|
53
52
|
token_address = (token_meta or {}).get("address") or ZERO_ADDRESS
|
|
53
|
+
is_native = not token_address or token_address.lower() == ZERO_ADDRESS.lower()
|
|
54
|
+
|
|
55
|
+
if is_native:
|
|
56
|
+
amount_wei = self._to_base_units(
|
|
57
|
+
amount, 18
|
|
58
|
+
) # Native tokens use 18 decimals
|
|
59
|
+
else:
|
|
60
|
+
decimals = int((token_meta or {}).get("decimals") or 18)
|
|
61
|
+
amount_wei = self._to_base_units(amount, decimals)
|
|
54
62
|
|
|
55
63
|
try:
|
|
56
64
|
tx = await self.builder.build_send_transaction(
|
|
57
65
|
from_address=from_address,
|
|
58
66
|
to_address=to_address,
|
|
59
67
|
token_address=token_address,
|
|
60
|
-
amount=
|
|
68
|
+
amount=amount_wei,
|
|
61
69
|
chain_id=int(chain_id),
|
|
70
|
+
is_native=is_native,
|
|
62
71
|
)
|
|
63
72
|
except Exception as exc: # noqa: BLE001
|
|
64
73
|
return False, f"Failed to build send transaction: {exc}"
|
|
@@ -127,12 +136,20 @@ class LocalTokenTxnService(TokenTxn):
|
|
|
127
136
|
raise ValueError("Chain ID is required")
|
|
128
137
|
return int(chain_id)
|
|
129
138
|
|
|
139
|
+
def _to_base_units(self, amount: float, decimals: int) -> int:
|
|
140
|
+
"""Convert human-readable amount to base units (wei for native, token units for ERC20)."""
|
|
141
|
+
scale = Decimal(10) ** int(decimals)
|
|
142
|
+
quantized = (Decimal(str(amount)) * scale).to_integral_value(
|
|
143
|
+
rounding=ROUND_DOWN
|
|
144
|
+
)
|
|
145
|
+
return int(quantized)
|
|
146
|
+
|
|
130
147
|
|
|
131
148
|
class _EvmTransactionBuilder:
|
|
132
149
|
"""Helpers that only build transaction dictionaries for sends and approvals."""
|
|
133
150
|
|
|
134
|
-
def __init__(self) -> None:
|
|
135
|
-
self.
|
|
151
|
+
def __init__(self, wallet_provider: EvmTxn) -> None:
|
|
152
|
+
self.wallet_provider = wallet_provider
|
|
136
153
|
|
|
137
154
|
async def build_send_transaction(
|
|
138
155
|
self,
|
|
@@ -140,22 +157,37 @@ class _EvmTransactionBuilder:
|
|
|
140
157
|
from_address: str,
|
|
141
158
|
to_address: str,
|
|
142
159
|
token_address: str | None,
|
|
143
|
-
amount:
|
|
160
|
+
amount: int,
|
|
144
161
|
chain_id: int,
|
|
162
|
+
is_native: bool,
|
|
145
163
|
) -> dict[str, Any]:
|
|
146
164
|
"""Build the transaction dict for sending native or ERC20 tokens."""
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
from_checksum = to_checksum_address(from_address)
|
|
166
|
+
to_checksum = to_checksum_address(to_address)
|
|
167
|
+
chain_id_int = int(chain_id)
|
|
168
|
+
|
|
169
|
+
if is_native:
|
|
170
|
+
return {
|
|
171
|
+
"chainId": chain_id_int,
|
|
172
|
+
"from": from_checksum,
|
|
173
|
+
"to": to_checksum,
|
|
174
|
+
"value": int(amount),
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
token_checksum = to_checksum_address(token_address or ZERO_ADDRESS)
|
|
178
|
+
w3_sync = Web3()
|
|
179
|
+
contract = w3_sync.eth.contract(address=token_checksum, abi=ERC20_ABI)
|
|
180
|
+
data = contract.functions.transfer(
|
|
181
|
+
to_checksum, int(amount)
|
|
182
|
+
)._encode_transaction_data()
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
"chainId": chain_id_int,
|
|
186
|
+
"from": from_checksum,
|
|
187
|
+
"to": token_checksum,
|
|
188
|
+
"data": data,
|
|
189
|
+
"value": 0,
|
|
190
|
+
}
|
|
159
191
|
|
|
160
192
|
def build_erc20_approval_transaction(
|
|
161
193
|
self,
|
|
@@ -173,10 +205,13 @@ class _EvmTransactionBuilder:
|
|
|
173
205
|
from_checksum = to_checksum_address(from_address)
|
|
174
206
|
amount_int = int(amount)
|
|
175
207
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
208
|
+
# Use synchronous Web3 for encoding (encodeABI doesn't exist in web3.py v7)
|
|
209
|
+
w3_sync = Web3()
|
|
210
|
+
contract = w3_sync.eth.contract(address=token_checksum, abi=ERC20_APPROVAL_ABI)
|
|
211
|
+
# In web3.py v7, use _encode_transaction_data to encode without network calls
|
|
212
|
+
data = contract.functions.approve(
|
|
213
|
+
spender_checksum, amount_int
|
|
214
|
+
)._encode_transaction_data()
|
|
180
215
|
|
|
181
216
|
return {
|
|
182
217
|
"chainId": int(chain_id),
|
|
@@ -185,47 +220,3 @@ class _EvmTransactionBuilder:
|
|
|
185
220
|
"data": data,
|
|
186
221
|
"value": 0,
|
|
187
222
|
}
|
|
188
|
-
|
|
189
|
-
def _payload_to_tx(
|
|
190
|
-
self, payload: dict[str, Any], from_address: str, is_native: bool
|
|
191
|
-
) -> dict[str, Any]:
|
|
192
|
-
data_root = payload.get("data", payload)
|
|
193
|
-
tx_src = data_root.get("transaction") or data_root
|
|
194
|
-
|
|
195
|
-
chain_id = tx_src.get("chainId") or data_root.get("chain_id")
|
|
196
|
-
if chain_id is None:
|
|
197
|
-
raise ValueError("Transaction payload missing chainId")
|
|
198
|
-
|
|
199
|
-
tx: dict[str, Any] = {"chainId": int(chain_id)}
|
|
200
|
-
tx["from"] = to_checksum_address(from_address)
|
|
201
|
-
|
|
202
|
-
if tx_src.get("to"):
|
|
203
|
-
tx["to"] = to_checksum_address(tx_src["to"])
|
|
204
|
-
if tx_src.get("data"):
|
|
205
|
-
data = tx_src["data"]
|
|
206
|
-
tx["data"] = data if str(data).startswith("0x") else f"0x{data}"
|
|
207
|
-
|
|
208
|
-
val = tx_src.get("value", 0)
|
|
209
|
-
tx["value"] = self._normalize_value(val) if is_native else 0
|
|
210
|
-
|
|
211
|
-
if tx_src.get("gas"):
|
|
212
|
-
tx["gas"] = int(tx_src["gas"])
|
|
213
|
-
if tx_src.get("maxFeePerGas"):
|
|
214
|
-
tx["maxFeePerGas"] = int(tx_src["maxFeePerGas"])
|
|
215
|
-
if tx_src.get("maxPriorityFeePerGas"):
|
|
216
|
-
tx["maxPriorityFeePerGas"] = int(tx_src["maxPriorityFeePerGas"])
|
|
217
|
-
if tx_src.get("gasPrice"):
|
|
218
|
-
tx["gasPrice"] = int(tx_src["gasPrice"])
|
|
219
|
-
if tx_src.get("nonce") is not None:
|
|
220
|
-
tx["nonce"] = int(tx_src["nonce"])
|
|
221
|
-
|
|
222
|
-
return tx
|
|
223
|
-
|
|
224
|
-
def _normalize_value(self, value: Any) -> int:
|
|
225
|
-
if isinstance(value, str):
|
|
226
|
-
if value.startswith("0x"):
|
|
227
|
-
return int(value, 16)
|
|
228
|
-
return int(float(value))
|
|
229
|
-
if isinstance(value, (int, float)):
|
|
230
|
-
return int(value)
|
|
231
|
-
return 0
|
|
@@ -16,7 +16,6 @@ class DefaultWeb3Service(Web3Service):
|
|
|
16
16
|
*,
|
|
17
17
|
wallet_provider: EvmTxn | None = None,
|
|
18
18
|
evm_transactions: TokenTxn | None = None,
|
|
19
|
-
simulation: bool = False,
|
|
20
19
|
) -> None:
|
|
21
20
|
"""
|
|
22
21
|
Initialize the service with optional dependency injection.
|
|
@@ -33,7 +32,6 @@ class DefaultWeb3Service(Web3Service):
|
|
|
33
32
|
self._evm_transactions = LocalTokenTxnService(
|
|
34
33
|
config=cfg,
|
|
35
34
|
wallet_provider=self._wallet_provider,
|
|
36
|
-
simulation=simulation,
|
|
37
35
|
)
|
|
38
36
|
|
|
39
37
|
@property
|
wayfinder_paths/core/settings.py
CHANGED
|
@@ -12,14 +12,14 @@ class CoreSettings(BaseSettings):
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
model_config = SettingsConfigDict(
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
# Note: .env file is only used for publishing (REPOSITORY_NAME, PUBLISH_TOKEN)
|
|
16
|
+
# All other configuration should come from config.json
|
|
17
17
|
case_sensitive=False,
|
|
18
18
|
extra="ignore", # Ignore extra environment variables (e.g., from Django)
|
|
19
19
|
)
|
|
20
20
|
|
|
21
21
|
# Core API Configuration
|
|
22
|
-
API_ENV: str = Field("development"
|
|
22
|
+
API_ENV: str = Field(default="development")
|
|
23
23
|
|
|
24
24
|
def _compute_default_api_url() -> str:
|
|
25
25
|
"""
|
|
@@ -44,17 +44,17 @@ class CoreSettings(BaseSettings):
|
|
|
44
44
|
base = "https://wayfinder.ai/api/v1"
|
|
45
45
|
return base
|
|
46
46
|
|
|
47
|
-
WAYFINDER_API_URL: str = Field(_compute_default_api_url
|
|
47
|
+
WAYFINDER_API_URL: str = Field(default_factory=_compute_default_api_url)
|
|
48
48
|
|
|
49
49
|
# Network Configuration
|
|
50
|
-
NETWORK: str = Field("testnet"
|
|
50
|
+
NETWORK: str = Field(default="testnet") # mainnet, testnet, devnet
|
|
51
51
|
|
|
52
52
|
# Logging
|
|
53
|
-
LOG_LEVEL: str = Field("INFO"
|
|
54
|
-
LOG_FILE: str = Field("logs/strategy.log"
|
|
53
|
+
LOG_LEVEL: str = Field(default="INFO")
|
|
54
|
+
LOG_FILE: str = Field(default="logs/strategy.log")
|
|
55
55
|
|
|
56
56
|
# Safety
|
|
57
|
-
DRY_RUN: bool = Field(False
|
|
57
|
+
DRY_RUN: bool = Field(default=False)
|
|
58
58
|
|
|
59
59
|
|
|
60
60
|
# Core settings instance
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import os
|
|
4
3
|
import traceback
|
|
5
4
|
from abc import ABC, abstractmethod
|
|
6
5
|
from collections.abc import Awaitable, Callable
|
|
@@ -57,16 +56,13 @@ class Strategy(ABC):
|
|
|
57
56
|
*,
|
|
58
57
|
main_wallet: WalletConfig | dict[str, Any] | None = None,
|
|
59
58
|
strategy_wallet: WalletConfig | dict[str, Any] | None = None,
|
|
60
|
-
simulation: bool = False,
|
|
61
59
|
web3_service: Web3Service | None = None,
|
|
62
60
|
api_key: str | None = None,
|
|
63
61
|
):
|
|
64
62
|
self.adapters = {}
|
|
65
63
|
self.ledger_adapter = None
|
|
66
64
|
self.logger = logger.bind(strategy=self.__class__.__name__)
|
|
67
|
-
|
|
68
|
-
os.environ["WAYFINDER_API_KEY"] = api_key
|
|
69
|
-
|
|
65
|
+
# Note: api_key is passed to ClientManager, not set in environment
|
|
70
66
|
self.config = config
|
|
71
67
|
|
|
72
68
|
async def setup(self) -> None:
|
|
@@ -59,7 +59,7 @@ def resolve_rpc_url(
|
|
|
59
59
|
explicit_rpc_url: str | None = None,
|
|
60
60
|
) -> str:
|
|
61
61
|
"""
|
|
62
|
-
Resolve RPC URL from config
|
|
62
|
+
Resolve RPC URL from config.
|
|
63
63
|
|
|
64
64
|
Args:
|
|
65
65
|
chain_id: Chain ID to look up RPC URL for
|
|
@@ -83,10 +83,7 @@ def resolve_rpc_url(
|
|
|
83
83
|
by_str = mapping.get(str(chain_id))
|
|
84
84
|
if by_str:
|
|
85
85
|
return str(by_str)
|
|
86
|
-
|
|
87
|
-
if env_rpc:
|
|
88
|
-
return env_rpc
|
|
89
|
-
raise ValueError("RPC URL not provided. Set strategy.rpc_urls or env RPC_URL.")
|
|
86
|
+
raise ValueError("RPC URL not provided. Set strategy.rpc_urls in config.json.")
|
|
90
87
|
|
|
91
88
|
|
|
92
89
|
async def get_next_nonce(
|
|
@@ -119,7 +116,7 @@ def resolve_private_key_for_from_address(
|
|
|
119
116
|
from_address: str, config: dict[str, Any]
|
|
120
117
|
) -> str | None:
|
|
121
118
|
"""
|
|
122
|
-
Resolve private key for the given address from config
|
|
119
|
+
Resolve private key for the given address from config.
|
|
123
120
|
|
|
124
121
|
Args:
|
|
125
122
|
from_address: Address to resolve private key for
|
|
@@ -159,14 +156,12 @@ def resolve_private_key_for_from_address(
|
|
|
159
156
|
logger.debug("Error resolving addresses from wallet config: %s", e)
|
|
160
157
|
|
|
161
158
|
if main_addr and from_addr_norm == (main_addr or "").lower():
|
|
162
|
-
return main_pk
|
|
159
|
+
return main_pk
|
|
163
160
|
if strategy_addr and from_addr_norm == (strategy_addr or "").lower():
|
|
164
|
-
return
|
|
165
|
-
strategy_pk or os.getenv("PRIVATE_KEY_STRATEGY") or os.getenv("PRIVATE_KEY")
|
|
166
|
-
)
|
|
161
|
+
return strategy_pk
|
|
167
162
|
|
|
168
|
-
#
|
|
169
|
-
return
|
|
163
|
+
# No fallback - private keys must be in config or wallets.json
|
|
164
|
+
return None
|
|
170
165
|
|
|
171
166
|
|
|
172
167
|
async def _get_abi(chain_id: int, address: str) -> str | None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Wallet Abstraction Layer
|
|
2
2
|
|
|
3
|
-
Wayfinder strategies interact with blockchains through a single abstraction: the `EvmTxn` interface defined in `wayfinder_paths/core/services/base.py`. The default implementation (`LocalEvmTxn`) signs transactions with private keys pulled from config
|
|
3
|
+
Wayfinder strategies interact with blockchains through a single abstraction: the `EvmTxn` interface defined in `wayfinder_paths/core/services/base.py`. The default implementation (`LocalEvmTxn`) signs transactions with private keys pulled from config.json or wallets.json, while `WalletManager` resolves which provider to use at runtime.
|
|
4
4
|
|
|
5
5
|
## Pieces
|
|
6
6
|
|
|
@@ -46,9 +46,6 @@ class PrivyWallet(EvmTxn):
|
|
|
46
46
|
async def read_erc20_allowance(self, chain_id: int, token_address: str, owner_address: str, spender_address: str) -> tuple[bool, Any]:
|
|
47
47
|
...
|
|
48
48
|
|
|
49
|
-
async def approve_token(...):
|
|
50
|
-
...
|
|
51
|
-
|
|
52
49
|
async def broadcast_transaction(...):
|
|
53
50
|
...
|
|
54
51
|
|
|
@@ -76,8 +73,8 @@ web3_service = DefaultWeb3Service(config, wallet_provider=custom_wallet)
|
|
|
76
73
|
{
|
|
77
74
|
"strategy": {
|
|
78
75
|
"wallet_type": "local",
|
|
79
|
-
"main_wallet": {"address": "0x...", "wallet_type": "local"},
|
|
80
|
-
"strategy_wallet": {"address": "0x..."}
|
|
76
|
+
"main_wallet": { "address": "0x...", "wallet_type": "local" },
|
|
77
|
+
"strategy_wallet": { "address": "0x..." }
|
|
81
78
|
}
|
|
82
79
|
}
|
|
83
80
|
```
|
wayfinder_paths/run_strategy.py
CHANGED
|
@@ -12,7 +12,7 @@ from pathlib import Path
|
|
|
12
12
|
|
|
13
13
|
from loguru import logger
|
|
14
14
|
|
|
15
|
-
from wayfinder_paths.core.config import StrategyJobConfig
|
|
15
|
+
from wayfinder_paths.core.config import StrategyJobConfig
|
|
16
16
|
from wayfinder_paths.core.engine.manifest import load_manifest, validate_manifest
|
|
17
17
|
from wayfinder_paths.core.engine.StrategyJob import StrategyJob
|
|
18
18
|
|
|
@@ -21,7 +21,6 @@ def load_strategy(
|
|
|
21
21
|
strategy_name: str,
|
|
22
22
|
*,
|
|
23
23
|
strategy_config: dict | None = None,
|
|
24
|
-
simulation: bool = False,
|
|
25
24
|
api_key: str | None = None,
|
|
26
25
|
):
|
|
27
26
|
"""
|
|
@@ -30,7 +29,6 @@ def load_strategy(
|
|
|
30
29
|
Args:
|
|
31
30
|
strategy_name: Name of the strategy to load (directory name in strategies/)
|
|
32
31
|
strategy_config: Configuration dict for the strategy
|
|
33
|
-
simulation: Enable simulation mode for testing
|
|
34
32
|
api_key: Optional API key for service account authentication
|
|
35
33
|
|
|
36
34
|
Returns:
|
|
@@ -59,36 +57,45 @@ def load_strategy(
|
|
|
59
57
|
module = __import__(module_path, fromlist=[class_name])
|
|
60
58
|
strategy_class = getattr(module, class_name)
|
|
61
59
|
|
|
62
|
-
return strategy_class(
|
|
63
|
-
config=strategy_config, simulation=simulation, api_key=api_key
|
|
64
|
-
)
|
|
60
|
+
return strategy_class(config=strategy_config, api_key=api_key)
|
|
65
61
|
|
|
66
62
|
|
|
67
63
|
def load_config(
|
|
68
64
|
config_path: str | None = None, strategy_name: str | None = None
|
|
69
65
|
) -> StrategyJobConfig:
|
|
70
66
|
"""
|
|
71
|
-
Load configuration from file
|
|
67
|
+
Load configuration from config.json file
|
|
72
68
|
|
|
73
69
|
Args:
|
|
74
|
-
config_path:
|
|
70
|
+
config_path: Path to config file (defaults to "config.json")
|
|
75
71
|
strategy_name: Optional strategy name for per-strategy wallet lookup
|
|
76
72
|
|
|
77
73
|
Returns:
|
|
78
74
|
StrategyJobConfig instance
|
|
75
|
+
|
|
76
|
+
Raises:
|
|
77
|
+
FileNotFoundError: If config file does not exist
|
|
79
78
|
"""
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
79
|
+
# Default to config.json if not provided
|
|
80
|
+
if not config_path:
|
|
81
|
+
config_path = "config.json"
|
|
82
|
+
|
|
83
|
+
config_file = Path(config_path)
|
|
84
|
+
if not config_file.exists():
|
|
85
|
+
raise FileNotFoundError(
|
|
86
|
+
f"Config file not found: {config_path}. "
|
|
87
|
+
"Please create config.json (see wayfinder_paths/config.example.json for template)"
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
logger.info(f"Loading config from {config_path}")
|
|
91
|
+
with open(config_file) as f:
|
|
92
|
+
config_data = json.load(f)
|
|
93
|
+
|
|
94
|
+
config = StrategyJobConfig.from_dict(config_data, strategy_name=strategy_name)
|
|
95
|
+
if strategy_name:
|
|
96
|
+
config.strategy_config["_strategy_name"] = strategy_name
|
|
97
|
+
config.__post_init__()
|
|
98
|
+
return config
|
|
92
99
|
|
|
93
100
|
|
|
94
101
|
async def run_strategy(
|
|
@@ -96,7 +103,6 @@ async def run_strategy(
|
|
|
96
103
|
config_path: str | None = None,
|
|
97
104
|
action: str = "run",
|
|
98
105
|
manifest_path: str | None = None,
|
|
99
|
-
simulation: bool = False,
|
|
100
106
|
**kwargs,
|
|
101
107
|
):
|
|
102
108
|
"""
|
|
@@ -159,9 +165,7 @@ async def run_strategy(
|
|
|
159
165
|
module_path, class_name = manifest.entrypoint.rsplit(".", 1)
|
|
160
166
|
module = __import__(module_path, fromlist=[class_name])
|
|
161
167
|
strategy_class = getattr(module, class_name)
|
|
162
|
-
strategy = strategy_class(
|
|
163
|
-
config=config.strategy_config, simulation=simulation
|
|
164
|
-
)
|
|
168
|
+
strategy = strategy_class(config=config.strategy_config)
|
|
165
169
|
logger.info(
|
|
166
170
|
f"Loaded strategy from manifest: {strategy_name_for_wallet or 'unnamed'}"
|
|
167
171
|
)
|
|
@@ -169,7 +173,6 @@ async def run_strategy(
|
|
|
169
173
|
strategy = load_strategy(
|
|
170
174
|
strategy_name,
|
|
171
175
|
strategy_config=config.strategy_config,
|
|
172
|
-
simulation=simulation,
|
|
173
176
|
)
|
|
174
177
|
logger.info(f"Loaded strategy: {strategy.name}")
|
|
175
178
|
|
|
@@ -326,7 +329,7 @@ def main():
|
|
|
326
329
|
help="Path to strategy manifest YAML (alternative to strategy name)",
|
|
327
330
|
)
|
|
328
331
|
parser.add_argument(
|
|
329
|
-
"--config", help="Path to config file (defaults to
|
|
332
|
+
"--config", help="Path to config file (defaults to config.json)"
|
|
330
333
|
)
|
|
331
334
|
parser.add_argument(
|
|
332
335
|
"--action",
|
|
@@ -372,11 +375,6 @@ def main():
|
|
|
372
375
|
"--duration", type=int, help="Duration in seconds for script action"
|
|
373
376
|
)
|
|
374
377
|
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
|
|
375
|
-
parser.add_argument(
|
|
376
|
-
"--simulation",
|
|
377
|
-
action="store_true",
|
|
378
|
-
help="Run in simulation mode (no real transactions)",
|
|
379
|
-
)
|
|
380
378
|
parser.add_argument(
|
|
381
379
|
"--wallet-id",
|
|
382
380
|
help="Wallet ID for policy rendering (replaces FORMAT_WALLET_ID in policies)",
|
|
@@ -401,7 +399,6 @@ def main():
|
|
|
401
399
|
gas_token_amount=args.gas_token_amount,
|
|
402
400
|
interval=args.interval,
|
|
403
401
|
duration=args.duration,
|
|
404
|
-
simulation=args.simulation,
|
|
405
402
|
wallet_id=getattr(args, "wallet_id", None),
|
|
406
403
|
)
|
|
407
404
|
)
|
|
@@ -15,27 +15,6 @@ def to_keystore_json(private_key_hex: str, password: str):
|
|
|
15
15
|
return Account.encrypt(private_key_hex, password)
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
def write_env(rows: list[dict[str, str]], out_dir: Path) -> None:
|
|
19
|
-
with open(out_dir / ".env.example", "w") as f:
|
|
20
|
-
if rows:
|
|
21
|
-
label_to_wallet = {r.get("label"): r for r in rows if r.get("label")}
|
|
22
|
-
main_w = (
|
|
23
|
-
label_to_wallet.get("main") or label_to_wallet.get("default") or rows[0]
|
|
24
|
-
)
|
|
25
|
-
strategy_w = label_to_wallet.get("strategy")
|
|
26
|
-
|
|
27
|
-
f.write("RPC_URL=https://rpc.ankr.com/eth\n")
|
|
28
|
-
# Back-compat defaults
|
|
29
|
-
f.write(f"PRIVATE_KEY={main_w['private_key_hex']}\n")
|
|
30
|
-
f.write(f"FROM_ADDRESS={main_w['address']}\n")
|
|
31
|
-
# Explicit main/strategy variables
|
|
32
|
-
f.write(f"MAIN_WALLET_ADDRESS={main_w['address']}\n")
|
|
33
|
-
if strategy_w:
|
|
34
|
-
f.write(f"STRATEGY_WALLET_ADDRESS={strategy_w['address']}\n")
|
|
35
|
-
# Optional: expose strategy private key for local dev only
|
|
36
|
-
f.write(f"PRIVATE_KEY_STRATEGY={strategy_w['private_key_hex']}\n")
|
|
37
|
-
|
|
38
|
-
|
|
39
18
|
def main():
|
|
40
19
|
parser = argparse.ArgumentParser(description="Generate local dev wallets")
|
|
41
20
|
parser.add_argument(
|
|
@@ -48,7 +27,7 @@ def main():
|
|
|
48
27
|
"--out-dir",
|
|
49
28
|
type=Path,
|
|
50
29
|
default=Path("."),
|
|
51
|
-
help="Output directory for wallets.json (and
|
|
30
|
+
help="Output directory for wallets.json (and keystore files)",
|
|
52
31
|
)
|
|
53
32
|
parser.add_argument(
|
|
54
33
|
"--keystore-password",
|
|
@@ -161,9 +140,6 @@ def main():
|
|
|
161
140
|
ks_path.write_text(json.dumps(ks))
|
|
162
141
|
index += 1
|
|
163
142
|
|
|
164
|
-
# Convenience outputs
|
|
165
|
-
write_env(rows, args.out_dir)
|
|
166
|
-
|
|
167
143
|
|
|
168
144
|
if __name__ == "__main__":
|
|
169
145
|
main()
|
|
@@ -55,7 +55,6 @@ async def _run(args: argparse.Namespace) -> int:
|
|
|
55
55
|
"main_wallet": main_wallet,
|
|
56
56
|
"strategy_wallet": strategy_wallet,
|
|
57
57
|
},
|
|
58
|
-
simulation=args.simulation,
|
|
59
58
|
)
|
|
60
59
|
|
|
61
60
|
await s.setup()
|
|
@@ -101,7 +100,6 @@ def main() -> int:
|
|
|
101
100
|
)
|
|
102
101
|
p.add_argument("--main-wallet-label", default="main")
|
|
103
102
|
p.add_argument("--strategy-wallet-label", default="basis_trading_strategy")
|
|
104
|
-
p.add_argument("--simulation", action="store_true")
|
|
105
103
|
|
|
106
104
|
sub = p.add_subparsers(dest="command", required=True)
|
|
107
105
|
|
|
@@ -8,7 +8,6 @@ from __future__ import annotations
|
|
|
8
8
|
|
|
9
9
|
import asyncio
|
|
10
10
|
import json
|
|
11
|
-
import os
|
|
12
11
|
import time
|
|
13
12
|
from datetime import UTC, datetime
|
|
14
13
|
from decimal import ROUND_DOWN, Decimal
|
|
@@ -1007,5 +1006,4 @@ class BasisSnapshotMixin:
|
|
|
1007
1006
|
)
|
|
1008
1007
|
if isinstance(val, str) and val.strip():
|
|
1009
1008
|
return val.strip()
|
|
1010
|
-
|
|
1011
|
-
return env.strip() if isinstance(env, str) and env.strip() else None
|
|
1009
|
+
return None
|