wayfinder-paths 0.1.7__py3-none-any.whl → 0.1.9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +5 -14
  2. wayfinder_paths/adapters/brap_adapter/README.md +1 -1
  3. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -53
  4. wayfinder_paths/adapters/brap_adapter/test_adapter.py +5 -7
  5. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
  6. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
  7. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
  8. wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
  9. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +3 -0
  10. wayfinder_paths/adapters/pool_adapter/README.md +3 -104
  11. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -194
  12. wayfinder_paths/adapters/pool_adapter/examples.json +0 -100
  13. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -134
  14. wayfinder_paths/adapters/token_adapter/README.md +1 -1
  15. wayfinder_paths/core/clients/AuthClient.py +0 -3
  16. wayfinder_paths/core/clients/BRAPClient.py +1 -0
  17. wayfinder_paths/core/clients/ClientManager.py +1 -22
  18. wayfinder_paths/core/clients/PoolClient.py +0 -16
  19. wayfinder_paths/core/clients/WalletClient.py +0 -8
  20. wayfinder_paths/core/clients/WayfinderClient.py +9 -14
  21. wayfinder_paths/core/clients/__init__.py +0 -8
  22. wayfinder_paths/core/clients/protocols.py +0 -64
  23. wayfinder_paths/core/config.py +5 -45
  24. wayfinder_paths/core/engine/StrategyJob.py +0 -3
  25. wayfinder_paths/core/services/base.py +0 -49
  26. wayfinder_paths/core/services/local_evm_txn.py +3 -82
  27. wayfinder_paths/core/services/local_token_txn.py +61 -70
  28. wayfinder_paths/core/services/web3_service.py +0 -2
  29. wayfinder_paths/core/settings.py +8 -8
  30. wayfinder_paths/core/strategies/Strategy.py +1 -5
  31. wayfinder_paths/core/utils/evm_helpers.py +7 -12
  32. wayfinder_paths/core/wallets/README.md +3 -6
  33. wayfinder_paths/run_strategy.py +29 -32
  34. wayfinder_paths/scripts/make_wallets.py +1 -25
  35. wayfinder_paths/scripts/run_strategy.py +0 -2
  36. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
  37. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +86 -137
  38. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
  39. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
  40. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
  41. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +106 -28
  42. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
  43. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1 -6
  44. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +40 -17
  45. wayfinder_paths/templates/strategy/test_strategy.py +0 -4
  46. {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/METADATA +33 -15
  47. {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/RECORD +49 -51
  48. wayfinder_paths/core/clients/SimulationClient.py +0 -192
  49. wayfinder_paths/core/clients/TransactionClient.py +0 -63
  50. {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/LICENSE +0 -0
  51. {wayfinder_paths-0.1.7.dist-info → wayfinder_paths-0.1.9.dist-info}/WHEEL +0 -0
@@ -13,14 +13,10 @@ from wayfinder_paths.core.clients.protocols import (
13
13
  HyperlendClientProtocol,
14
14
  LedgerClientProtocol,
15
15
  PoolClientProtocol,
16
- SimulationClientProtocol,
17
16
  TokenClientProtocol,
18
- TransactionClientProtocol,
19
17
  WalletClientProtocol,
20
18
  )
21
- from wayfinder_paths.core.clients.SimulationClient import SimulationClient
22
19
  from wayfinder_paths.core.clients.TokenClient import TokenClient
23
- from wayfinder_paths.core.clients.TransactionClient import TransactionClient
24
20
  from wayfinder_paths.core.clients.WalletClient import WalletClient
25
21
  from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
26
22
 
@@ -30,19 +26,15 @@ __all__ = [
30
26
  "AuthClient",
31
27
  "TokenClient",
32
28
  "WalletClient",
33
- "TransactionClient",
34
29
  "LedgerClient",
35
30
  "PoolClient",
36
31
  "BRAPClient",
37
- "SimulationClient",
38
32
  "HyperlendClient",
39
33
  # Protocols for SDK usage
40
34
  "TokenClientProtocol",
41
35
  "HyperlendClientProtocol",
42
36
  "LedgerClientProtocol",
43
37
  "WalletClientProtocol",
44
- "TransactionClientProtocol",
45
38
  "PoolClientProtocol",
46
39
  "BRAPClientProtocol",
47
- "SimulationClientProtocol",
48
40
  ]
@@ -28,12 +28,10 @@ if TYPE_CHECKING:
28
28
  LlamaReport,
29
29
  PoolList,
30
30
  )
31
- from wayfinder_paths.core.clients.SimulationClient import SimulationResult
32
31
  from wayfinder_paths.core.clients.TokenClient import (
33
32
  GasToken,
34
33
  TokenDetails,
35
34
  )
36
- from wayfinder_paths.core.clients.TransactionClient import TransactionPayload
37
35
  from wayfinder_paths.core.clients.WalletClient import (
38
36
  PoolBalance,
39
37
  TokenBalance,
@@ -186,21 +184,6 @@ class WalletClientProtocol(Protocol):
186
184
  ...
187
185
 
188
186
 
189
- class TransactionClientProtocol(Protocol):
190
- """Protocol for transaction operations"""
191
-
192
- async def build_send(
193
- self,
194
- from_address: str,
195
- to_address: str,
196
- token_address: str,
197
- amount: float,
198
- chain_id: int,
199
- ) -> TransactionPayload:
200
- """Build a send transaction payload for EVM tokens/native transfers"""
201
- ...
202
-
203
-
204
187
  class PoolClientProtocol(Protocol):
205
188
  """Protocol for pool-related read operations"""
206
189
 
@@ -213,10 +196,6 @@ class PoolClientProtocol(Protocol):
213
196
  """Fetch pools by comma-separated pool ids"""
214
197
  ...
215
198
 
216
- async def get_all_pools(self, *, merge_external: bool | None = None) -> PoolList:
217
- """Fetch all pools"""
218
- ...
219
-
220
199
  async def get_llama_matches(self) -> dict[str, LlamaMatch]:
221
200
  """Fetch Llama matches for pools"""
222
201
  ...
@@ -246,49 +225,6 @@ class BRAPClientProtocol(Protocol):
246
225
  ...
247
226
 
248
227
 
249
- class SimulationClientProtocol(Protocol):
250
- """Protocol for blockchain transaction simulations"""
251
-
252
- async def simulate_send(
253
- self,
254
- from_address: str,
255
- to_address: str,
256
- token_address: str,
257
- amount: str,
258
- chain_id: int,
259
- initial_balances: dict[str, str],
260
- ) -> SimulationResult:
261
- """Simulate sending native ETH or ERC20 tokens"""
262
- ...
263
-
264
- async def simulate_approve(
265
- self,
266
- from_address: str,
267
- to_address: str,
268
- token_address: str,
269
- amount: str,
270
- chain_id: int,
271
- initial_balances: dict[str, str],
272
- clear_approval_first: bool = False,
273
- ) -> SimulationResult:
274
- """Simulate ERC20 token approval"""
275
- ...
276
-
277
- async def simulate_swap(
278
- self,
279
- from_token_address: str,
280
- to_token_address: str,
281
- from_chain_id: int,
282
- to_chain_id: int,
283
- amount: str,
284
- from_address: str,
285
- slippage: float,
286
- initial_balances: dict[str, str],
287
- ) -> SimulationResult:
288
- """Simulate token swap operation"""
289
- ...
290
-
291
-
292
228
  class HyperliquidExecutorProtocol(Protocol):
293
229
  """Protocol for Hyperliquid order execution operations."""
294
230
 
@@ -4,7 +4,6 @@ Separates user-provided configuration from system configuration
4
4
  """
5
5
 
6
6
  import json
7
- import os
8
7
  from dataclasses import dataclass, field
9
8
  from pathlib import Path
10
9
  from typing import Any
@@ -72,12 +71,8 @@ class SystemConfig:
72
71
  These are values managed by the Wayfinder system
73
72
  """
74
73
 
75
- # API endpoints (populated from environment or defaults)
76
- api_base_url: str = field(
77
- default_factory=lambda: os.getenv(
78
- "WAYFINDER_API_URL", "https://api.wayfinder.ai"
79
- )
80
- )
74
+ # API endpoints (populated from config.json or defaults)
75
+ api_base_url: str = field(default="https://api.wayfinder.ai")
81
76
 
82
77
  # Job configuration
83
78
  job_id: str | None = None
@@ -102,10 +97,7 @@ class SystemConfig:
102
97
  def from_dict(cls, data: dict[str, Any]) -> "SystemConfig":
103
98
  """Create SystemConfig from dictionary"""
104
99
  return cls(
105
- api_base_url=data.get(
106
- "api_base_url",
107
- os.getenv("WAYFINDER_API_URL", "https://api.wayfinder.ai"),
108
- ),
100
+ api_base_url=data.get("api_base_url", "https://api.wayfinder.ai"),
109
101
  job_id=data.get("job_id"),
110
102
  job_type=data.get("job_type", "strategy"),
111
103
  update_interval=data.get("update_interval", 60),
@@ -113,10 +105,8 @@ class SystemConfig:
113
105
  retry_delay=data.get("retry_delay", 5),
114
106
  log_path=data.get("log_path"),
115
107
  data_path=data.get("data_path"),
116
- wallets_path=data.get(
117
- "wallets_path", os.getenv("WALLETS_PATH", "wallets.json")
118
- ),
119
- wallet_id=data.get("wallet_id") or os.getenv("WALLET_ID"),
108
+ wallets_path=data.get("wallets_path", "wallets.json"),
109
+ wallet_id=data.get("wallet_id"),
120
110
  )
121
111
 
122
112
  def to_dict(self) -> dict[str, Any]:
@@ -386,36 +376,6 @@ class StrategyJobConfig:
386
376
  return config
387
377
 
388
378
 
389
- def load_config_from_env() -> StrategyJobConfig:
390
- """
391
- Load configuration from environment variables
392
- This is the simplest way for users to provide configuration
393
- """
394
- user_config = UserConfig(
395
- username=os.getenv("WAYFINDER_USERNAME"),
396
- password=os.getenv("WAYFINDER_PASSWORD"),
397
- refresh_token=os.getenv("WAYFINDER_REFRESH_TOKEN"),
398
- main_wallet_address=os.getenv("MAIN_WALLET_ADDRESS"),
399
- strategy_wallet_address=os.getenv("STRATEGY_WALLET_ADDRESS"),
400
- default_slippage=float(os.getenv("DEFAULT_SLIPPAGE", "0.005")),
401
- gas_multiplier=float(os.getenv("GAS_MULTIPLIER", "1.2")),
402
- )
403
-
404
- system_config = SystemConfig(
405
- api_base_url=os.getenv("WAYFINDER_API_URL", "https://api.wayfinder.ai"),
406
- job_id=os.getenv("JOB_ID"),
407
- update_interval=int(os.getenv("UPDATE_INTERVAL", "60")),
408
- max_retries=int(os.getenv("MAX_RETRIES", "3")),
409
- retry_delay=int(os.getenv("RETRY_DELAY", "5")),
410
- wallets_path=os.getenv("WALLETS_PATH", "wallets.json"),
411
- wallet_id=os.getenv("WALLET_ID"),
412
- )
413
-
414
- # No auto-population - wallets must be explicitly set in environment or matched by label
415
-
416
- return StrategyJobConfig(user=user_config, system=system_config)
417
-
418
-
419
379
  # --- Internal helpers -------------------------------------------------------
420
380
 
421
381
 
@@ -1,5 +1,4 @@
1
1
  import asyncio
2
- import os
3
2
  from typing import Any
4
3
 
5
4
  from loguru import logger
@@ -54,8 +53,6 @@ class StrategyJob:
54
53
  creds = self.clients.auth._load_config_credentials()
55
54
  if creds.get("api_key"):
56
55
  return True
57
- if os.getenv("WAYFINDER_API_KEY"):
58
- return True
59
56
  except Exception:
60
57
  pass
61
58
 
@@ -50,55 +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
- ) -> tuple[bool, Any]:
60
- """
61
- Get balance for an address.
62
-
63
- Args:
64
- address: Address to query balance for
65
- token_address: ERC20 token address, or None for native token
66
- chain_id: Chain ID
67
-
68
- Returns:
69
- Tuple of (success, balance_integer_or_error_message)
70
- """
71
- pass
72
-
73
- @abstractmethod
74
- async def approve_token(
75
- self,
76
- token_address: str,
77
- spender: str,
78
- amount: int,
79
- from_address: str,
80
- chain_id: int,
81
- wait_for_receipt: bool = True,
82
- timeout: int = DEFAULT_TRANSACTION_TIMEOUT,
83
- ) -> tuple[bool, Any]:
84
- """
85
- Approve a spender to spend tokens on behalf of from_address.
86
-
87
- Args:
88
- token_address: ERC20 token contract address
89
- spender: Address being approved to spend tokens
90
- amount: Amount to approve (in token units, not human-readable)
91
- from_address: Address approving the tokens
92
- chain_id: Chain ID
93
- wait_for_receipt: Whether to wait for the transaction receipt
94
- timeout: Receipt timeout in seconds
95
-
96
- Returns:
97
- Tuple of (success, transaction_result_dict_or_error_message)
98
- Transaction result should include 'tx_hash' and optionally 'receipt'
99
- """
100
- pass
101
-
102
53
  @abstractmethod
103
54
  async def broadcast_transaction(
104
55
  self,
@@ -4,18 +4,13 @@ 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
9
  from wayfinder_paths.core.constants import (
10
10
  DEFAULT_GAS_ESTIMATE_FALLBACK,
11
11
  ONE_GWEI,
12
- ZERO_ADDRESS,
13
12
  )
14
13
  from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
15
- from wayfinder_paths.core.constants.erc20_abi import (
16
- ERC20_APPROVAL_ABI,
17
- ERC20_MINIMAL_ABI,
18
- )
19
14
  from wayfinder_paths.core.services.base import EvmTxn
20
15
  from wayfinder_paths.core.utils.evm_helpers import (
21
16
  resolve_private_key_for_from_address,
@@ -79,10 +74,10 @@ class NonceManager:
79
74
 
80
75
  class LocalEvmTxn(EvmTxn):
81
76
  """
82
- Local wallet provider using private keys stored in config or environment variables.
77
+ Local wallet provider using private keys stored in config.json or wallets.json.
83
78
 
84
79
  This provider implements the current default behavior:
85
- - Resolves private keys from config or environment
80
+ - Resolves private keys from config.json or wallets.json
86
81
  - Signs transactions using eth_account
87
82
  - Broadcasts transactions via RPC
88
83
  """
@@ -107,80 +102,6 @@ class LocalEvmTxn(EvmTxn):
107
102
  rpc_url = self._resolve_rpc_url(chain_id)
108
103
  return AsyncWeb3(AsyncHTTPProvider(rpc_url))
109
104
 
110
- async def get_balance(
111
- self,
112
- address: str,
113
- token_address: str | None,
114
- chain_id: int,
115
- ) -> tuple[bool, Any]:
116
- """
117
- Get balance for an address (native or ERC20 token).
118
- """
119
- w3 = self.get_web3(chain_id)
120
- try:
121
- checksum_addr = to_checksum_address(address)
122
-
123
- if not token_address or token_address.lower() == ZERO_ADDRESS:
124
- balance = await w3.eth.get_balance(checksum_addr)
125
- return (True, int(balance))
126
-
127
- token_checksum = to_checksum_address(token_address)
128
- contract = w3.eth.contract(address=token_checksum, abi=ERC20_MINIMAL_ABI)
129
- balance = await contract.functions.balanceOf(checksum_addr).call()
130
- return (True, int(balance))
131
-
132
- except Exception as exc: # noqa: BLE001
133
- self.logger.error(f"Failed to get balance: {exc}")
134
- return (False, f"Balance query failed: {exc}")
135
- finally:
136
- await self._close_web3(w3)
137
-
138
- async def approve_token(
139
- self,
140
- token_address: str,
141
- spender: str,
142
- amount: int,
143
- from_address: str,
144
- chain_id: int,
145
- wait_for_receipt: bool = True,
146
- timeout: int = DEFAULT_TRANSACTION_TIMEOUT,
147
- ) -> tuple[bool, Any]:
148
- """
149
- Approve a spender to spend tokens on behalf of from_address.
150
- """
151
- try:
152
- token_checksum = to_checksum_address(token_address)
153
- spender_checksum = to_checksum_address(spender)
154
- from_checksum = to_checksum_address(from_address)
155
- amount_int = int(amount)
156
-
157
- w3_sync = Web3()
158
- contract = w3_sync.eth.contract(
159
- address=token_checksum, abi=ERC20_APPROVAL_ABI
160
- )
161
- transaction_data = contract.encodeABI(
162
- fn_name="approve",
163
- args=[spender_checksum, amount_int],
164
- )
165
-
166
- approve_txn = {
167
- "from": from_checksum,
168
- "chainId": int(chain_id),
169
- "to": token_checksum,
170
- "data": transaction_data,
171
- "value": 0,
172
- "gas": ERC20_APPROVAL_GAS_LIMIT,
173
- }
174
-
175
- return await self.broadcast_transaction(
176
- approve_txn,
177
- wait_for_receipt=wait_for_receipt,
178
- timeout=timeout,
179
- )
180
- except Exception as exc: # noqa: BLE001
181
- self.logger.error(f"ERC20 approval failed: {exc}")
182
- return (False, f"ERC20 approval failed: {exc}")
183
-
184
105
  async def broadcast_transaction(
185
106
  self,
186
107
  transaction: dict[str, Any],
@@ -1,15 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from decimal import ROUND_DOWN, Decimal
3
4
  from typing import Any
4
5
 
5
6
  from eth_utils import to_checksum_address
6
7
  from loguru import logger
7
- from web3 import AsyncWeb3
8
+ from web3 import AsyncWeb3, Web3
8
9
 
9
10
  from wayfinder_paths.core.clients.TokenClient import TokenClient
10
- from wayfinder_paths.core.clients.TransactionClient import TransactionClient
11
11
  from wayfinder_paths.core.constants import ZERO_ADDRESS
12
- from wayfinder_paths.core.constants.erc20_abi import ERC20_APPROVAL_ABI
12
+ from wayfinder_paths.core.constants.erc20_abi import ERC20_ABI, ERC20_APPROVAL_ABI
13
13
  from wayfinder_paths.core.services.base import EvmTxn, TokenTxn
14
14
  from wayfinder_paths.core.utils.evm_helpers import resolve_chain_id
15
15
 
@@ -22,13 +22,12 @@ class LocalTokenTxnService(TokenTxn):
22
22
  config: dict[str, Any] | None,
23
23
  *,
24
24
  wallet_provider: EvmTxn,
25
- simulation: bool = False,
26
25
  ) -> None:
27
- del config, simulation
26
+ del config
28
27
  self.wallet_provider = wallet_provider
29
28
  self.logger = logger.bind(service="DefaultEvmTransactionService")
30
29
  self.token_client = TokenClient()
31
- self.builder = _EvmTransactionBuilder()
30
+ self.builder = _EvmTransactionBuilder(wallet_provider)
32
31
 
33
32
  async def build_send(
34
33
  self,
@@ -51,14 +50,24 @@ class LocalTokenTxnService(TokenTxn):
51
50
  return False, f"Token {token_id} is missing a chain id"
52
51
 
53
52
  token_address = (token_meta or {}).get("address") or ZERO_ADDRESS
53
+ is_native = not token_address or token_address.lower() == ZERO_ADDRESS.lower()
54
+
55
+ if is_native:
56
+ amount_wei = self._to_base_units(
57
+ amount, 18
58
+ ) # Native tokens use 18 decimals
59
+ else:
60
+ decimals = int((token_meta or {}).get("decimals") or 18)
61
+ amount_wei = self._to_base_units(amount, decimals)
54
62
 
55
63
  try:
56
64
  tx = await self.builder.build_send_transaction(
57
65
  from_address=from_address,
58
66
  to_address=to_address,
59
67
  token_address=token_address,
60
- amount=amount,
68
+ amount=amount_wei,
61
69
  chain_id=int(chain_id),
70
+ is_native=is_native,
62
71
  )
63
72
  except Exception as exc: # noqa: BLE001
64
73
  return False, f"Failed to build send transaction: {exc}"
@@ -127,12 +136,20 @@ class LocalTokenTxnService(TokenTxn):
127
136
  raise ValueError("Chain ID is required")
128
137
  return int(chain_id)
129
138
 
139
+ def _to_base_units(self, amount: float, decimals: int) -> int:
140
+ """Convert human-readable amount to base units (wei for native, token units for ERC20)."""
141
+ scale = Decimal(10) ** int(decimals)
142
+ quantized = (Decimal(str(amount)) * scale).to_integral_value(
143
+ rounding=ROUND_DOWN
144
+ )
145
+ return int(quantized)
146
+
130
147
 
131
148
  class _EvmTransactionBuilder:
132
149
  """Helpers that only build transaction dictionaries for sends and approvals."""
133
150
 
134
- def __init__(self) -> None:
135
- self.transaction_client = TransactionClient()
151
+ def __init__(self, wallet_provider: EvmTxn) -> None:
152
+ self.wallet_provider = wallet_provider
136
153
 
137
154
  async def build_send_transaction(
138
155
  self,
@@ -140,22 +157,37 @@ class _EvmTransactionBuilder:
140
157
  from_address: str,
141
158
  to_address: str,
142
159
  token_address: str | None,
143
- amount: float,
160
+ amount: int,
144
161
  chain_id: int,
162
+ is_native: bool,
145
163
  ) -> dict[str, Any]:
146
164
  """Build the transaction dict for sending native or ERC20 tokens."""
147
- 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
- )
165
+ from_checksum = to_checksum_address(from_address)
166
+ to_checksum = to_checksum_address(to_address)
167
+ chain_id_int = int(chain_id)
168
+
169
+ if is_native:
170
+ return {
171
+ "chainId": chain_id_int,
172
+ "from": from_checksum,
173
+ "to": to_checksum,
174
+ "value": int(amount),
175
+ }
176
+
177
+ token_checksum = to_checksum_address(token_address or ZERO_ADDRESS)
178
+ w3_sync = Web3()
179
+ contract = w3_sync.eth.contract(address=token_checksum, abi=ERC20_ABI)
180
+ data = contract.functions.transfer(
181
+ to_checksum, int(amount)
182
+ )._encode_transaction_data()
183
+
184
+ return {
185
+ "chainId": chain_id_int,
186
+ "from": from_checksum,
187
+ "to": token_checksum,
188
+ "data": data,
189
+ "value": 0,
190
+ }
159
191
 
160
192
  def build_erc20_approval_transaction(
161
193
  self,
@@ -173,10 +205,13 @@ class _EvmTransactionBuilder:
173
205
  from_checksum = to_checksum_address(from_address)
174
206
  amount_int = int(amount)
175
207
 
176
- 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
- )
208
+ # Use synchronous Web3 for encoding (encodeABI doesn't exist in web3.py v7)
209
+ w3_sync = Web3()
210
+ contract = w3_sync.eth.contract(address=token_checksum, abi=ERC20_APPROVAL_ABI)
211
+ # In web3.py v7, use _encode_transaction_data to encode without network calls
212
+ data = contract.functions.approve(
213
+ spender_checksum, amount_int
214
+ )._encode_transaction_data()
180
215
 
181
216
  return {
182
217
  "chainId": int(chain_id),
@@ -185,47 +220,3 @@ class _EvmTransactionBuilder:
185
220
  "data": data,
186
221
  "value": 0,
187
222
  }
188
-
189
- def _payload_to_tx(
190
- self, payload: dict[str, Any], from_address: str, is_native: bool
191
- ) -> dict[str, Any]:
192
- data_root = payload.get("data", payload)
193
- tx_src = data_root.get("transaction") or data_root
194
-
195
- chain_id = tx_src.get("chainId") or data_root.get("chain_id")
196
- if chain_id is None:
197
- raise ValueError("Transaction payload missing chainId")
198
-
199
- tx: dict[str, Any] = {"chainId": int(chain_id)}
200
- tx["from"] = to_checksum_address(from_address)
201
-
202
- if tx_src.get("to"):
203
- tx["to"] = to_checksum_address(tx_src["to"])
204
- if tx_src.get("data"):
205
- data = tx_src["data"]
206
- tx["data"] = data if str(data).startswith("0x") else f"0x{data}"
207
-
208
- val = tx_src.get("value", 0)
209
- tx["value"] = self._normalize_value(val) if is_native else 0
210
-
211
- if tx_src.get("gas"):
212
- tx["gas"] = int(tx_src["gas"])
213
- if tx_src.get("maxFeePerGas"):
214
- tx["maxFeePerGas"] = int(tx_src["maxFeePerGas"])
215
- if tx_src.get("maxPriorityFeePerGas"):
216
- tx["maxPriorityFeePerGas"] = int(tx_src["maxPriorityFeePerGas"])
217
- if tx_src.get("gasPrice"):
218
- tx["gasPrice"] = int(tx_src["gasPrice"])
219
- if tx_src.get("nonce") is not None:
220
- tx["nonce"] = int(tx_src["nonce"])
221
-
222
- return tx
223
-
224
- def _normalize_value(self, value: Any) -> int:
225
- if isinstance(value, str):
226
- if value.startswith("0x"):
227
- return int(value, 16)
228
- return int(float(value))
229
- if isinstance(value, (int, float)):
230
- return int(value)
231
- return 0
@@ -16,7 +16,6 @@ class DefaultWeb3Service(Web3Service):
16
16
  *,
17
17
  wallet_provider: EvmTxn | None = None,
18
18
  evm_transactions: TokenTxn | None = None,
19
- simulation: bool = False,
20
19
  ) -> None:
21
20
  """
22
21
  Initialize the service with optional dependency injection.
@@ -33,7 +32,6 @@ class DefaultWeb3Service(Web3Service):
33
32
  self._evm_transactions = LocalTokenTxnService(
34
33
  config=cfg,
35
34
  wallet_provider=self._wallet_provider,
36
- simulation=simulation,
37
35
  )
38
36
 
39
37
  @property
@@ -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: