wayfinder-paths 0.1.10__py3-none-any.whl → 0.1.11__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/core/services/base.py +0 -55
- wayfinder_paths/core/services/local_evm_txn.py +15 -130
- wayfinder_paths/core/utils/evm_helpers.py +0 -27
- {wayfinder_paths-0.1.10.dist-info → wayfinder_paths-0.1.11.dist-info}/METADATA +7 -7
- {wayfinder_paths-0.1.10.dist-info → wayfinder_paths-0.1.11.dist-info}/RECORD +7 -9
- wayfinder_paths/CONFIG_GUIDE.md +0 -390
- wayfinder_paths/config.example.json +0 -22
- {wayfinder_paths-0.1.10.dist-info → wayfinder_paths-0.1.11.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.10.dist-info → wayfinder_paths-0.1.11.dist-info}/WHEEL +0 -0
|
@@ -50,61 +50,6 @@ class EvmTxn(ABC):
|
|
|
50
50
|
transaction confirmations.
|
|
51
51
|
"""
|
|
52
52
|
|
|
53
|
-
@abstractmethod
|
|
54
|
-
async def get_balance(
|
|
55
|
-
self,
|
|
56
|
-
address: str,
|
|
57
|
-
token_address: str | None,
|
|
58
|
-
chain_id: int,
|
|
59
|
-
block_identifier: int | str | None = None,
|
|
60
|
-
) -> tuple[bool, Any]:
|
|
61
|
-
"""
|
|
62
|
-
Get balance for an address at a specific block.
|
|
63
|
-
|
|
64
|
-
Args:
|
|
65
|
-
address: Address to query balance for
|
|
66
|
-
token_address: ERC20 token address, or None for native token
|
|
67
|
-
chain_id: Chain ID
|
|
68
|
-
block_identifier: Block to query at. Can be:
|
|
69
|
-
- int: specific block number (for pinning to tx block)
|
|
70
|
-
- "safe": OP Stack safe block (data posted to L1)
|
|
71
|
-
- "finalized": fully finalized block
|
|
72
|
-
- None/"latest": current head (default, but avoid after txs)
|
|
73
|
-
|
|
74
|
-
Returns:
|
|
75
|
-
Tuple of (success, balance_integer_or_error_message)
|
|
76
|
-
"""
|
|
77
|
-
pass
|
|
78
|
-
|
|
79
|
-
@abstractmethod
|
|
80
|
-
async def approve_token(
|
|
81
|
-
self,
|
|
82
|
-
token_address: str,
|
|
83
|
-
spender: str,
|
|
84
|
-
amount: int,
|
|
85
|
-
from_address: str,
|
|
86
|
-
chain_id: int,
|
|
87
|
-
wait_for_receipt: bool = True,
|
|
88
|
-
timeout: int = DEFAULT_TRANSACTION_TIMEOUT,
|
|
89
|
-
) -> tuple[bool, Any]:
|
|
90
|
-
"""
|
|
91
|
-
Approve a spender to spend tokens on behalf of from_address.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
token_address: ERC20 token contract address
|
|
95
|
-
spender: Address being approved to spend tokens
|
|
96
|
-
amount: Amount to approve (in token units, not human-readable)
|
|
97
|
-
from_address: Address approving the tokens
|
|
98
|
-
chain_id: Chain ID
|
|
99
|
-
wait_for_receipt: Whether to wait for the transaction receipt
|
|
100
|
-
timeout: Receipt timeout in seconds
|
|
101
|
-
|
|
102
|
-
Returns:
|
|
103
|
-
Tuple of (success, transaction_result_dict_or_error_message)
|
|
104
|
-
Transaction result should include 'tx_hash' and optionally 'receipt'
|
|
105
|
-
"""
|
|
106
|
-
pass
|
|
107
|
-
|
|
108
53
|
@abstractmethod
|
|
109
54
|
async def broadcast_transaction(
|
|
110
55
|
self,
|
|
@@ -4,16 +4,9 @@ from typing import Any
|
|
|
4
4
|
from eth_account import Account
|
|
5
5
|
from eth_utils import to_checksum_address
|
|
6
6
|
from loguru import logger
|
|
7
|
-
from web3 import AsyncHTTPProvider, AsyncWeb3
|
|
7
|
+
from web3 import AsyncHTTPProvider, AsyncWeb3
|
|
8
8
|
|
|
9
|
-
from wayfinder_paths.core.constants import (
|
|
10
|
-
ZERO_ADDRESS,
|
|
11
|
-
)
|
|
12
9
|
from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
|
|
13
|
-
from wayfinder_paths.core.constants.erc20_abi import (
|
|
14
|
-
ERC20_APPROVAL_ABI,
|
|
15
|
-
ERC20_MINIMAL_ABI,
|
|
16
|
-
)
|
|
17
10
|
from wayfinder_paths.core.services.base import EvmTxn
|
|
18
11
|
from wayfinder_paths.core.utils.evm_helpers import (
|
|
19
12
|
resolve_private_key_for_from_address,
|
|
@@ -60,105 +53,12 @@ class LocalEvmTxn(EvmTxn):
|
|
|
60
53
|
def __init__(self, config: dict[str, Any] | None = None):
|
|
61
54
|
self.config = config or {}
|
|
62
55
|
self.logger = logger.bind(provider="LocalWalletProvider")
|
|
63
|
-
# Cache web3 instances per chain to avoid load balancer inconsistency
|
|
64
|
-
self._web3_cache: dict[int, AsyncWeb3] = {}
|
|
65
56
|
|
|
66
57
|
def get_web3(self, chain_id: int) -> AsyncWeb3:
|
|
67
|
-
# Reuse cached instance to ensure consistent RPC node for reads after writes
|
|
68
|
-
if chain_id in self._web3_cache:
|
|
69
|
-
return self._web3_cache[chain_id]
|
|
70
58
|
rpc_url = self._resolve_rpc_url(chain_id)
|
|
71
59
|
w3 = AsyncWeb3(AsyncHTTPProvider(rpc_url))
|
|
72
|
-
self._web3_cache[chain_id] = w3
|
|
73
60
|
return w3
|
|
74
61
|
|
|
75
|
-
async def get_balance(
|
|
76
|
-
self,
|
|
77
|
-
address: str,
|
|
78
|
-
token_address: str | None,
|
|
79
|
-
chain_id: int,
|
|
80
|
-
block_identifier: int | str | None = None,
|
|
81
|
-
) -> tuple[bool, Any]:
|
|
82
|
-
"""
|
|
83
|
-
Get balance for an address at a specific block.
|
|
84
|
-
|
|
85
|
-
Args:
|
|
86
|
-
address: Address to query balance for
|
|
87
|
-
token_address: ERC20 token address, or None for native token
|
|
88
|
-
chain_id: Chain ID
|
|
89
|
-
block_identifier: Block to query at. Can be:
|
|
90
|
-
- int: specific block number (for pinning to tx block)
|
|
91
|
-
- "safe": OP Stack safe block (data posted to L1)
|
|
92
|
-
- "finalized": fully finalized block
|
|
93
|
-
- None/"latest": current head (default, but avoid after txs)
|
|
94
|
-
|
|
95
|
-
Returns:
|
|
96
|
-
Tuple of (success, balance_integer_or_error_message)
|
|
97
|
-
"""
|
|
98
|
-
w3 = self.get_web3(chain_id)
|
|
99
|
-
try:
|
|
100
|
-
checksum_addr = to_checksum_address(address)
|
|
101
|
-
block_id = block_identifier if block_identifier is not None else "latest"
|
|
102
|
-
|
|
103
|
-
if not token_address or token_address.lower() == ZERO_ADDRESS:
|
|
104
|
-
balance = await w3.eth.get_balance(
|
|
105
|
-
checksum_addr, block_identifier=block_id
|
|
106
|
-
)
|
|
107
|
-
return (True, int(balance))
|
|
108
|
-
|
|
109
|
-
token_checksum = to_checksum_address(token_address)
|
|
110
|
-
contract = w3.eth.contract(address=token_checksum, abi=ERC20_MINIMAL_ABI)
|
|
111
|
-
balance = await contract.functions.balanceOf(checksum_addr).call(
|
|
112
|
-
block_identifier=block_id
|
|
113
|
-
)
|
|
114
|
-
return (True, int(balance))
|
|
115
|
-
|
|
116
|
-
except Exception as exc: # noqa: BLE001
|
|
117
|
-
self.logger.error(f"Failed to get balance: {exc}")
|
|
118
|
-
return (False, f"Balance query failed: {exc}")
|
|
119
|
-
finally:
|
|
120
|
-
await self._close_web3(w3)
|
|
121
|
-
|
|
122
|
-
async def approve_token(
|
|
123
|
-
self,
|
|
124
|
-
token_address: str,
|
|
125
|
-
spender: str,
|
|
126
|
-
amount: int,
|
|
127
|
-
from_address: str,
|
|
128
|
-
chain_id: int,
|
|
129
|
-
wait_for_receipt: bool = True,
|
|
130
|
-
timeout: int = DEFAULT_TRANSACTION_TIMEOUT,
|
|
131
|
-
) -> tuple[bool, Any]:
|
|
132
|
-
try:
|
|
133
|
-
token_checksum = to_checksum_address(token_address)
|
|
134
|
-
spender_checksum = to_checksum_address(spender)
|
|
135
|
-
from_checksum = to_checksum_address(from_address)
|
|
136
|
-
amount_int = int(amount)
|
|
137
|
-
|
|
138
|
-
w3_sync = Web3()
|
|
139
|
-
contract = w3_sync.eth.contract(
|
|
140
|
-
address=token_checksum, abi=ERC20_APPROVAL_ABI
|
|
141
|
-
)
|
|
142
|
-
transaction_data = contract.encode_abi(
|
|
143
|
-
"approve", args=[spender_checksum, amount_int]
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
approve_txn = {
|
|
147
|
-
"from": from_checksum,
|
|
148
|
-
"chainId": int(chain_id),
|
|
149
|
-
"to": token_checksum,
|
|
150
|
-
"data": transaction_data,
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return await self.broadcast_transaction(
|
|
154
|
-
approve_txn,
|
|
155
|
-
wait_for_receipt=wait_for_receipt,
|
|
156
|
-
timeout=timeout,
|
|
157
|
-
)
|
|
158
|
-
except Exception as exc: # noqa: BLE001
|
|
159
|
-
self.logger.error(f"ERC20 approval failed: {exc}")
|
|
160
|
-
return (False, f"ERC20 approval failed: {exc}")
|
|
161
|
-
|
|
162
62
|
def _validate_transaction(self, transaction: dict[str, Any]) -> dict[str, Any]:
|
|
163
63
|
tx = dict(transaction)
|
|
164
64
|
|
|
@@ -264,29 +164,29 @@ class LocalEvmTxn(EvmTxn):
|
|
|
264
164
|
*,
|
|
265
165
|
wait_for_receipt: bool = True,
|
|
266
166
|
timeout: int = DEFAULT_TRANSACTION_TIMEOUT,
|
|
267
|
-
confirmations: int =
|
|
167
|
+
confirmations: int = 1,
|
|
268
168
|
) -> tuple[bool, Any]:
|
|
269
169
|
try:
|
|
270
170
|
chain_id = transaction["chainId"]
|
|
271
171
|
from_address = transaction["from"]
|
|
272
172
|
|
|
273
|
-
|
|
173
|
+
web3 = self.get_web3(chain_id)
|
|
274
174
|
try:
|
|
275
175
|
transaction = self._validate_transaction(transaction)
|
|
276
|
-
transaction = await self._nonce_transaction(transaction,
|
|
277
|
-
transaction = await self._gas_limit_transaction(transaction,
|
|
176
|
+
transaction = await self._nonce_transaction(transaction, web3)
|
|
177
|
+
transaction = await self._gas_limit_transaction(transaction, web3)
|
|
278
178
|
transaction = await self._gas_price_transaction(
|
|
279
|
-
transaction, chain_id,
|
|
179
|
+
transaction, chain_id, web3
|
|
280
180
|
)
|
|
281
181
|
|
|
282
182
|
signed_tx = self._sign_transaction(transaction, from_address)
|
|
283
183
|
|
|
284
|
-
tx_hash = await
|
|
184
|
+
tx_hash = await web3.eth.send_raw_transaction(signed_tx)
|
|
285
185
|
tx_hash_hex = tx_hash.hex()
|
|
286
186
|
|
|
287
187
|
result: dict[str, Any] = {"tx_hash": tx_hash_hex}
|
|
288
188
|
if wait_for_receipt:
|
|
289
|
-
receipt = await
|
|
189
|
+
receipt = await web3.eth.wait_for_transaction_receipt(
|
|
290
190
|
tx_hash, timeout=timeout
|
|
291
191
|
)
|
|
292
192
|
result["receipt"] = self._format_receipt(receipt)
|
|
@@ -299,32 +199,19 @@ class LocalEvmTxn(EvmTxn):
|
|
|
299
199
|
False,
|
|
300
200
|
f"Transaction reverted (status={receipt_status}): {tx_hash_hex}",
|
|
301
201
|
)
|
|
302
|
-
# Check if transaction reverted (status=0)
|
|
303
|
-
# Handle both dict-like and attribute access for web3.py receipts
|
|
304
|
-
receipt_status = (
|
|
305
|
-
receipt.get("status")
|
|
306
|
-
if hasattr(receipt, "get")
|
|
307
|
-
else getattr(receipt, "status", None)
|
|
308
|
-
)
|
|
309
|
-
self.logger.debug(
|
|
310
|
-
f"Transaction {tx_hash_hex} receipt status: {receipt_status} (type: {type(receipt_status).__name__})"
|
|
311
|
-
)
|
|
312
|
-
if receipt_status == 0:
|
|
313
|
-
self.logger.error(f"Transaction reverted: {tx_hash_hex}")
|
|
314
|
-
return (False, f"Transaction reverted: {tx_hash_hex}")
|
|
315
202
|
|
|
316
203
|
# Wait for additional confirmations if requested
|
|
317
204
|
if confirmations > 0:
|
|
318
205
|
tx_block = result["receipt"].get("blockNumber")
|
|
319
206
|
if tx_block:
|
|
320
207
|
await self._wait_for_confirmations(
|
|
321
|
-
|
|
208
|
+
web3, tx_block, confirmations
|
|
322
209
|
)
|
|
323
210
|
|
|
324
211
|
return (True, result)
|
|
325
212
|
|
|
326
213
|
finally:
|
|
327
|
-
await self._close_web3(
|
|
214
|
+
await self._close_web3(web3)
|
|
328
215
|
|
|
329
216
|
except Exception as exc: # noqa: BLE001
|
|
330
217
|
self.logger.error(f"Transaction broadcast failed: {exc}")
|
|
@@ -362,14 +249,12 @@ class LocalEvmTxn(EvmTxn):
|
|
|
362
249
|
def _resolve_rpc_url(self, chain_id: int) -> str:
|
|
363
250
|
return resolve_rpc_url(chain_id, self.config or {}, None)
|
|
364
251
|
|
|
365
|
-
async def _close_web3(self,
|
|
366
|
-
# Don't close cached connections - we want to reuse them for consistency
|
|
367
|
-
if w3 in self._web3_cache.values():
|
|
368
|
-
return
|
|
252
|
+
async def _close_web3(self, web3: AsyncWeb3) -> None:
|
|
369
253
|
try:
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
254
|
+
if hasattr(web3.provider, "disconnect"):
|
|
255
|
+
await web3.provider.disconnect()
|
|
256
|
+
except Exception as e: # noqa: BLE001
|
|
257
|
+
self.logger.debug(f"Error disconnecting provider: {e}")
|
|
373
258
|
|
|
374
259
|
async def _wait_for_confirmations(
|
|
375
260
|
self, w3: AsyncWeb3, tx_block: int, confirmations: int
|
|
@@ -11,7 +11,6 @@ from pathlib import Path
|
|
|
11
11
|
from typing import Any
|
|
12
12
|
|
|
13
13
|
from loguru import logger
|
|
14
|
-
from web3 import AsyncHTTPProvider, AsyncWeb3
|
|
15
14
|
|
|
16
15
|
from wayfinder_paths.core.constants.base import CHAIN_CODE_TO_ID
|
|
17
16
|
|
|
@@ -86,32 +85,6 @@ def resolve_rpc_url(
|
|
|
86
85
|
raise ValueError("RPC URL not provided. Set strategy.rpc_urls in config.json.")
|
|
87
86
|
|
|
88
87
|
|
|
89
|
-
async def get_next_nonce(
|
|
90
|
-
from_address: str, rpc_url: str, use_latest: bool = False
|
|
91
|
-
) -> int:
|
|
92
|
-
"""
|
|
93
|
-
Get the next nonce for the given address.
|
|
94
|
-
|
|
95
|
-
Args:
|
|
96
|
-
from_address: Address to get nonce for
|
|
97
|
-
rpc_url: RPC URL to connect to
|
|
98
|
-
use_latest: If True, use 'latest' block instead of 'pending'
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
Next nonce as integer
|
|
102
|
-
"""
|
|
103
|
-
w3 = AsyncWeb3(AsyncHTTPProvider(rpc_url))
|
|
104
|
-
try:
|
|
105
|
-
if use_latest:
|
|
106
|
-
return await w3.eth.get_transaction_count(from_address, "latest")
|
|
107
|
-
return await w3.eth.get_transaction_count(from_address)
|
|
108
|
-
finally:
|
|
109
|
-
try:
|
|
110
|
-
await w3.provider.session.close()
|
|
111
|
-
except Exception:
|
|
112
|
-
pass
|
|
113
|
-
|
|
114
|
-
|
|
115
88
|
def resolve_private_key_for_from_address(
|
|
116
89
|
from_address: str, config: dict[str, Any]
|
|
117
90
|
) -> str | None:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: wayfinder-paths
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.11
|
|
4
4
|
Summary: Wayfinder Path: strategies and adapters
|
|
5
5
|
Author: Wayfinder
|
|
6
6
|
Author-email: dev@wayfinder.ai
|
|
@@ -48,7 +48,7 @@ just create-wallets
|
|
|
48
48
|
# Or manually: poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
|
|
49
49
|
|
|
50
50
|
# To test a specific strategy
|
|
51
|
-
just
|
|
51
|
+
just create-wallet stablecoin_yield_strategy
|
|
52
52
|
|
|
53
53
|
# Copy and configure
|
|
54
54
|
cp wayfinder_paths/config.example.json config.json
|
|
@@ -374,11 +374,11 @@ class MyStrategy(Strategy):
|
|
|
374
374
|
|
|
375
375
|
The following strategies are available and can be run using the CLI:
|
|
376
376
|
|
|
377
|
-
| Strategy
|
|
378
|
-
|
|
379
|
-
| `basis_trading_strategy`
|
|
380
|
-
| `hyperlend_stable_yield_strategy` | Stable yield on HyperLend
|
|
381
|
-
| `moonwell_wsteth_loop_strategy`
|
|
377
|
+
| Strategy | Description | Chain |
|
|
378
|
+
| --------------------------------- | --------------------------- | -------- |
|
|
379
|
+
| `basis_trading_strategy` | Delta-neutral basis trading | - |
|
|
380
|
+
| `hyperlend_stable_yield_strategy` | Stable yield on HyperLend | HyperEVM |
|
|
381
|
+
| `moonwell_wsteth_loop_strategy` | Leveraged wstETH yield loop | Base |
|
|
382
382
|
|
|
383
383
|
#### Running Strategies
|
|
384
384
|
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
wayfinder_paths/CONFIG_GUIDE.md,sha256=p8eT2FC_dp4bBdezwGmQYLiefLNze-I6lY__UXv07zQ,12975
|
|
2
1
|
wayfinder_paths/__init__.py,sha256=YgOg-PRPT3ROh0zg6hgQyQE-YFkFGw6TM77zDvB4_sE,427
|
|
3
2
|
wayfinder_paths/abis/generic/erc20.json,sha256=geyzVzdTNt3u1XHKxi4seszP_GIWIzPTl0FYgiftRnM,9336
|
|
4
3
|
wayfinder_paths/adapters/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -41,7 +40,6 @@ wayfinder_paths/adapters/token_adapter/__init__.py,sha256=nEmxrvffEygn3iKH3cZTNL
|
|
|
41
40
|
wayfinder_paths/adapters/token_adapter/adapter.py,sha256=JEb7A8wJYHxENFhJ6upAgnQAbPZeVfYi6OGs1hiHxnA,3432
|
|
42
41
|
wayfinder_paths/adapters/token_adapter/examples.json,sha256=RW-3xazj4wbTPl-AVrzduRH1NCXx8m7-06bRMOUJ-lc,3626
|
|
43
42
|
wayfinder_paths/adapters/token_adapter/test_adapter.py,sha256=oEhV5OooRh1oGnaTTMKtdU9oqvHBKR25KgL6-ZB6Mzo,4304
|
|
44
|
-
wayfinder_paths/config.example.json,sha256=gDvS7W-cbaNe2IV7Q72di_PYpCDKODojELaXdd77Gx4,605
|
|
45
43
|
wayfinder_paths/conftest.py,sha256=pqDNijXn9_zmbAdkt_2a18UQLjtsDkNTBJVTgC6H2nA,1136
|
|
46
44
|
wayfinder_paths/core/__init__.py,sha256=AJK8oS2dCVuJ2pmSxqXOCvuWacNaVEU3yALEqsD3rog,398
|
|
47
45
|
wayfinder_paths/core/adapters/BaseAdapter.py,sha256=bzc3ER7aKOsmk9cxyoJxGdI54eibbpcMC8nGYJUrsp0,2033
|
|
@@ -73,8 +71,8 @@ wayfinder_paths/core/constants/moonwell_abi.py,sha256=ALb-kKdfF9aUtEHR8OlqvA-3zJ
|
|
|
73
71
|
wayfinder_paths/core/engine/StrategyJob.py,sha256=DqwkPu5JHp00xkDmj7kyUqs9U-VP0k-OBlVipjEzk14,7257
|
|
74
72
|
wayfinder_paths/core/engine/__init__.py,sha256=WZ2KWnmOZnBocYrqdwq6EUHp6lmTyrKyXgHSHyQswnU,108
|
|
75
73
|
wayfinder_paths/core/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
76
|
-
wayfinder_paths/core/services/base.py,sha256=
|
|
77
|
-
wayfinder_paths/core/services/local_evm_txn.py,sha256=
|
|
74
|
+
wayfinder_paths/core/services/base.py,sha256=94Wvs7Ym7tK9J3k9lEOhSSIC7ptnMJ161dYtF4XTSZ8,4096
|
|
75
|
+
wayfinder_paths/core/services/local_evm_txn.py,sha256=0SJTTIUNx91bm0XzoqPUUfveNMPPYTUFTMHNFyoJ0ao,11968
|
|
78
76
|
wayfinder_paths/core/services/local_token_txn.py,sha256=YQjuHjdyGwVfAZt3FGMkSKoc34bRiJrWRiR5pXJmL9U,8556
|
|
79
77
|
wayfinder_paths/core/services/web3_service.py,sha256=7iR7bfqfUQCQcdfEWVGqy04PZBtZTuzCDpfLt1a-4OI,1485
|
|
80
78
|
wayfinder_paths/core/settings.py,sha256=aJdy2bRcJtufr4TZnu30R2iv---Ru4s6nxKo-j22uKQ,1962
|
|
@@ -83,7 +81,7 @@ wayfinder_paths/core/strategies/__init__.py,sha256=2NjvvDw6sIQGUFV4Qo1olXTxUOY3G
|
|
|
83
81
|
wayfinder_paths/core/strategies/base.py,sha256=-s0qeiGZl5CHTUL2PavGXM7ACkNlaa0c4jeZR_4DuBM,155
|
|
84
82
|
wayfinder_paths/core/strategies/descriptors.py,sha256=9eFf-gPNw1NO1o1eQqGlEgnz237IpAmrmZnIH3JD-ys,1642
|
|
85
83
|
wayfinder_paths/core/utils/__init__.py,sha256=TEylMYHnG37Z3mizSmw28bUm0vyNBFzf0Nc8dB_7l1A,73
|
|
86
|
-
wayfinder_paths/core/utils/evm_helpers.py,sha256=
|
|
84
|
+
wayfinder_paths/core/utils/evm_helpers.py,sha256=jJBOhQfS19xPmrbAnYRai7LG7ixnTHgcNrPKLDeE3tQ,5476
|
|
87
85
|
wayfinder_paths/core/utils/wallets.py,sha256=tGgVxDW2ZvkvJIb6yow1cirrqhQ67_X9IqxZocBEy2k,2438
|
|
88
86
|
wayfinder_paths/core/wallets/README.md,sha256=GdO1RFUG_jZdVH6qeobHlr_c69hEDgLLrgqYCvj_dGs,3701
|
|
89
87
|
wayfinder_paths/core/wallets/WalletManager.py,sha256=sptj0Dya9iM87BDzUktrYM_Mw33xyVJNrRUTVfBjHGw,1870
|
|
@@ -136,7 +134,7 @@ wayfinder_paths/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
|
|
|
136
134
|
wayfinder_paths/tests/test_smoke_manifest.py,sha256=kTcIa4qikcspVh2ohQIk0aHUdIvBvcQBfNbm3PNiVvg,1636
|
|
137
135
|
wayfinder_paths/tests/test_test_coverage.py,sha256=9NrZeVmP02D4W7Qc0XjciC05bhvdTCVibYjTGfa_GQk,7893
|
|
138
136
|
wayfinder_paths/tests/test_utils.py,sha256=pxHT0QKFlyJeJo8bFnKXzWcOdi6t8rbJ0JFCBaFCBRQ,2112
|
|
139
|
-
wayfinder_paths-0.1.
|
|
140
|
-
wayfinder_paths-0.1.
|
|
141
|
-
wayfinder_paths-0.1.
|
|
142
|
-
wayfinder_paths-0.1.
|
|
137
|
+
wayfinder_paths-0.1.11.dist-info/LICENSE,sha256=dYKnlkC_xosBAEQNUvB6cHMuhFgcUtN0oBR7E8_aR2Y,1066
|
|
138
|
+
wayfinder_paths-0.1.11.dist-info/METADATA,sha256=GIUfhzTEdw7KphRWf19P5AaCkT1RIKACpjaelJootCc,25763
|
|
139
|
+
wayfinder_paths-0.1.11.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
140
|
+
wayfinder_paths-0.1.11.dist-info/RECORD,,
|
wayfinder_paths/CONFIG_GUIDE.md
DELETED
|
@@ -1,390 +0,0 @@
|
|
|
1
|
-
# Configuration Guide
|
|
2
|
-
|
|
3
|
-
This guide explains how to configure your strategies for local testing.
|
|
4
|
-
|
|
5
|
-
## Quick Setup
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
# 1. Generate test wallets (required!)
|
|
9
|
-
# Creates a main wallet (or use 'just create-strategy' which auto-creates wallets)
|
|
10
|
-
just create-wallets
|
|
11
|
-
# Or manually: poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
|
|
12
|
-
|
|
13
|
-
# 2. Create your config file
|
|
14
|
-
cp wayfinder_paths/config.example.json config.json
|
|
15
|
-
|
|
16
|
-
# 3. Edit config.json with your Wayfinder credentials
|
|
17
|
-
# NEVER commit this file - it contains your credentials!
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## Configuration Structure
|
|
21
|
-
|
|
22
|
-
### config.json Structure
|
|
23
|
-
|
|
24
|
-
```json
|
|
25
|
-
{
|
|
26
|
-
"user": {
|
|
27
|
-
"username": "your_username", // OPTIONAL: For OAuth authentication
|
|
28
|
-
"password": "your_password", // OPTIONAL: For OAuth authentication
|
|
29
|
-
"refresh_token": null, // OPTIONAL: Alternative to username/password
|
|
30
|
-
"api_key": "sk_live_abc123..." // OPTIONAL: For service account authentication (loaded directly by clients, not stored in UserConfig)
|
|
31
|
-
},
|
|
32
|
-
"system": {
|
|
33
|
-
"api_base_url": "https://wayfinder.ai/api/v1",
|
|
34
|
-
"api_key": "sk_live_abc123...", // OPTIONAL: System-level API key (alternative to user.api_key, loaded directly by clients)
|
|
35
|
-
"wallets_path": "wallets.json" // Path to your generated wallets.json
|
|
36
|
-
},
|
|
37
|
-
"strategy": {
|
|
38
|
-
"rpc_urls": { // RPC endpoints for different chains
|
|
39
|
-
"1": "https://eth.llamarpc.com",
|
|
40
|
-
"42161": "https://arb1.arbitrum.io/rpc",
|
|
41
|
-
"8453": "https://mainnet.base.org",
|
|
42
|
-
"solana": "https://api.mainnet-beta.solana.com"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## User Configuration
|
|
49
|
-
|
|
50
|
-
**Authentication (choose one method):**
|
|
51
|
-
|
|
52
|
-
**Option 1: Service Account (API Key) - Recommended for Production**
|
|
53
|
-
- `user.api_key` - Your Wayfinder API key for service account authentication (loaded directly by clients from config.json)
|
|
54
|
-
- OR `system.api_key` - System-level API key (alternative location, loaded directly by clients)
|
|
55
|
-
- OR pass `api_key` parameter to strategy constructor
|
|
56
|
-
|
|
57
|
-
**Note:** API keys in `config.json` are loaded directly by `WayfinderClient` via `_load_config_credentials()`, not through the `UserConfig` or `SystemConfig` dataclasses. This is intentional to allow flexible credential loading.
|
|
58
|
-
|
|
59
|
-
**Option 2: Personal Access (OAuth) - For Development**
|
|
60
|
-
- `user.username` - Your Wayfinder backend username
|
|
61
|
-
- `user.password` - Your Wayfinder backend password
|
|
62
|
-
- OR `user.refresh_token` - Alternative to username/password
|
|
63
|
-
|
|
64
|
-
**Other Optional fields:**
|
|
65
|
-
- `user.main_wallet_address` - Override auto-loaded main wallet
|
|
66
|
-
- `user.strategy_wallet_address` - Override auto-loaded strategy wallet
|
|
67
|
-
|
|
68
|
-
**Security Note:** Never commit `config.json` to version control. Add it to `.gitignore`.
|
|
69
|
-
|
|
70
|
-
## System Configuration
|
|
71
|
-
|
|
72
|
-
These are managed automatically by Wayfinder:
|
|
73
|
-
- `api_base_url` - Wayfinder backend API endpoint
|
|
74
|
-
- `wallets_path` - Location of generated wallets.json
|
|
75
|
-
- `job_id` - Auto-generated by Wayfinder
|
|
76
|
-
- `job_type` - Set to "strategy"
|
|
77
|
-
|
|
78
|
-
## Strategy Configuration
|
|
79
|
-
|
|
80
|
-
### RPC Endpoints
|
|
81
|
-
|
|
82
|
-
Default RPC URLs are provided for:
|
|
83
|
-
- Ethereum (chain ID: 1)
|
|
84
|
-
- Arbitrum (chain ID: 42161)
|
|
85
|
-
- Base (chain ID: 8453)
|
|
86
|
-
- Solana
|
|
87
|
-
|
|
88
|
-
You can override these in `config.json` if needed.
|
|
89
|
-
|
|
90
|
-
### Strategy-Specific Settings
|
|
91
|
-
|
|
92
|
-
Individual strategies may have their own configuration parameters. Check the strategy's README for available options.
|
|
93
|
-
|
|
94
|
-
## Wallet Generation for Testing
|
|
95
|
-
|
|
96
|
-
Use the built-in script to generate test wallets for local development:
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
# Generate main wallet (recommended for initial setup)
|
|
100
|
-
poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
|
|
101
|
-
|
|
102
|
-
# Add additional wallets for multi-account testing
|
|
103
|
-
poetry run python wayfinder_paths/scripts/make_wallets.py -n 3
|
|
104
|
-
|
|
105
|
-
# Create a wallet with a specific label (e.g., for a strategy)
|
|
106
|
-
poetry run python wayfinder_paths/scripts/make_wallets.py --label "my_strategy_name"
|
|
107
|
-
|
|
108
|
-
# Generate keystore files (for geth/web3 compatibility)
|
|
109
|
-
poetry run python wayfinder_paths/scripts/make_wallets.py -n 1 --keystore-password "your-password"
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
This creates `wallets.json` in the repository root with:
|
|
113
|
-
- **main** wallet - your main wallet for testing (labeled "main")
|
|
114
|
-
- Additional wallets with labels if specified
|
|
115
|
-
|
|
116
|
-
**Note:** Generated wallets are for testing only. Wallet addresses are automatically loaded from `wallets.json` when not explicitly set in config. Strategy-specific wallets are automatically created when you use `just create-strategy "Strategy Name"`.
|
|
117
|
-
|
|
118
|
-
## Per-Strategy Wallets
|
|
119
|
-
|
|
120
|
-
Each strategy should have its own dedicated wallet for isolation and security. The system automatically looks up wallets by strategy directory name.
|
|
121
|
-
|
|
122
|
-
### How It Works
|
|
123
|
-
|
|
124
|
-
When you run a strategy:
|
|
125
|
-
1. The system uses the strategy directory name (e.g., `hyperlend_stable_yield_strategy`) to look up a wallet
|
|
126
|
-
2. It searches `wallets.json` for a wallet with a matching `label`
|
|
127
|
-
3. If found, that wallet is used as the strategy's dedicated wallet
|
|
128
|
-
4. Falls back to `strategy_wallet_address` from config if explicitly provided
|
|
129
|
-
|
|
130
|
-
### Creating a Strategy with Wallet
|
|
131
|
-
|
|
132
|
-
The easiest way to create a new strategy with its own wallet:
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
# Create a new strategy with dedicated wallet
|
|
136
|
-
just create-strategy "My Strategy Name"
|
|
137
|
-
|
|
138
|
-
# This automatically:
|
|
139
|
-
# - Creates the strategy directory
|
|
140
|
-
# - Generates a wallet with label matching the directory name
|
|
141
|
-
# - Updates the strategy files with the correct names
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### Wallet Structure
|
|
145
|
-
|
|
146
|
-
Wallets in `wallets.json` are stored with labels that match strategy directory names:
|
|
147
|
-
|
|
148
|
-
```json
|
|
149
|
-
[
|
|
150
|
-
{
|
|
151
|
-
"address": "0x...",
|
|
152
|
-
"private_key_hex": "...",
|
|
153
|
-
"label": "main"
|
|
154
|
-
},
|
|
155
|
-
{
|
|
156
|
-
"address": "0x...",
|
|
157
|
-
"private_key_hex": "...",
|
|
158
|
-
"label": "hyperlend_stable_yield_strategy"
|
|
159
|
-
},
|
|
160
|
-
{
|
|
161
|
-
"address": "0x...",
|
|
162
|
-
"private_key_hex": "...",
|
|
163
|
-
"label": "my_awesome_strategy"
|
|
164
|
-
}
|
|
165
|
-
]
|
|
166
|
-
```
|
|
167
|
-
|
|
168
|
-
### Manual Wallet Creation
|
|
169
|
-
|
|
170
|
-
If you need to manually create a wallet for an existing strategy:
|
|
171
|
-
|
|
172
|
-
```bash
|
|
173
|
-
# Generate a wallet
|
|
174
|
-
poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
|
|
175
|
-
|
|
176
|
-
# Then edit wallets.json to add a label matching your strategy directory name:
|
|
177
|
-
# {
|
|
178
|
-
# "address": "0x...",
|
|
179
|
-
# "private_key_hex": "...",
|
|
180
|
-
# "label": "your_strategy_directory_name"
|
|
181
|
-
# }
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
### Strategy Access
|
|
185
|
-
|
|
186
|
-
Strategies access wallets the same way as before:
|
|
187
|
-
|
|
188
|
-
```python
|
|
189
|
-
# In strategy code
|
|
190
|
-
strategy_address = self.config.get("strategy_wallet").get("address")
|
|
191
|
-
main_address = self.config.get("main_wallet").get("address")
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
The strategy wallet is automatically populated from the wallet with label matching the strategy directory name.
|
|
195
|
-
|
|
196
|
-
## Loading Configuration
|
|
197
|
-
|
|
198
|
-
The configuration is loaded automatically when running strategies via `run_strategy.py`:
|
|
199
|
-
|
|
200
|
-
```bash
|
|
201
|
-
poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy --config config.json
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
For programmatic use:
|
|
205
|
-
|
|
206
|
-
```python
|
|
207
|
-
from pathlib import Path
|
|
208
|
-
from core.config import StrategyJobConfig
|
|
209
|
-
import json
|
|
210
|
-
|
|
211
|
-
# Load from file
|
|
212
|
-
with open("config.json") as f:
|
|
213
|
-
config_data = json.load(f)
|
|
214
|
-
|
|
215
|
-
config = StrategyJobConfig.from_dict(config_data)
|
|
216
|
-
|
|
217
|
-
# Configuration now has:
|
|
218
|
-
# - config.user.username & password (for Wayfinder backend)
|
|
219
|
-
# - config.system.api_base_url & wallets_path
|
|
220
|
-
# - config.strategy_config (strategy-specific settings)
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
## Wallet Abstraction
|
|
224
|
-
|
|
225
|
-
The strategy system supports multiple wallet types through a wallet abstraction layer. By default, adapters use local private keys (self-custodial wallets), but you can inject custom wallet providers for custodial wallets like Privy or Turnkey.
|
|
226
|
-
|
|
227
|
-
### Default Behavior (Local Wallets)
|
|
228
|
-
|
|
229
|
-
By default, adapters use `LocalWalletProvider` which resolves private keys from:
|
|
230
|
-
- `wallets.json` (matched by address)
|
|
231
|
-
- Wallet config in `config.json`
|
|
232
|
-
|
|
233
|
-
No code changes are required - existing strategies continue to work.
|
|
234
|
-
|
|
235
|
-
### Using Custom Wallet Providers
|
|
236
|
-
|
|
237
|
-
To use a custodial wallet provider (e.g., Privy, Turnkey), inject it directly into adapters:
|
|
238
|
-
|
|
239
|
-
```python
|
|
240
|
-
from adapters.evm_transaction_adapter.adapter import EvmTransactionAdapter
|
|
241
|
-
from my_privy_integration import PrivyWalletProvider
|
|
242
|
-
|
|
243
|
-
# Create your custom wallet provider
|
|
244
|
-
privy_provider = PrivyWalletProvider(privy_api_key, privy_wallet_id)
|
|
245
|
-
|
|
246
|
-
# Inject it into adapters
|
|
247
|
-
adapter = EvmTransactionAdapter(config, wallet_provider=privy_provider)
|
|
248
|
-
```
|
|
249
|
-
|
|
250
|
-
See `core/wallets/README.md` for details on implementing custom wallet providers.
|
|
251
|
-
|
|
252
|
-
## Security Best Practices
|
|
253
|
-
|
|
254
|
-
1. **Never commit `config.json`** - add it to `.gitignore`
|
|
255
|
-
2. **Never commit `wallets.json`** - contains private keys
|
|
256
|
-
3. **Use test wallets** - the script generates throwaway wallets for testing
|
|
257
|
-
4. **Keep credentials secure** - Wayfinder username/password grant access to backend resources
|
|
258
|
-
5. **Set conservative parameters** for initial testing:
|
|
259
|
-
- Lower leverage ratios
|
|
260
|
-
- Higher slippage tolerance
|
|
261
|
-
- Lower position sizes
|
|
262
|
-
|
|
263
|
-
## Authentication with Wayfinder Backend
|
|
264
|
-
|
|
265
|
-
Wayfinder Paths supports **dual authentication** for different use cases:
|
|
266
|
-
|
|
267
|
-
### 1. Service Account Authentication (API Key)
|
|
268
|
-
|
|
269
|
-
**Best for:** Backend services, automated systems, and production deployments with higher rate limits.
|
|
270
|
-
|
|
271
|
-
API keys provide service account authentication and are automatically discovered by all clients. You can provide an API key in two ways:
|
|
272
|
-
|
|
273
|
-
#### Option A: Strategy Constructor (Programmatic)
|
|
274
|
-
```python
|
|
275
|
-
from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import StablecoinYieldStrategy
|
|
276
|
-
|
|
277
|
-
strategy = StablecoinYieldStrategy(
|
|
278
|
-
config={...},
|
|
279
|
-
api_key="sk_live_abc123..." # API key is auto-discovered by all clients
|
|
280
|
-
)
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
#### Option B: config.json
|
|
284
|
-
```json
|
|
285
|
-
{
|
|
286
|
-
"user": {
|
|
287
|
-
"api_key": "sk_live_abc123..."
|
|
288
|
-
},
|
|
289
|
-
"system": {
|
|
290
|
-
"api_key": "sk_live_abc123..." // Alternative: system-level API key
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
**Priority Order:** Constructor parameter > `config.json` (user.api_key or system.api_key)
|
|
296
|
-
|
|
297
|
-
**How It Works:**
|
|
298
|
-
- All clients created by adapters automatically discover the API key from:
|
|
299
|
-
- Constructor parameter (if passed)
|
|
300
|
-
- `config.json` (via `WayfinderClient._load_config_credentials()` which reads `user.api_key` or `system.api_key`)
|
|
301
|
-
- API keys are included in **all** API requests (including public endpoints) for rate limiting
|
|
302
|
-
- No need to pass API keys explicitly to adapters or clients—they auto-discover it
|
|
303
|
-
- **Note:** API keys in `config.json` are loaded directly by clients, not stored in the `UserConfig` or `SystemConfig` dataclasses
|
|
304
|
-
|
|
305
|
-
### 2. Personal Access Authentication (OAuth)
|
|
306
|
-
|
|
307
|
-
**Best for:** Standalone SDK users and local development.
|
|
308
|
-
|
|
309
|
-
The `username` and `password` in your config authenticate with the Wayfinder backend to access:
|
|
310
|
-
- Wallet management
|
|
311
|
-
- Transaction signing services
|
|
312
|
-
- Strategy execution services
|
|
313
|
-
|
|
314
|
-
```json
|
|
315
|
-
{
|
|
316
|
-
"user": {
|
|
317
|
-
"username": "your_username",
|
|
318
|
-
"password": "your_password",
|
|
319
|
-
"refresh_token": null // Optional: use refresh token instead of username/password
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
```
|
|
323
|
-
|
|
324
|
-
**Fallback Behavior:**
|
|
325
|
-
- If an API key is not found or authentication fails, the system automatically falls back to OAuth
|
|
326
|
-
- OAuth tokens are automatically refreshed when they expire
|
|
327
|
-
|
|
328
|
-
### Security Best Practices
|
|
329
|
-
|
|
330
|
-
- **Never commit credentials** to version control - add `config.json` to `.gitignore`
|
|
331
|
-
- **Use API keys for production** - they provide better rate limits and don't require token refresh
|
|
332
|
-
- **Use OAuth for development** - simpler setup for local testing
|
|
333
|
-
- **Rotate credentials regularly** - especially if exposed or compromised
|
|
334
|
-
|
|
335
|
-
## Configuration in Strategies
|
|
336
|
-
|
|
337
|
-
Strategies receive configuration automatically through StrategyJob:
|
|
338
|
-
|
|
339
|
-
```python
|
|
340
|
-
from core.strategies.Strategy import Strategy
|
|
341
|
-
|
|
342
|
-
class MyStrategy(Strategy):
|
|
343
|
-
async def setup(self):
|
|
344
|
-
# Access strategy-specific config
|
|
345
|
-
target_leverage = self.config.get("target_leverage", 1.0)
|
|
346
|
-
|
|
347
|
-
# Access RPC URLs
|
|
348
|
-
eth_rpc = self.config.get("rpc_urls", {}).get("1")
|
|
349
|
-
|
|
350
|
-
# Configuration is already loaded from config.json
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
## Advanced: Custom RPC Endpoints
|
|
354
|
-
|
|
355
|
-
To use custom RPC endpoints, update the `strategy.rpc_urls` section in `config.json`:
|
|
356
|
-
|
|
357
|
-
```json
|
|
358
|
-
{
|
|
359
|
-
"strategy": {
|
|
360
|
-
"rpc_urls": {
|
|
361
|
-
"1": "https://your-custom-ethereum-rpc.com",
|
|
362
|
-
"8453": "https://your-custom-base-rpc.com"
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
## Troubleshooting
|
|
369
|
-
|
|
370
|
-
**Issue:** "Authentication failed"
|
|
371
|
-
- **If using API key:**
|
|
372
|
-
- Verify API key is correct and not expired
|
|
373
|
-
- Check that API key is set in one of: constructor parameter or `config.json` (user.api_key or system.api_key)
|
|
374
|
-
- Ensure API key has proper permissions for the operations you're performing
|
|
375
|
-
- **If using OAuth:**
|
|
376
|
-
- Check that `username` and `password` are correct in `config.json`
|
|
377
|
-
- Verify your Wayfinder account credentials
|
|
378
|
-
- Try using `refresh_token` instead if you have one
|
|
379
|
-
- **General:**
|
|
380
|
-
- The system automatically falls back from API key to OAuth if API key authentication fails
|
|
381
|
-
- Check logs for specific authentication error messages
|
|
382
|
-
|
|
383
|
-
**Issue:** "Wallet not found"
|
|
384
|
-
- Run the wallet generation script first
|
|
385
|
-
- Check that `wallets.json` exists in repository root
|
|
386
|
-
- Verify `system.wallets_path` in config points to the correct location
|
|
387
|
-
|
|
388
|
-
**Issue:** "Invalid config"
|
|
389
|
-
- Ensure `config.json` follows the correct structure
|
|
390
|
-
- Check that all required fields are present
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"user": {
|
|
3
|
-
"username": "your_username",
|
|
4
|
-
"password": "your_password",
|
|
5
|
-
"refresh_token": null,
|
|
6
|
-
"api_key": "sk_live_abc123..."
|
|
7
|
-
},
|
|
8
|
-
"system": {
|
|
9
|
-
"api_base_url": "https://wayfinder.ai/api/v1",
|
|
10
|
-
"api_key": "sk_live_abc123...",
|
|
11
|
-
"wallets_path": "wallets.json"
|
|
12
|
-
},
|
|
13
|
-
"strategy": {
|
|
14
|
-
"rpc_urls": {
|
|
15
|
-
"1": "https://eth.llamarpc.com",
|
|
16
|
-
"42161": "https://arb1.arbitrum.io/rpc",
|
|
17
|
-
"8453": "https://mainnet.base.org",
|
|
18
|
-
"solana": "https://api.mainnet-beta.solana.com",
|
|
19
|
-
"999": "https://rpc.hyperliquid.xyz/evm"
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
}
|
|
File without changes
|
|
File without changes
|