wayfinder-paths 0.1.8__py3-none-any.whl → 0.1.10__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.

Files changed (80) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +6 -15
  2. wayfinder_paths/adapters/balance_adapter/README.md +1 -2
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +4 -4
  4. wayfinder_paths/adapters/brap_adapter/README.md +1 -1
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +139 -74
  6. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
  7. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
  8. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
  9. wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
  10. wayfinder_paths/adapters/moonwell_adapter/README.md +174 -0
  11. wayfinder_paths/adapters/moonwell_adapter/__init__.py +7 -0
  12. wayfinder_paths/adapters/moonwell_adapter/adapter.py +1226 -0
  13. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +635 -0
  14. wayfinder_paths/adapters/pool_adapter/README.md +1 -77
  15. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -122
  16. wayfinder_paths/adapters/pool_adapter/examples.json +0 -57
  17. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -86
  18. wayfinder_paths/adapters/token_adapter/README.md +1 -1
  19. wayfinder_paths/core/clients/ClientManager.py +1 -22
  20. wayfinder_paths/core/clients/WalletClient.py +0 -8
  21. wayfinder_paths/core/clients/WayfinderClient.py +7 -12
  22. wayfinder_paths/core/clients/__init__.py +0 -8
  23. wayfinder_paths/core/clients/protocols.py +0 -60
  24. wayfinder_paths/core/config.py +5 -45
  25. wayfinder_paths/core/constants/__init__.py +0 -2
  26. wayfinder_paths/core/constants/base.py +6 -2
  27. wayfinder_paths/core/constants/moonwell_abi.py +411 -0
  28. wayfinder_paths/core/services/base.py +7 -1
  29. wayfinder_paths/core/services/local_evm_txn.py +223 -222
  30. wayfinder_paths/core/services/local_token_txn.py +103 -92
  31. wayfinder_paths/core/services/web3_service.py +0 -2
  32. wayfinder_paths/core/settings.py +8 -8
  33. wayfinder_paths/core/strategies/Strategy.py +1 -5
  34. wayfinder_paths/core/strategies/descriptors.py +1 -1
  35. wayfinder_paths/core/utils/evm_helpers.py +7 -12
  36. wayfinder_paths/core/wallets/README.md +3 -6
  37. wayfinder_paths/run_strategy.py +62 -105
  38. wayfinder_paths/scripts/create_strategy.py +2 -27
  39. wayfinder_paths/scripts/make_wallets.py +1 -25
  40. wayfinder_paths/scripts/run_strategy.py +37 -9
  41. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
  42. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +87 -138
  43. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
  44. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -17
  45. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
  46. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +107 -29
  47. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
  48. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +108 -0
  49. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/examples.json +11 -0
  50. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2975 -0
  51. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +886 -0
  52. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +0 -7
  53. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +2 -7
  54. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -4
  55. wayfinder_paths/templates/adapter/README.md +5 -21
  56. wayfinder_paths/templates/adapter/adapter.py +1 -2
  57. wayfinder_paths/templates/adapter/test_adapter.py +1 -1
  58. wayfinder_paths/templates/strategy/README.md +4 -21
  59. wayfinder_paths/templates/strategy/test_strategy.py +0 -4
  60. wayfinder_paths/tests/test_smoke_manifest.py +17 -2
  61. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/METADATA +64 -201
  62. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/RECORD +64 -71
  63. wayfinder_paths/adapters/balance_adapter/manifest.yaml +0 -8
  64. wayfinder_paths/adapters/brap_adapter/manifest.yaml +0 -11
  65. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +0 -10
  66. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +0 -8
  67. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +0 -11
  68. wayfinder_paths/adapters/pool_adapter/manifest.yaml +0 -10
  69. wayfinder_paths/adapters/token_adapter/manifest.yaml +0 -6
  70. wayfinder_paths/core/clients/SimulationClient.py +0 -192
  71. wayfinder_paths/core/clients/TransactionClient.py +0 -63
  72. wayfinder_paths/core/engine/manifest.py +0 -97
  73. wayfinder_paths/scripts/validate_manifests.py +0 -213
  74. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +0 -23
  75. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +0 -7
  76. wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +0 -17
  77. wayfinder_paths/templates/adapter/manifest.yaml +0 -6
  78. wayfinder_paths/templates/strategy/manifest.yaml +0 -8
  79. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/LICENSE +0 -0
  80. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/WHEEL +0 -0
@@ -1,15 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
4
+ from decimal import ROUND_DOWN, Decimal
3
5
  from typing import Any
4
6
 
5
7
  from eth_utils import to_checksum_address
6
8
  from loguru import logger
7
- from web3 import AsyncWeb3
9
+ from web3 import AsyncWeb3, Web3
8
10
 
9
11
  from wayfinder_paths.core.clients.TokenClient import TokenClient
10
- from wayfinder_paths.core.clients.TransactionClient import TransactionClient
11
12
  from wayfinder_paths.core.constants import ZERO_ADDRESS
12
- from wayfinder_paths.core.constants.erc20_abi import ERC20_APPROVAL_ABI
13
+ from wayfinder_paths.core.constants.erc20_abi import ERC20_ABI, ERC20_APPROVAL_ABI
13
14
  from wayfinder_paths.core.services.base import EvmTxn, TokenTxn
14
15
  from wayfinder_paths.core.utils.evm_helpers import resolve_chain_id
15
16
 
@@ -22,13 +23,11 @@ class LocalTokenTxnService(TokenTxn):
22
23
  config: dict[str, Any] | None,
23
24
  *,
24
25
  wallet_provider: EvmTxn,
25
- simulation: bool = False,
26
26
  ) -> None:
27
- del config, simulation
27
+ del config
28
28
  self.wallet_provider = wallet_provider
29
29
  self.logger = logger.bind(service="DefaultEvmTransactionService")
30
30
  self.token_client = TokenClient()
31
- self.builder = _EvmTransactionBuilder()
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
- tx = await self.builder.build_send_transaction(
64
+ tx = await self.build_send_transaction(
57
65
  from_address=from_address,
58
66
  to_address=to_address,
59
67
  token_address=token_address,
60
- amount=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}"
@@ -84,7 +93,7 @@ class LocalTokenTxnService(TokenTxn):
84
93
  except (TypeError, ValueError) as exc:
85
94
  return False, str(exc)
86
95
 
87
- approve_tx = self.builder.build_erc20_approval_transaction(
96
+ approve_tx = self.build_erc20_approval_transaction(
88
97
  chain_id=chain_id,
89
98
  token_address=token_checksum,
90
99
  from_address=from_checksum,
@@ -95,28 +104,53 @@ class LocalTokenTxnService(TokenTxn):
95
104
  return True, approve_tx
96
105
 
97
106
  async def read_erc20_allowance(
98
- self, chain: Any, token_address: str, from_address: str, spender_address: str
107
+ self,
108
+ chain: Any,
109
+ token_address: str,
110
+ from_address: str,
111
+ spender_address: str,
112
+ max_retries: int = 3,
99
113
  ) -> dict[str, Any]:
100
114
  try:
101
115
  chain_id = self._chain_id(chain)
102
116
  except (TypeError, ValueError) as exc:
103
117
  return {"error": str(exc), "allowance": 0}
104
118
 
105
- w3 = self.get_web3(chain_id)
106
- try:
107
- contract = w3.eth.contract(
108
- address=to_checksum_address(token_address), abi=ERC20_APPROVAL_ABI
109
- )
110
- allowance = await contract.functions.allowance(
111
- to_checksum_address(from_address),
112
- to_checksum_address(spender_address),
113
- ).call()
114
- return (True, {"allowance": int(allowance)})
115
- except Exception as exc: # noqa: BLE001
116
- self.logger.error(f"Failed to read allowance: {exc}")
117
- return {"error": f"Allowance query failed: {exc}", "allowance": 0}
118
- finally:
119
- await self._close_web3(w3)
119
+ last_error = None
120
+ for attempt in range(max_retries):
121
+ w3 = self.wallet_provider.get_web3(chain_id)
122
+ try:
123
+ contract = w3.eth.contract(
124
+ address=to_checksum_address(token_address), abi=ERC20_APPROVAL_ABI
125
+ )
126
+ allowance = await contract.functions.allowance(
127
+ to_checksum_address(from_address),
128
+ to_checksum_address(spender_address),
129
+ ).call()
130
+ return {"allowance": int(allowance)}
131
+ except Exception as exc: # noqa: BLE001
132
+ last_error = exc
133
+ error_str = str(exc)
134
+ if "429" in error_str or "Too Many Requests" in error_str:
135
+ if attempt < max_retries - 1:
136
+ wait_time = 3.0 * (2**attempt) # 3, 6, 12 seconds
137
+ self.logger.warning(
138
+ f"Rate limited reading allowance, retrying in {wait_time}s..."
139
+ )
140
+ await asyncio.sleep(wait_time)
141
+ continue
142
+ self.logger.error(f"Failed to read allowance: {exc}")
143
+ return {"error": f"Allowance query failed: {exc}", "allowance": 0}
144
+ finally:
145
+ await self.wallet_provider._close_web3(w3)
146
+
147
+ self.logger.error(
148
+ f"Failed to read allowance after {max_retries} retries: {last_error}"
149
+ )
150
+ return {
151
+ "error": f"Allowance query failed after retries: {last_error}",
152
+ "allowance": 0,
153
+ }
120
154
 
121
155
  def _chain_id(self, chain: Any) -> int:
122
156
  if isinstance(chain, dict):
@@ -127,12 +161,13 @@ class LocalTokenTxnService(TokenTxn):
127
161
  raise ValueError("Chain ID is required")
128
162
  return int(chain_id)
129
163
 
130
-
131
- class _EvmTransactionBuilder:
132
- """Helpers that only build transaction dictionaries for sends and approvals."""
133
-
134
- def __init__(self) -> None:
135
- self.transaction_client = TransactionClient()
164
+ def _to_base_units(self, amount: float, decimals: int) -> int:
165
+ """Convert human-readable amount to base units (wei for native, token units for ERC20)."""
166
+ scale = Decimal(10) ** int(decimals)
167
+ quantized = (Decimal(str(amount)) * scale).to_integral_value(
168
+ rounding=ROUND_DOWN
169
+ )
170
+ return int(quantized)
136
171
 
137
172
  async def build_send_transaction(
138
173
  self,
@@ -140,22 +175,37 @@ class _EvmTransactionBuilder:
140
175
  from_address: str,
141
176
  to_address: str,
142
177
  token_address: str | None,
143
- amount: float,
178
+ amount: int,
144
179
  chain_id: int,
180
+ is_native: bool,
145
181
  ) -> dict[str, Any]:
146
182
  """Build the transaction dict for sending native or ERC20 tokens."""
147
- payload = await self.transaction_client.build_send(
148
- from_address=from_address,
149
- to_address=to_address,
150
- token_address=token_address or "",
151
- amount=float(amount),
152
- chain_id=int(chain_id),
153
- )
154
- return self._payload_to_tx(
155
- payload=payload,
156
- from_address=from_address,
157
- is_native=not token_address or token_address.lower() == ZERO_ADDRESS,
158
- )
183
+ from_checksum = to_checksum_address(from_address)
184
+ to_checksum = to_checksum_address(to_address)
185
+ chain_id_int = int(chain_id)
186
+
187
+ if is_native:
188
+ return {
189
+ "chainId": chain_id_int,
190
+ "from": from_checksum,
191
+ "to": to_checksum,
192
+ "value": int(amount),
193
+ }
194
+
195
+ token_checksum = to_checksum_address(token_address or ZERO_ADDRESS)
196
+ w3_sync = Web3()
197
+ contract = w3_sync.eth.contract(address=token_checksum, abi=ERC20_ABI)
198
+ data = contract.functions.transfer(
199
+ to_checksum, int(amount)
200
+ )._encode_transaction_data()
201
+
202
+ return {
203
+ "chainId": chain_id_int,
204
+ "from": from_checksum,
205
+ "to": token_checksum,
206
+ "data": data,
207
+ "value": 0,
208
+ }
159
209
 
160
210
  def build_erc20_approval_transaction(
161
211
  self,
@@ -168,15 +218,20 @@ class _EvmTransactionBuilder:
168
218
  web3: AsyncWeb3,
169
219
  ) -> dict[str, Any]:
170
220
  """Build an ERC20 approval transaction dict."""
221
+ del web3 # Use sync Web3 for encoding (AsyncContract doesn't have encodeABI)
171
222
  token_checksum = to_checksum_address(token_address)
172
223
  spender_checksum = to_checksum_address(spender)
173
224
  from_checksum = to_checksum_address(from_address)
174
225
  amount_int = int(amount)
175
226
 
176
- contract = web3.eth.contract(address=token_checksum, abi=ERC20_APPROVAL_ABI)
177
- data = contract.encodeABI(
178
- fn_name="approve", args=[spender_checksum, amount_int]
179
- )
227
+ # Use synchronous Web3 for encoding (encodeABI doesn't exist in web3.py v7)
228
+ w3_sync = Web3()
229
+ contract = w3_sync.eth.contract(address=token_checksum, abi=ERC20_APPROVAL_ABI)
230
+
231
+ # In web3.py v7, use _encode_transaction_data to encode without network calls
232
+ data = contract.functions.approve(
233
+ spender_checksum, amount_int
234
+ )._encode_transaction_data()
180
235
 
181
236
  return {
182
237
  "chainId": int(chain_id),
@@ -185,47 +240,3 @@ class _EvmTransactionBuilder:
185
240
  "data": data,
186
241
  "value": 0,
187
242
  }
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
@@ -12,14 +12,14 @@ class CoreSettings(BaseSettings):
12
12
  """
13
13
 
14
14
  model_config = SettingsConfigDict(
15
- env_file=".env",
16
- env_file_encoding="utf-8",
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", env="API_ENV")
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(), env="WAYFINDER_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", env="NETWORK") # mainnet, testnet, devnet
50
+ NETWORK: str = Field(default="testnet") # mainnet, testnet, devnet
51
51
 
52
52
  # Logging
53
- LOG_LEVEL: str = Field("INFO", env="LOG_LEVEL")
54
- LOG_FILE: str = Field("logs/strategy.log", env="LOG_FILE")
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, env="DRY_RUN")
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
- if api_key:
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:
@@ -66,7 +66,7 @@ class StratDescriptor(BaseModel):
66
66
 
67
67
  # risk indicators
68
68
  volatility: Volatility
69
- volatility_description_short: str
69
+ volatility_description: str
70
70
  directionality: Directionality
71
71
  directionality_description: str
72
72
  complexity: Complexity
@@ -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 or environment variables.
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
- env_rpc = os.getenv("RPC_URL")
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 or environment.
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 or os.getenv("PRIVATE_KEY")
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
- # Fallback to environment variables
169
- return os.getenv("PRIVATE_KEY_STRATEGY") or os.getenv("PRIVATE_KEY")
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/environment variables, while `WalletManager` resolves which provider to use at runtime.
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
  ```