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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) 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 +0 -51
  4. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
  5. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
  6. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
  7. wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
  8. wayfinder_paths/adapters/pool_adapter/README.md +1 -77
  9. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -122
  10. wayfinder_paths/adapters/pool_adapter/examples.json +0 -57
  11. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -86
  12. wayfinder_paths/adapters/token_adapter/README.md +1 -1
  13. wayfinder_paths/core/clients/AuthClient.py +0 -3
  14. wayfinder_paths/core/clients/ClientManager.py +1 -22
  15. wayfinder_paths/core/clients/WalletClient.py +0 -8
  16. wayfinder_paths/core/clients/WayfinderClient.py +9 -14
  17. wayfinder_paths/core/clients/__init__.py +0 -8
  18. wayfinder_paths/core/clients/protocols.py +0 -60
  19. wayfinder_paths/core/config.py +5 -45
  20. wayfinder_paths/core/engine/StrategyJob.py +0 -3
  21. wayfinder_paths/core/services/base.py +0 -49
  22. wayfinder_paths/core/services/local_evm_txn.py +3 -82
  23. wayfinder_paths/core/services/local_token_txn.py +61 -70
  24. wayfinder_paths/core/services/web3_service.py +0 -2
  25. wayfinder_paths/core/settings.py +8 -8
  26. wayfinder_paths/core/strategies/Strategy.py +1 -5
  27. wayfinder_paths/core/utils/evm_helpers.py +7 -12
  28. wayfinder_paths/core/wallets/README.md +3 -6
  29. wayfinder_paths/run_strategy.py +29 -32
  30. wayfinder_paths/scripts/make_wallets.py +1 -25
  31. wayfinder_paths/scripts/run_strategy.py +0 -2
  32. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
  33. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +86 -137
  34. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
  35. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
  36. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
  37. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +106 -28
  38. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
  39. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1 -6
  40. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -4
  41. wayfinder_paths/templates/strategy/test_strategy.py +0 -4
  42. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/METADATA +5 -15
  43. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/RECORD +45 -47
  44. wayfinder_paths/core/clients/SimulationClient.py +0 -192
  45. wayfinder_paths/core/clients/TransactionClient.py +0 -63
  46. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/LICENSE +0 -0
  47. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.9.dist-info}/WHEEL +0 -0
@@ -69,77 +69,6 @@ class TestPoolAdapter:
69
69
  assert success is True
70
70
  assert data == mock_response
71
71
 
72
- @pytest.mark.asyncio
73
- async def test_find_high_yield_pools_success(self, adapter, mock_pool_client):
74
- """Test successful high yield pool discovery"""
75
- mock_llama_response = {
76
- "matches": [
77
- {
78
- "pool_id": "pool-123",
79
- "llama_apy_pct": 5.2,
80
- "llama_tvl_usd": 1000000,
81
- "llama_stablecoin": True,
82
- "network": "base",
83
- },
84
- {
85
- "pool_id": "pool-456",
86
- "llama_apy_pct": 2.0,
87
- "llama_tvl_usd": 500000,
88
- "llama_stablecoin": True,
89
- "network": "ethereum",
90
- },
91
- {
92
- "pool_id": "pool-789",
93
- "llama_apy_pct": 6.0,
94
- "llama_tvl_usd": 2000000,
95
- "llama_stablecoin": False,
96
- "network": "base",
97
- },
98
- ]
99
- }
100
- mock_pool_client.get_llama_matches = AsyncMock(return_value=mock_llama_response)
101
-
102
- success, data = await adapter.find_high_yield_pools(
103
- min_apy=0.03, min_tvl=500000, stablecoin_only=True, network_codes=["base"]
104
- )
105
-
106
- assert success is True
107
- assert len(data["pools"]) == 1 # Only pool-123 meets criteria
108
- assert (
109
- data["pools"][0].get("pool_id") == "pool-123"
110
- or data["pools"][0].get("id") == "pool-123"
111
- )
112
- assert data["total_found"] == 1
113
- assert data["filters_applied"]["min_apy"] == 0.03
114
- assert data["filters_applied"]["stablecoin_only"] is True
115
-
116
- @pytest.mark.asyncio
117
- async def test_get_pool_analytics_success(self, adapter, mock_pool_client):
118
- """Test successful pool analytics generation"""
119
- mock_pool_data = {
120
- "pools": [
121
- {"id": "pool-123", "name": "USDC/USDT Pool", "symbol": "USDC-USDT"}
122
- ]
123
- }
124
- mock_pool_client.get_pools_by_ids = AsyncMock(return_value=mock_pool_data)
125
-
126
- mock_llama_data = {
127
- "pool-123": {
128
- "llama_apy_pct": 5.2,
129
- "llama_combined_apy_pct": 5.2,
130
- "llama_tvl_usd": 1000000,
131
- }
132
- }
133
- mock_pool_client.get_llama_reports = AsyncMock(return_value=mock_llama_data)
134
-
135
- success, data = await adapter.get_pool_analytics(["pool-123"])
136
-
137
- assert success is True
138
- assert len(data["analytics"]) == 1
139
- assert data["analytics"][0]["pool"]["id"] == "pool-123"
140
- assert round(data["analytics"][0]["combined_apy"], 6) == round(0.052, 6)
141
- assert data["analytics"][0]["tvl_usd"] == 1000000
142
-
143
72
  @pytest.mark.asyncio
144
73
  async def test_get_pools_by_ids_failure(self, adapter, mock_pool_client):
145
74
  """Test pool retrieval failure"""
@@ -152,21 +81,6 @@ class TestPoolAdapter:
152
81
  assert success is False
153
82
  assert "API Error" in data
154
83
 
155
- @pytest.mark.asyncio
156
- async def test_find_high_yield_pools_no_matches(self, adapter, mock_pool_client):
157
- """Test high yield pool discovery with no matches"""
158
- mock_llama_response = {"matches": []}
159
- mock_pool_client.get_llama_matches.return_value = mock_llama_response
160
-
161
- success, data = await adapter.find_high_yield_pools(
162
- min_apy=0.10,
163
- min_tvl=10000000,
164
- )
165
-
166
- assert success is True
167
- assert len(data["pools"]) == 0
168
- assert data["total_found"] == 0
169
-
170
84
  def test_adapter_type(self, adapter):
171
85
  """Test adapter has adapter_type"""
172
86
  assert adapter.adapter_type == "POOL"
@@ -12,7 +12,7 @@ The adapter uses the TokenClient which automatically handles authentication and
12
12
 
13
13
  The TokenClient will automatically:
14
14
  - Use the WAYFINDER_API_URL from settings
15
- - Handle authentication via environment variables or config.json
15
+ - Handle authentication via config.json
16
16
  - Manage token refresh and retry logic
17
17
 
18
18
  ## Usage
@@ -1,4 +1,3 @@
1
- import os
2
1
  from typing import Any
3
2
 
4
3
  from loguru import logger
@@ -30,8 +29,6 @@ class AuthClient(WayfinderClient):
30
29
  creds = self._load_config_credentials()
31
30
  if creds.get("api_key"):
32
31
  return True
33
- if os.getenv("WAYFINDER_API_KEY"):
34
- return True
35
32
  except Exception:
36
33
  pass
37
34
 
@@ -15,14 +15,10 @@ from wayfinder_paths.core.clients.protocols import (
15
15
  HyperlendClientProtocol,
16
16
  LedgerClientProtocol,
17
17
  PoolClientProtocol,
18
- SimulationClientProtocol,
19
18
  TokenClientProtocol,
20
- TransactionClientProtocol,
21
19
  WalletClientProtocol,
22
20
  )
23
- from wayfinder_paths.core.clients.SimulationClient import SimulationClient
24
21
  from wayfinder_paths.core.clients.TokenClient import TokenClient
25
- from wayfinder_paths.core.clients.TransactionClient import TransactionClient
26
22
  from wayfinder_paths.core.clients.WalletClient import WalletClient
27
23
 
28
24
 
@@ -32,7 +28,7 @@ class ClientManager:
32
28
 
33
29
  Args:
34
30
  clients: Optional dict of pre-instantiated clients to inject directly.
35
- Keys: 'token', 'hyperlend', 'ledger', 'wallet', 'transaction', 'pool', 'brap', 'simulation'.
31
+ Keys: 'token', 'hyperlend', 'ledger', 'wallet', 'transaction', 'pool', 'brap'.
36
32
  If not provided, defaults to HTTP-based clients.
37
33
  skip_auth: If True, skips authentication (for SDK usage).
38
34
  """
@@ -59,12 +55,10 @@ class ClientManager:
59
55
  self._auth_client: AuthClient | None = None
60
56
  self._token_client: TokenClientProtocol | None = None
61
57
  self._wallet_client: WalletClientProtocol | None = None
62
- self._transaction_client: TransactionClientProtocol | None = None
63
58
  self._ledger_client: LedgerClientProtocol | None = None
64
59
  self._pool_client: PoolClientProtocol | None = None
65
60
  self._hyperlend_client: HyperlendClientProtocol | None = None
66
61
  self._brap_client: BRAPClientProtocol | None = None
67
- self._simulation_client: SimulationClientProtocol | None = None
68
62
 
69
63
  def _get_or_create_client(
70
64
  self,
@@ -109,13 +103,6 @@ class ClientManager:
109
103
  """Get or create token client"""
110
104
  return self._get_or_create_client("_token_client", "token", TokenClient)
111
105
 
112
- @property
113
- def transaction(self) -> TransactionClientProtocol:
114
- """Get or create transaction client"""
115
- return self._get_or_create_client(
116
- "_transaction_client", "transaction", TransactionClient
117
- )
118
-
119
106
  @property
120
107
  def ledger(self) -> LedgerClientProtocol:
121
108
  """Get or create ledger client"""
@@ -143,13 +130,6 @@ class ClientManager:
143
130
  """Get or create BRAP client"""
144
131
  return self._get_or_create_client("_brap_client", "brap", BRAPClient)
145
132
 
146
- @property
147
- def simulation(self) -> SimulationClientProtocol:
148
- """Get or create simulation client"""
149
- return self._get_or_create_client(
150
- "_simulation_client", "simulation", SimulationClient
151
- )
152
-
153
133
  async def authenticate(
154
134
  self,
155
135
  username: str | None = None,
@@ -206,5 +186,4 @@ class ClientManager:
206
186
  "wallet": self._wallet_client,
207
187
  "hyperlend": self._hyperlend_client,
208
188
  "brap": self._brap_client,
209
- "simulation": self._simulation_client,
210
189
  }
@@ -33,14 +33,6 @@ class PoolBalance(TypedDict):
33
33
  usd_value: NotRequired[float | None]
34
34
 
35
35
 
36
- class EnrichedBalances(TypedDict):
37
- """Enriched token balances response structure"""
38
-
39
- wallet_address: Required[str]
40
- balances: Required[list[TokenBalance]]
41
- total_usd_value: NotRequired[float | None]
42
-
43
-
44
36
  class WalletClient(WayfinderClient):
45
37
  def __init__(self, api_key: str | None = None):
46
38
  super().__init__(api_key=api_key)
@@ -121,17 +121,17 @@ class WayfinderClient:
121
121
 
122
122
  async def _ensure_bearer_token(self) -> bool:
123
123
  """
124
- Ensure Authorization header is set. Priority: existing header > constructor api_key > config.json api_key > env api_key > config.json tokens > env tokens > username/password.
124
+ Ensure Authorization header is set. Priority: existing header > constructor api_key > config.json api_key > config.json tokens > username/password.
125
125
  Raises PermissionError if no credentials found.
126
126
  """
127
127
  if self.headers.get("Authorization"):
128
128
  return True
129
129
 
130
- # Check for API key: constructor > config.json > environment
130
+ # Check for API key: constructor > config.json
131
131
  api_key = self._api_key
132
132
  if not api_key:
133
133
  creds = self._load_config_credentials()
134
- api_key = creds.get("api_key") or os.getenv("WAYFINDER_API_KEY")
134
+ api_key = creds.get("api_key")
135
135
 
136
136
  if api_key:
137
137
  api_key = api_key.strip() if isinstance(api_key, str) else api_key
@@ -142,12 +142,7 @@ class WayfinderClient:
142
142
 
143
143
  # Fall back to OAuth token-based auth
144
144
  creds = self._load_config_credentials()
145
- access = os.getenv("WAYFINDER_ACCESS_TOKEN")
146
- refresh = creds.get("refresh_token") or os.getenv("WAYFINDER_REFRESH_TOKEN")
147
-
148
- if access:
149
- self.set_tokens(access, refresh)
150
- return True
145
+ refresh = creds.get("refresh_token")
151
146
 
152
147
  if refresh:
153
148
  self._refresh_token = refresh
@@ -155,8 +150,8 @@ class WayfinderClient:
155
150
  if refreshed:
156
151
  return True
157
152
 
158
- username = creds.get("username") or os.getenv("WAYFINDER_USERNAME")
159
- password = creds.get("password") or os.getenv("WAYFINDER_PASSWORD")
153
+ username = creds.get("username")
154
+ password = creds.get("password")
160
155
 
161
156
  if username and password:
162
157
  try:
@@ -178,7 +173,7 @@ class WayfinderClient:
178
173
  pass
179
174
 
180
175
  raise PermissionError(
181
- "Not authenticated: provide api_key (via constructor, config.json, or WAYFINDER_API_KEY env var) for service account auth, "
176
+ "Not authenticated: provide api_key (via constructor or config.json) for service account auth, "
182
177
  "or username+password/refresh_token in config.json for personal access"
183
178
  )
184
179
 
@@ -201,11 +196,11 @@ class WayfinderClient:
201
196
  # Ensure API key or bearer token is set in headers if available and not already set
202
197
  # This ensures API keys are passed to all endpoints (including public ones) for rate limiting
203
198
  if not self.headers.get("Authorization"):
204
- # Try to get API key from constructor, config, or env
199
+ # Try to get API key from constructor or config
205
200
  api_key = self._api_key
206
201
  if not api_key:
207
202
  creds = self._load_config_credentials()
208
- api_key = creds.get("api_key") or os.getenv("WAYFINDER_API_KEY")
203
+ api_key = creds.get("api_key")
209
204
 
210
205
  if api_key:
211
206
  api_key = api_key.strip() if isinstance(api_key, str) else api_key
@@ -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
 
@@ -242,49 +225,6 @@ class BRAPClientProtocol(Protocol):
242
225
  ...
243
226
 
244
227
 
245
- class SimulationClientProtocol(Protocol):
246
- """Protocol for blockchain transaction simulations"""
247
-
248
- async def simulate_send(
249
- self,
250
- from_address: str,
251
- to_address: str,
252
- token_address: str,
253
- amount: str,
254
- chain_id: int,
255
- initial_balances: dict[str, str],
256
- ) -> SimulationResult:
257
- """Simulate sending native ETH or ERC20 tokens"""
258
- ...
259
-
260
- async def simulate_approve(
261
- self,
262
- from_address: str,
263
- to_address: str,
264
- token_address: str,
265
- amount: str,
266
- chain_id: int,
267
- initial_balances: dict[str, str],
268
- clear_approval_first: bool = False,
269
- ) -> SimulationResult:
270
- """Simulate ERC20 token approval"""
271
- ...
272
-
273
- async def simulate_swap(
274
- self,
275
- from_token_address: str,
276
- to_token_address: str,
277
- from_chain_id: int,
278
- to_chain_id: int,
279
- amount: str,
280
- from_address: str,
281
- slippage: float,
282
- initial_balances: dict[str, str],
283
- ) -> SimulationResult:
284
- """Simulate token swap operation"""
285
- ...
286
-
287
-
288
228
  class HyperliquidExecutorProtocol(Protocol):
289
229
  """Protocol for Hyperliquid order execution operations."""
290
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],