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.

@@ -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, Web3
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 = 0,
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
- w3 = self.get_web3(chain_id)
173
+ web3 = self.get_web3(chain_id)
274
174
  try:
275
175
  transaction = self._validate_transaction(transaction)
276
- transaction = await self._nonce_transaction(transaction, w3)
277
- transaction = await self._gas_limit_transaction(transaction, w3)
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, w3
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 w3.eth.send_raw_transaction(signed_tx)
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 w3.eth.wait_for_transaction_receipt(
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
- w3, tx_block, confirmations
208
+ web3, tx_block, confirmations
322
209
  )
323
210
 
324
211
  return (True, result)
325
212
 
326
213
  finally:
327
- await self._close_web3(w3)
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, w3: AsyncWeb3) -> None:
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
- await w3.provider.session.close()
371
- except Exception: # noqa: BLE001
372
- pass
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.10
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 just create-wallet stablecoin_yield_strategy
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 | 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 |
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=cvfNoS8VXP7ksrZIjk4BYvqtSSueiHphcxny7Qt7VHY,5980
77
- wayfinder_paths/core/services/local_evm_txn.py,sha256=kzJUOvHWLZ0ygRmYpVnJJTp48E072YpfD8R9IJJIzOM,16522
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=D1NFanIdy7TcBHOFwYHAgnhENChaW34BNarFfauMX38,6230
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.10.dist-info/LICENSE,sha256=dYKnlkC_xosBAEQNUvB6cHMuhFgcUtN0oBR7E8_aR2Y,1066
140
- wayfinder_paths-0.1.10.dist-info/METADATA,sha256=Nyi2X5KQqUSKYoXjM7MQl_mnYZ8EIYbZ4t0BPwC-RSY,25656
141
- wayfinder_paths-0.1.10.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
142
- wayfinder_paths-0.1.10.dist-info/RECORD,,
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,,
@@ -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
- }