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

@@ -147,8 +147,7 @@ class BRAPAdapter(BaseAdapter):
147
147
  )
148
148
 
149
149
  # Extract best quote from response
150
- quotes = data.get("quotes", {})
151
- best_quote = quotes.get("best_quote")
150
+ best_quote = data["best_route"]
152
151
 
153
152
  if not best_quote:
154
153
  return (False, "No quotes available")
@@ -81,12 +81,10 @@ class TestBRAPAdapter:
81
81
  async def test_get_best_quote_success(self, adapter, mock_brap_client):
82
82
  """Test successful best quote retrieval"""
83
83
  mock_response = {
84
- "quotes": {
85
- "best_quote": {
86
- "input_amount": "1000000000000000000",
87
- "output_amount": "995000000000000000",
88
- "total_fee": "8000000000000000",
89
- }
84
+ "best_route": {
85
+ "input_amount": "1000000000000000000",
86
+ "output_amount": "995000000000000000",
87
+ "total_fee": "8000000000000000",
90
88
  }
91
89
  }
92
90
  mock_brap_client.get_quote = AsyncMock(return_value=mock_response)
@@ -108,7 +106,7 @@ class TestBRAPAdapter:
108
106
  @pytest.mark.asyncio
109
107
  async def test_get_best_quote_no_quotes(self, adapter, mock_brap_client):
110
108
  """Test best quote retrieval when no quotes available"""
111
- mock_response = {"quotes": {}}
109
+ mock_response = {"best_route": None}
112
110
  mock_brap_client.get_quote = AsyncMock(return_value=mock_response)
113
111
 
114
112
  success, data = await adapter.get_best_quote(
@@ -159,12 +159,15 @@ class TestLedgerAdapter:
159
159
 
160
160
  # Test
161
161
  operation_data = SWAP(
162
+ adapter="TestAdapter",
162
163
  from_token_id="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
163
164
  to_token_id="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
164
165
  from_amount="1000000000000000000",
165
166
  to_amount="995000000000000000",
166
167
  from_amount_usd=1000.0,
167
168
  to_amount_usd=995.0,
169
+ transaction_hash="0x123abc",
170
+ transaction_chain_id=8453,
168
171
  )
169
172
 
170
173
  success, data = await adapter.record_operation(
@@ -15,6 +15,7 @@ A Wayfinder adapter that provides high-level operations for DeFi pool data and a
15
15
  The adapter uses the PoolClient which automatically handles authentication and API configuration through the Wayfinder settings. No additional configuration is required.
16
16
 
17
17
  The PoolClient will automatically:
18
+
18
19
  - Use the WAYFINDER_API_URL from settings
19
20
  - Handle authentication via environment variables or config.json
20
21
  - Manage token refresh and retry logic
@@ -44,17 +45,6 @@ else:
44
45
  print(f"Error: {data}")
45
46
  ```
46
47
 
47
- ### Get All Pools
48
-
49
- ```python
50
- success, data = await adapter.get_all_pools(merge_external=False)
51
- if success:
52
- pools = data.get("pools", [])
53
- print(f"Total pools available: {len(pools)}")
54
- else:
55
- print(f"Error: {data}")
56
- ```
57
-
58
48
  ### Find High Yield Pools
59
49
 
60
50
  ```python
@@ -90,22 +80,6 @@ else:
90
80
  print(f"Error: {data}")
91
81
  ```
92
82
 
93
- ### Search Pools
94
-
95
- ```python
96
- success, data = await adapter.search_pools(
97
- query="USDC",
98
- limit=10
99
- )
100
- if success:
101
- pools = data.get("pools", [])
102
- print(f"Found {len(pools)} pools matching 'USDC'")
103
- for pool in pools:
104
- print(f"Pool: {pool.get('name')} - {pool.get('symbol')}")
105
- else:
106
- print(f"Error: {data}")
107
- ```
108
-
109
83
  ### Get Llama Matches
110
84
 
111
85
  ```python
@@ -168,7 +142,7 @@ if success:
168
142
  for pool_analytics in analytics:
169
143
  pool = pool_analytics.get("pool", {})
170
144
  llama_data = pool_analytics.get("llama_data", {})
171
-
145
+
172
146
  print(f"Pool: {pool.get('name')}")
173
147
  print(f" Combined APY: {pool_analytics.get('combined_apy', 0):.2%}")
174
148
  print(f" TVL: ${pool_analytics.get('tvl_usd', 0):,.0f}")
@@ -189,6 +163,7 @@ The adapter uses the following Wayfinder API endpoints:
189
163
  ## Error Handling
190
164
 
191
165
  All methods return a tuple of `(success: bool, data: Any)` where:
166
+
192
167
  - `success` is `True` if the operation succeeded
193
168
  - `data` contains the response data on success or error message on failure
194
169
 
@@ -53,25 +53,6 @@ class PoolAdapter(BaseAdapter):
53
53
  self.logger.error(f"Error fetching pools by IDs: {e}")
54
54
  return (False, str(e))
55
55
 
56
- async def get_all_pools(
57
- self, merge_external: bool | None = None
58
- ) -> tuple[bool, PoolList | str]:
59
- """
60
- Get all available pools.
61
-
62
- Args:
63
- merge_external: Whether to merge external data
64
-
65
- Returns:
66
- Tuple of (success, data) where data is all pools or error message
67
- """
68
- try:
69
- data = await self.pool_client.get_all_pools(merge_external=merge_external)
70
- return (True, data)
71
- except Exception as e:
72
- self.logger.error(f"Error fetching all pools: {e}")
73
- return (False, str(e))
74
-
75
56
  async def get_llama_matches(self) -> tuple[bool, dict[str, LlamaMatch] | str]:
76
57
  """
77
58
  Get Llama protocol matches for pools.
@@ -227,56 +208,3 @@ class PoolAdapter(BaseAdapter):
227
208
  except Exception as e:
228
209
  self.logger.error(f"Error getting pool analytics: {e}")
229
210
  return (False, str(e))
230
-
231
- async def search_pools(self, query: str, limit: int = 10) -> tuple[bool, Any]:
232
- """
233
- Search pools by name, symbol, or other criteria.
234
-
235
- Args:
236
- query: Search query string
237
- limit: Maximum number of results
238
-
239
- Returns:
240
- Tuple of (success, data) where data is search results or error message
241
- """
242
- try:
243
- success, all_pools_data = await self.get_all_pools()
244
- if not success:
245
- return (False, f"Failed to fetch pools: {all_pools_data}")
246
-
247
- pools = all_pools_data.get("pools", [])
248
- query_lower = query.lower()
249
-
250
- # Simple text search
251
- matching_pools = []
252
- for pool in pools:
253
- name = pool.get("name", "").lower()
254
- symbol = pool.get("symbol", "").lower()
255
- description = pool.get("description", "").lower()
256
-
257
- if (
258
- query_lower in name
259
- or query_lower in symbol
260
- or query_lower in description
261
- ):
262
- matching_pools.append(pool)
263
-
264
- # Sort by relevance (exact matches first)
265
- matching_pools.sort(
266
- key=lambda x: (
267
- query_lower not in x.get("name", "").lower(),
268
- query_lower not in x.get("symbol", "").lower(),
269
- )
270
- )
271
-
272
- return (
273
- True,
274
- {
275
- "pools": matching_pools[:limit],
276
- "total_found": len(matching_pools),
277
- "query": query,
278
- },
279
- )
280
- except Exception as e:
281
- self.logger.error(f"Error searching pools: {e}")
282
- return (False, str(e))
@@ -21,27 +21,6 @@
21
21
  }
22
22
  }
23
23
  },
24
- "get_all_pools": {
25
- "description": "Get all available pools",
26
- "input": {
27
- "merge_external": false
28
- },
29
- "output": {
30
- "success": true,
31
- "data": {
32
- "pools": [
33
- {
34
- "id": "pool-123",
35
- "name": "USDC/USDT Pool",
36
- "symbol": "USDC-USDT",
37
- "apy": 0.05,
38
- "tvl": 1000000
39
- }
40
- ],
41
- "total": 1
42
- }
43
- }
44
- },
45
24
  "get_llama_matches": {
46
25
  "description": "Get Llama protocol matches for pools",
47
26
  "input": {},
@@ -117,27 +96,5 @@
117
96
  "total_pools": 1
118
97
  }
119
98
  }
120
- },
121
- "search_pools": {
122
- "description": "Search pools by name, symbol, or other criteria",
123
- "input": {
124
- "query": "USDC",
125
- "limit": 5
126
- },
127
- "output": {
128
- "success": true,
129
- "data": {
130
- "pools": [
131
- {
132
- "id": "pool-123",
133
- "name": "USDC/USDT Pool",
134
- "symbol": "USDC-USDT",
135
- "description": "Stablecoin pool on Base"
136
- }
137
- ],
138
- "total_found": 1,
139
- "query": "USDC"
140
- }
141
- }
142
99
  }
143
100
  }
@@ -47,25 +47,6 @@ class TestPoolAdapter:
47
47
  pool_ids="pool-123,pool-456", merge_external=True
48
48
  )
49
49
 
50
- @pytest.mark.asyncio
51
- async def test_get_all_pools_success(self, adapter, mock_pool_client):
52
- """Test successful retrieval of all pools"""
53
- # Mock response
54
- mock_response = {
55
- "pools": [
56
- {"id": "pool-123", "name": "Pool 1"},
57
- {"id": "pool-456", "name": "Pool 2"},
58
- ],
59
- "total": 2,
60
- }
61
- mock_pool_client.get_all_pools = AsyncMock(return_value=mock_response)
62
-
63
- success, data = await adapter.get_all_pools(merge_external=False)
64
-
65
- assert success is True
66
- assert data == mock_response
67
- mock_pool_client.get_all_pools.assert_called_once_with(merge_external=False)
68
-
69
50
  @pytest.mark.asyncio
70
51
  async def test_get_llama_matches_success(self, adapter, mock_pool_client):
71
52
  """Test successful Llama matches retrieval"""
@@ -159,35 +140,6 @@ class TestPoolAdapter:
159
140
  assert round(data["analytics"][0]["combined_apy"], 6) == round(0.052, 6)
160
141
  assert data["analytics"][0]["tvl_usd"] == 1000000
161
142
 
162
- @pytest.mark.asyncio
163
- async def test_search_pools_success(self, adapter, mock_pool_client):
164
- """Test successful pool search"""
165
- mock_all_pools = {
166
- "pools": [
167
- {
168
- "id": "pool-123",
169
- "name": "USDC/USDT Pool",
170
- "symbol": "USDC-USDT",
171
- "description": "Stablecoin pool on Base",
172
- },
173
- {
174
- "id": "pool-456",
175
- "name": "ETH/WETH Pool",
176
- "symbol": "ETH-WETH",
177
- "description": "Ethereum pool",
178
- },
179
- ]
180
- }
181
- mock_pool_client.get_all_pools = AsyncMock(return_value=mock_all_pools)
182
-
183
- success, data = await adapter.search_pools("USDC", limit=5)
184
-
185
- assert success is True
186
- assert len(data["pools"]) == 1
187
- assert data["pools"][0]["id"] == "pool-123"
188
- assert data["total_found"] == 1
189
- assert data["query"] == "USDC"
190
-
191
143
  @pytest.mark.asyncio
192
144
  async def test_get_pools_by_ids_failure(self, adapter, mock_pool_client):
193
145
  """Test pool retrieval failure"""
@@ -27,6 +27,7 @@ class BRAPQuote(TypedDict):
27
27
  amount1: Required[str]
28
28
  amount2: NotRequired[str]
29
29
  routes: NotRequired[list[dict[str, Any]]]
30
+ best_route: NotRequired[dict[str, Any]]
30
31
  fees: NotRequired[dict[str, Any] | None]
31
32
  slippage: NotRequired[float | None]
32
33
  wayfinder_fee: NotRequired[float | None]
@@ -85,22 +85,6 @@ class PoolClient(WayfinderClient):
85
85
  data = response.json()
86
86
  return data.get("data", data)
87
87
 
88
- async def get_all_pools(self, *, merge_external: bool | None = None) -> PoolList:
89
- """
90
- Fetch all pools.
91
-
92
- Example:
93
- GET /api/v1/public/pools/?merge_external=false
94
- """
95
- url = f"{self.api_base_url}/public/pools/"
96
- params: dict[str, Any] = {}
97
- if merge_external is not None:
98
- params["merge_external"] = "true" if merge_external else "false"
99
- response = await self._request("GET", url, params=params, headers={})
100
- response.raise_for_status()
101
- data = response.json()
102
- return data.get("data", data)
103
-
104
88
  async def get_llama_matches(self) -> dict[str, LlamaMatch]:
105
89
  """
106
90
  Fetch Llama matches for pools.
@@ -213,10 +213,6 @@ class PoolClientProtocol(Protocol):
213
213
  """Fetch pools by comma-separated pool ids"""
214
214
  ...
215
215
 
216
- async def get_all_pools(self, *, merge_external: bool | None = None) -> PoolList:
217
- """Fetch all pools"""
218
- ...
219
-
220
216
  async def get_llama_matches(self) -> dict[str, LlamaMatch]:
221
217
  """Fetch Llama matches for pools"""
222
218
  ...
@@ -65,6 +65,7 @@ def strategy():
65
65
  if hasattr(s, "token_adapter") and s.token_adapter:
66
66
  default_usdc = {
67
67
  "id": "usd-coin-base",
68
+ "token_id": "usd-coin-base",
68
69
  "symbol": "USDC",
69
70
  "name": "USD Coin",
70
71
  "decimals": 6,
@@ -74,6 +75,7 @@ def strategy():
74
75
 
75
76
  default_pool_token = {
76
77
  "id": "test-pool-base",
78
+ "token_id": "test-pool-base",
77
79
  "symbol": "POOL",
78
80
  "name": "Test Pool",
79
81
  "decimals": 18,
@@ -97,6 +99,7 @@ def strategy():
97
99
  True,
98
100
  {
99
101
  "id": "ethereum-base",
102
+ "token_id": "ethereum-base",
100
103
  "symbol": "ETH",
101
104
  "name": "Ethereum",
102
105
  "decimals": 18,
@@ -207,6 +210,7 @@ def strategy():
207
210
  s.DEPOSIT_USDC = 0
208
211
  s.usdc_token_info = {
209
212
  "id": "usd-coin-base",
213
+ "token_id": "usd-coin-base",
210
214
  "symbol": "USDC",
211
215
  "name": "USD Coin",
212
216
  "decimals": 6,
@@ -215,6 +219,7 @@ def strategy():
215
219
  }
216
220
  s.gas_token = {
217
221
  "id": "ethereum-base",
222
+ "token_id": "ethereum-base",
218
223
  "symbol": "ETH",
219
224
  "name": "Ethereum",
220
225
  "decimals": 18,
@@ -223,6 +228,7 @@ def strategy():
223
228
  }
224
229
  s.current_pool = {
225
230
  "id": "usd-coin-base",
231
+ "token_id": "usd-coin-base",
226
232
  "symbol": "USDC",
227
233
  "decimals": 6,
228
234
  "chain": {"code": "base", "id": 8453, "name": "Base"},
@@ -245,9 +251,6 @@ def strategy():
245
251
  side_effect=get_token_price_side_effect
246
252
  )
247
253
 
248
- async def mock_sweep_wallet(target_token):
249
- pass
250
-
251
254
  async def mock_refresh_current_pool_balance():
252
255
  pass
253
256
 
@@ -257,7 +260,6 @@ def strategy():
257
260
  async def mock_has_idle_assets(balances, target):
258
261
  return True
259
262
 
260
- s._sweep_wallet = mock_sweep_wallet
261
263
  s._refresh_current_pool_balance = mock_refresh_current_pool_balance
262
264
  s._rebalance_gas = mock_rebalance_gas
263
265
  s._has_idle_assets = mock_has_idle_assets
@@ -432,17 +434,34 @@ async def test_deposit_tracks_usdc(strategy):
432
434
  @pytest.mark.asyncio
433
435
  async def test_sweep_wallet_uses_tracked_tokens(strategy):
434
436
  """Test that _sweep_wallet only swaps tracked tokens."""
437
+ # Import the real implementation to restore it
438
+ from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import (
439
+ StablecoinYieldStrategy,
440
+ )
441
+
442
+ # Restore the real _sweep_wallet method (fixture mocks it as a no-op)
443
+ strategy._sweep_wallet = StablecoinYieldStrategy._sweep_wallet.__get__(
444
+ strategy, StablecoinYieldStrategy
445
+ )
446
+
435
447
  # Setup: track some tokens with balances
436
448
  strategy._track_token("token-1", 1000000)
437
449
  strategy._track_token("token-2", 2000000)
438
450
 
451
+ # Track the actual token IDs to avoid issues with gas token
452
+ # Make sure we're not accidentally matching gas token
453
+ assert "token-1" in strategy.tracked_token_ids
454
+ assert "token-2" in strategy.tracked_token_ids
455
+
439
456
  # Mock balance adapter to return fresh balances
440
- strategy.balance_adapter.get_balance = AsyncMock(
441
- side_effect=lambda token_id, **kwargs: (
442
- True,
443
- strategy.tracked_balances.get(token_id, 0),
444
- )
445
- )
457
+ async def get_balance_mock(token_id, **kwargs):
458
+ balance = strategy.tracked_balances.get(token_id, 0)
459
+ # Return the balance, ensuring it's an int
460
+ return (True, int(balance) if balance else 0)
461
+
462
+ # Create a new AsyncMock that will track calls
463
+ new_mock = AsyncMock(side_effect=get_balance_mock)
464
+ strategy.balance_adapter.get_balance = new_mock
446
465
 
447
466
  # Mock brap adapter swap
448
467
  strategy.brap_adapter.swap_from_token_ids = AsyncMock(
@@ -451,15 +470,23 @@ async def test_sweep_wallet_uses_tracked_tokens(strategy):
451
470
 
452
471
  target_token = {
453
472
  "token_id": "usd-coin-base",
454
- "address": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
473
+ "address": "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",
455
474
  "chain": {"code": "base", "name": "Base"},
456
475
  }
457
476
 
458
477
  # Call sweep
459
478
  await strategy._sweep_wallet(target_token)
460
479
 
461
- # Verify that swap was called for tracked tokens
462
- assert strategy.brap_adapter.swap_from_token_ids.called
480
+ # Verify that swap was called for tracked tokens (should be called twice, once for each token)
481
+ # If this fails, check: balance_adapter.get_balance was called, tracked_balances has values,
482
+ # and tokens pass the gas/target token checks
483
+ assert strategy.brap_adapter.swap_from_token_ids.call_count >= 1, (
484
+ f"Expected at least 1 swap call, got {strategy.brap_adapter.swap_from_token_ids.call_count}. "
485
+ f"Tracked tokens: {strategy.tracked_token_ids}, "
486
+ f"Tracked balances: {strategy.tracked_balances}, "
487
+ f"Get balance calls: {new_mock.call_count}, "
488
+ f"balance_adapter mock is: {id(strategy.balance_adapter.get_balance)}, new_mock is: {id(new_mock)}"
489
+ )
463
490
 
464
491
 
465
492
  @pytest.mark.asyncio
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: wayfinder-paths
3
- Version: 0.1.7
3
+ Version: 0.1.8
4
4
  Summary: Wayfinder Path: strategies and adapters
5
5
  Author: Wayfinder
6
6
  Author-email: dev@wayfinder.ai
@@ -125,6 +125,7 @@ We welcome contributions! This is an open-source project where community members
125
125
  ### Contributor Guidelines
126
126
 
127
127
  #### For Adapters
128
+
128
129
  - **Start from the template**: Copy `wayfinder_paths/templates/adapter/` as a starting point
129
130
  - Extend `BaseAdapter` from `wayfinder_paths/core/adapters/BaseAdapter.py`
130
131
  - Create a `manifest.yaml` (template at `wayfinder_paths/templates/adapter/manifest.yaml`) with:
@@ -138,6 +139,7 @@ We welcome contributions! This is an open-source project where community members
138
139
  - Validate your manifest: `just validate-manifests`
139
140
 
140
141
  #### For Strategies
142
+
141
143
  - **Start from the template**: Use `just create-strategy "Strategy Name"` to create a new strategy with its own wallet, or copy `wayfinder_paths/templates/strategy/` manually
142
144
  - Extend `Strategy` from `wayfinder_paths/core/strategies/Strategy.py`
143
145
  - Create a `manifest.yaml` (template at `wayfinder_paths/templates/strategy/manifest.yaml`) with:
@@ -151,6 +153,7 @@ We welcome contributions! This is an open-source project where community members
151
153
  - Validate your manifest: `just validate-manifests`
152
154
 
153
155
  #### General Guidelines
156
+
154
157
  - **Code Quality**: Follow existing patterns and use type hints
155
158
  - **Testing**: See [TESTING.md](TESTING.md) - minimum: smoke test for strategies, basic tests for adapters
156
159
  - **Documentation**: Update README files and add docstrings
@@ -205,6 +208,7 @@ poetry run python wayfinder_paths/run_strategy.py your_strategy --action status
205
208
  ## 🏗️ Architecture
206
209
 
207
210
  ### Client System
211
+
208
212
  The platform uses a unified client system for all API interactions. Clients are thin wrappers that handle low-level API calls, authentication, and network communication. **Strategies should not call clients directly** - use adapters instead for domain-specific operations.
209
213
 
210
214
  ### Clients vs Adapters
@@ -231,6 +235,7 @@ Adapter manifests declare the capabilities an adapter provides and the clients i
231
235
  **Template:** Copy `wayfinder_paths/templates/adapter/manifest.yaml` as a starting point.
232
236
 
233
237
  **Schema:**
238
+
234
239
  ```yaml
235
240
  schema_version: "0.1"
236
241
  entrypoint: "adapters.my_adapter.adapter.MyAdapter"
@@ -243,12 +248,14 @@ dependencies:
243
248
  ```
244
249
 
245
250
  **Fields:**
251
+
246
252
  - `schema_version`: Manifest schema version (currently `"0.1"`)
247
253
  - `entrypoint`: Full Python import path to the adapter class (required)
248
254
  - `capabilities`: List of abstract capabilities this adapter provides (required, non-empty)
249
255
  - `dependencies`: List of client class names from `core.clients` that this adapter requires (required, non-empty)
250
256
 
251
257
  **Example** (`wayfinder_paths/adapters/pool_adapter/manifest.yaml`):
258
+
252
259
  ```yaml
253
260
  schema_version: "0.1"
254
261
  entrypoint: "adapters.pool_adapter.adapter.PoolAdapter"
@@ -269,6 +276,7 @@ Strategy manifests declare permissions and required adapters with their capabili
269
276
  **Template:** Copy `wayfinder_paths/templates/strategy/manifest.yaml` as a starting point.
270
277
 
271
278
  **Schema:**
279
+
272
280
  ```yaml
273
281
  schema_version: "0.1"
274
282
  entrypoint: "strategies.my_strategy.strategy.MyStrategy"
@@ -282,6 +290,7 @@ adapters:
282
290
  ```
283
291
 
284
292
  **Fields:**
293
+
285
294
  - `schema_version`: Manifest schema version (currently `"0.1"`)
286
295
  - `entrypoint`: Full Python import path to the strategy class (required)
287
296
  - `name`: Strategy directory name (optional, used for wallet lookup - defaults to directory name)
@@ -291,6 +300,7 @@ adapters:
291
300
  - `capabilities`: List of capabilities required from this adapter
292
301
 
293
302
  **Example** (`wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml`):
303
+
294
304
  ```yaml
295
305
  schema_version: "0.1"
296
306
  entrypoint: "strategies.stablecoin_yield_strategy.strategy.StablecoinYieldStrategy"
@@ -308,12 +318,14 @@ adapters:
308
318
  #### Manifest Validation
309
319
 
310
320
  Manifests are automatically validated to ensure:
321
+
311
322
  - Schema compliance (all required fields present, correct types)
312
323
  - Entrypoint classes exist and are importable
313
324
  - Dependencies are valid client classes
314
325
  - Permissions policies are non-empty
315
326
 
316
327
  **Validate locally:**
328
+
317
329
  ```bash
318
330
  # Validate all manifests
319
331
  just validate-manifests
@@ -348,6 +360,7 @@ The `validate_manifests.py` script performs multi-stage validation:
348
360
  - Policy syntax is not parsed/validated (assumed to be valid at runtime)
349
361
 
350
362
  **Validation Flow:**
363
+
351
364
  ```
352
365
  For each manifest file:
353
366
  1. Load YAML → Parse with Pydantic (schema validation)
@@ -357,6 +370,7 @@ For each manifest file:
357
370
  ```
358
371
 
359
372
  The script automatically discovers all manifests by scanning:
373
+
360
374
  - `wayfinder_paths/adapters/*/manifest.yaml` for adapter manifests
361
375
  - `wayfinder_paths/strategies/*/manifest.yaml` for strategy manifests
362
376
 
@@ -367,12 +381,15 @@ All errors are collected and reported at the end, with the script exiting with c
367
381
  Capabilities are abstract operation identifiers (e.g., `"pool.read"`, `"swap.execute"`) declared in manifests. They represent what operations an adapter can perform, not specific method names. The manifest is the **single source of truth** for capabilities—they are not duplicated in code.
368
382
 
369
383
  When creating an adapter:
384
+
370
385
  1. Declare capabilities in your `manifest.yaml`
371
386
  2. Implement methods that fulfill those capabilities
372
387
  3. Capabilities are validated at manifest validation time (entrypoint must be importable)
373
388
 
374
389
  ### Configuration
390
+
375
391
  Configuration is split between:
392
+
376
393
  - **User Config**: Your credentials and preferences
377
394
  - **System Config**: Platform settings
378
395
  - **Strategy Config**: Strategy-specific parameters
@@ -384,9 +401,11 @@ See [CONFIG_GUIDE.md](wayfinder_paths/CONFIG_GUIDE.md) for details.
384
401
  Wayfinder Paths supports two authentication methods:
385
402
 
386
403
  #### 1. Service Account Authentication (API Key)
404
+
387
405
  For backend services and automated systems with higher rate limits:
388
406
 
389
407
  **Option A: Pass to Strategy Constructor**
408
+
390
409
  ```python
391
410
  from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import StablecoinYieldStrategy
392
411
 
@@ -397,19 +416,21 @@ strategy = StablecoinYieldStrategy(
397
416
  ```
398
417
 
399
418
  **Option B: Set Environment Variable**
419
+
400
420
  ```bash
401
421
  export WAYFINDER_API_KEY="sk_live_abc123..."
402
422
  # All clients will automatically discover and use this
403
423
  ```
404
424
 
405
425
  **Option C: Add to config.json**
426
+
406
427
  ```json
407
428
  {
408
429
  "user": {
409
430
  "api_key": "sk_live_abc123..."
410
431
  },
411
432
  "system": {
412
- "api_key": "sk_live_abc123..." // Alternative: system-level API key
433
+ "api_key": "sk_live_abc123..." // Alternative: system-level API key
413
434
  }
414
435
  }
415
436
  ```
@@ -419,6 +440,7 @@ export WAYFINDER_API_KEY="sk_live_abc123..."
419
440
  **Note:** API keys in `config.json` are loaded directly by `WayfinderClient` via `_load_config_credentials()`, not through the `UserConfig` or `SystemConfig` dataclasses. This allows flexible credential loading.
420
441
 
421
442
  #### 2. Personal Access Authentication (OAuth)
443
+
422
444
  For standalone SDK users with username/password:
423
445
 
424
446
  ```json
@@ -426,12 +448,13 @@ For standalone SDK users with username/password:
426
448
  "user": {
427
449
  "username": "your_username",
428
450
  "password": "your_password",
429
- "refresh_token": null // Optional: use refresh token instead
451
+ "refresh_token": null // Optional: use refresh token instead
430
452
  }
431
453
  }
432
454
  ```
433
455
 
434
456
  **How It Works:**
457
+
435
458
  - API keys are automatically discovered by all clients (no need to pass explicitly)
436
459
  - When an API key is available, it's used for all API requests (including public endpoints) for rate limiting
437
460
  - If no API key is found, the system falls back to OAuth authentication
@@ -614,6 +637,7 @@ just create-wallets
614
637
  ```
615
638
 
616
639
  This creates:
640
+
617
641
  - `main` wallet - your main wallet for testing (labeled "main" in wallets.json)
618
642
  - `wallets.json` - wallet addresses and private keys for local testing
619
643
 
@@ -689,12 +713,14 @@ This package follows [Semantic Versioning](https://semver.org/) (SemVer) and is
689
713
  3. **Dependent changes**: Only after publishing can dependent changes be merged in other applications
690
714
 
691
715
  **Why this order matters:**
716
+
692
717
  - Other applications depend on this package from PyPI
693
718
  - They cannot merge changes that depend on new versions until those versions are available on PyPI
694
719
  - Publishing from `main` ensures the published version matches what's in the repository
695
720
  - This prevents dependency resolution failures in downstream applications
696
721
 
697
722
  **Example workflow:**
723
+
698
724
  ```bash
699
725
  # 1. Make changes in a feature branch
700
726
  git checkout -b feature/new-adapter
@@ -727,6 +753,7 @@ just publish
727
753
  ```
728
754
 
729
755
  **Important:**
756
+
730
757
  - ⚠️ **Publishing is only allowed from the `main` branch** - the publish command will fail if run from any other branch
731
758
  - ⚠️ **Versions must be unique** - ensure the version in `pyproject.toml` has been bumped and is unique
732
759
  - ⚠️ **Follow the order of operations** - see [Versioning](#-versioning) section above for the required workflow
@@ -749,6 +776,7 @@ pip install git+https://github.com/wayfinder-ai/wayfinder-paths.git
749
776
  ### Managing Package Access
750
777
 
751
778
  To add collaborators who can publish updates:
779
+
752
780
  1. Go to https://pypi.org/project/wayfinder-paths/
753
781
  2. Click "Manage" → "Collaborators"
754
782
  3. Add users as "Maintainers" (can publish) or "Owners" (full control)
@@ -9,10 +9,10 @@ wayfinder_paths/adapters/balance_adapter/manifest.yaml,sha256=vp2VoQJf-TxFxgkTsU
9
9
  wayfinder_paths/adapters/balance_adapter/test_adapter.py,sha256=Z8iTRU0Rv1UsODuVSo5q4j-DrTXMd4YRxvaxLdZE4Us,1878
10
10
  wayfinder_paths/adapters/brap_adapter/README.md,sha256=euWkSBR6OkYtebhvdNR_PL64sKbzKD5bg5hrYTIWZ1c,7905
11
11
  wayfinder_paths/adapters/brap_adapter/__init__.py,sha256=jpqxZ-Bv_8kBo-lhgO_QCWaVZNq_WwlkNBHD4RsqOJg,90
12
- wayfinder_paths/adapters/brap_adapter/adapter.py,sha256=ndjT7Ayg2_3yJI2gMSNdJZcAxjyZsgEOVO11aXfui5U,26878
12
+ wayfinder_paths/adapters/brap_adapter/adapter.py,sha256=OCXlNxIR7vQUktaoEeM55VcFOXR-PucNJW1NpBkNEUs,26828
13
13
  wayfinder_paths/adapters/brap_adapter/examples.json,sha256=KWuAklUspd2uvk0s2ey8gczg4nbzhdwxQqzhascyMiQ,5287
14
14
  wayfinder_paths/adapters/brap_adapter/manifest.yaml,sha256=bJ8o4j9ZPjfnLxXxHfekoXKUHoBkXmWQ3nokTH1aya4,240
15
- wayfinder_paths/adapters/brap_adapter/test_adapter.py,sha256=1IYcmT_bJcOSRDL782AXniCHYHMUJ_PgCd5U66Hfv2g,10806
15
+ wayfinder_paths/adapters/brap_adapter/test_adapter.py,sha256=w2q35tcE7j2QG53jSm_XZgIk7OKL4O51fnFuGMVRSNQ,10754
16
16
  wayfinder_paths/adapters/hyperlend_adapter/__init__.py,sha256=DsWOnEn-Tlu9ZoIoGaFeSqOYI3b4lXGVK3_FTntWpLw,139
17
17
  wayfinder_paths/adapters/hyperlend_adapter/adapter.py,sha256=QevMiOrztvTRHx7vA_dAQGX3ioUFdLY4aVOfsT-DXX8,10555
18
18
  wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml,sha256=Ugc0jNf3txAQRGAXlVvTN3Mbdc4-fUMS1yVs0SZcBwI,259
@@ -31,13 +31,13 @@ wayfinder_paths/adapters/ledger_adapter/__init__.py,sha256=DK9GShIUiQ57YKSqhCKoS
31
31
  wayfinder_paths/adapters/ledger_adapter/adapter.py,sha256=6Fjxltvn9iXp_-CZtN7lDz1Xt0lWaNQX2drx6lgeryw,10260
32
32
  wayfinder_paths/adapters/ledger_adapter/examples.json,sha256=DdqTSe4vnBrfIycQVQQ_JZom7fBGHbL7MR4ppK9ljCY,3936
33
33
  wayfinder_paths/adapters/ledger_adapter/manifest.yaml,sha256=121VPXNpx13vO9qoBww47Wvpi29JLn5WoIFnudCkDYs,271
34
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py,sha256=WFDHJx4dNKCLUDEClewZmGH7V76GoeQY5E0ObXNe2kk,7355
35
- wayfinder_paths/adapters/pool_adapter/README.md,sha256=rF7KYEtxu6BmDa9gi505-IFUddk6BDaMOQPB4YjeOLc,5863
34
+ wayfinder_paths/adapters/ledger_adapter/test_adapter.py,sha256=Z1-rPP9k5fI-8ofWMKgU3syzNegKGH_hGO6CKApQj1c,7470
35
+ wayfinder_paths/adapters/pool_adapter/README.md,sha256=ttfG4aP_Y0Bl7DXxPlrAhrubOs9Yu0mtki3h7JqWvxQ,5293
36
36
  wayfinder_paths/adapters/pool_adapter/__init__.py,sha256=rv56pYzz2Gqiz33uoPJktCQRe3CRt8U9ry5GbjVgK3A,90
37
- wayfinder_paths/adapters/pool_adapter/adapter.py,sha256=jXGq5F-xHXF42JN5R0Hd6LEGDds1qAiwOCGQ4etzuPE,9642
38
- wayfinder_paths/adapters/pool_adapter/examples.json,sha256=hLH74Oy6WlrEvAIOjwqsjpcCDxC-N0efWeLa_-TbntM,3202
37
+ wayfinder_paths/adapters/pool_adapter/adapter.py,sha256=D3J9Bx7urFZjuqfmexg-wDjngKfaeeXi5aNgQXQ2W_Y,7249
38
+ wayfinder_paths/adapters/pool_adapter/examples.json,sha256=FS0cssPu2KB05MmzG4Hc0Ka0DzOn_0nogqfHBGLJns8,2295
39
39
  wayfinder_paths/adapters/pool_adapter/manifest.yaml,sha256=z-OQYBsl2RdV6M34RZzqtQTAFHtQod0po_JD_-9ElNM,217
40
- wayfinder_paths/adapters/pool_adapter/test_adapter.py,sha256=4RcaCixHuXT75OfwZEhNoexLUdvTDGERJiI9XW7plIA,7653
40
+ wayfinder_paths/adapters/pool_adapter/test_adapter.py,sha256=AYk4YWf0H3FXNBc1NKVmXuLQvE7dpMU-QrCp0XWQYFw,5954
41
41
  wayfinder_paths/adapters/token_adapter/README.md,sha256=d2tMJte6HBu62CCYXdjS8GHZXj5f2fU03uZAO6pscBI,2698
42
42
  wayfinder_paths/adapters/token_adapter/__init__.py,sha256=nEmxrvffEygn3iKH3cZTNLkhnUUhlUAEtshmrFRAjq8,62
43
43
  wayfinder_paths/adapters/token_adapter/adapter.py,sha256=JEb7A8wJYHxENFhJ6upAgnQAbPZeVfYi6OGs1hiHxnA,3432
@@ -56,18 +56,18 @@ wayfinder_paths/core/analytics/bootstrap.py,sha256=lb_PjL4Vh3O2F8eXgvAbnAFevJczR
56
56
  wayfinder_paths/core/analytics/stats.py,sha256=qE6h0j8TZAbqbVpDeYlVKe0YbV5CENQcHbREzKyZ_s8,1426
57
57
  wayfinder_paths/core/analytics/test_analytics.py,sha256=DNkVTsbWPLc9I1eeCD5wsPPqUDgN-npbGRhBgMKn3GM,5580
58
58
  wayfinder_paths/core/clients/AuthClient.py,sha256=scz8GvnabNYAQq_XYDcLP2lf2LZqurQOixA7MMAfbCY,2796
59
- wayfinder_paths/core/clients/BRAPClient.py,sha256=AtTYNk1FuCS59xj5FFgv2fIts44BQK19Kv0IeXlcPtw,3700
59
+ wayfinder_paths/core/clients/BRAPClient.py,sha256=-cL05ELlroi3pUfT_5nF8Axie2a0n19npnuP408bkAQ,3744
60
60
  wayfinder_paths/core/clients/ClientManager.py,sha256=2p8oEFnCxKCH_TBMKo9gMLAwzwLgeotdgFod8wpoa04,8135
61
61
  wayfinder_paths/core/clients/HyperlendClient.py,sha256=6yAhojEbjrRC7YLckwGL_2z5lwI4xnrRVNzxspqKSTg,6173
62
62
  wayfinder_paths/core/clients/LedgerClient.py,sha256=M6VlG0yq3H4rQt6qRxc0QQVd7GoPXJpj2FcD0RM_C_k,14430
63
- wayfinder_paths/core/clients/PoolClient.py,sha256=s4fg-OPPxq7tfy8Fbk_QtGCkzkiRCx-P8UwANUfJpE0,4070
63
+ wayfinder_paths/core/clients/PoolClient.py,sha256=EMIRRw7nh2bha-Qb5uOcIRgbnnu_v5FIvDU0D61nXGI,3475
64
64
  wayfinder_paths/core/clients/SimulationClient.py,sha256=ViQmXCQKwhpnZA-YkfIgArrpxGr1U11lZNlbBIak1MU,6364
65
65
  wayfinder_paths/core/clients/TokenClient.py,sha256=zg39K-uA1ObkNEcxoXviA1QYSd-fxQXxjBHFOeClY9E,2788
66
66
  wayfinder_paths/core/clients/TransactionClient.py,sha256=APs-8lMdgBnE40wOn5L8_lEdJ3DddTZFcQbW0tIfJWg,2040
67
67
  wayfinder_paths/core/clients/WalletClient.py,sha256=Vc2AwllBxUzkdZKKVRrPR4gl8mtvffRxz5QbrpxcH-0,2819
68
68
  wayfinder_paths/core/clients/WayfinderClient.py,sha256=lLdmD58gAyx5N4yYN4-IYjvRDVzwE3K408XuI07g6g4,10724
69
69
  wayfinder_paths/core/clients/__init__.py,sha256=oNq6fQW8hUnpkuIZxdbOTLPayJRLA6S-k8e7wqsH_7c,1581
70
- wayfinder_paths/core/clients/protocols.py,sha256=qSPPRCEkb1FVNu_OWiUfXAg_GustfPSZ_H2gkKxZoPs,10246
70
+ wayfinder_paths/core/clients/protocols.py,sha256=3TYdOdvz9en72_xA6sMHgahB21ZQFP5w86qE6pwo7YA,10117
71
71
  wayfinder_paths/core/clients/sdk_example.py,sha256=Y6mSyHfsWcOje6E-geNI0C4CQ6uyZaD3V9Q8kPM53eo,2969
72
72
  wayfinder_paths/core/config.py,sha256=A--KQp_EDLXhtituvk3WXPUP2SJv45IcNcm4G_nFMc0,16890
73
73
  wayfinder_paths/core/constants/__init__.py,sha256=KH-TtfNBJgp0WfKIxvHnvS521odH8RS3Qhl8cQhr4Ys,663
@@ -128,7 +128,7 @@ wayfinder_paths/strategies/stablecoin_yield_strategy/README.md,sha256=Qj1b2bU560
128
128
  wayfinder_paths/strategies/stablecoin_yield_strategy/examples.json,sha256=pL1DNFEvYvXKK7xXD5oQYFPQj3Cm1ocKnk6r_iZk0IY,423
129
129
  wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml,sha256=rBb7-Fmub8twfKJgbBIiCWbwI2nLnuqBNyAJs36WhIg,750
130
130
  wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py,sha256=6Ug2_cFx3nqw4Of5Oo1e9h1tQL1G3JXk2XcxNoq2Q0g,75607
131
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py,sha256=hCznS-kr7xKb-Iz7MdDEiwhnbclE1nEg8CMcNKEQQ5E,18611
131
+ wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py,sha256=StGLLqDwthmqYDKLo-Qo0XCUU0pEFD_H65smdxgpBGc,20125
132
132
  wayfinder_paths/templates/adapter/README.md,sha256=QcJ0cwXqqtj1VRK1wAs-unUphTPHdJwoIrIoSU4hTmA,3550
133
133
  wayfinder_paths/templates/adapter/adapter.py,sha256=8wdqcEwqb7XGUxl2gQvGnbFwhPi1h15ZJhB2lgtZieI,814
134
134
  wayfinder_paths/templates/adapter/examples.json,sha256=KLHy3AgPIplAaZN0qY2A-HBMa1xXkMhIyusORovTD9w,79
@@ -143,7 +143,7 @@ wayfinder_paths/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3h
143
143
  wayfinder_paths/tests/test_smoke_manifest.py,sha256=YjVzHTWys5o6Ae2cUuuJPhk-QgKxT1InDFHLjpouRiY,1267
144
144
  wayfinder_paths/tests/test_test_coverage.py,sha256=9NrZeVmP02D4W7Qc0XjciC05bhvdTCVibYjTGfa_GQk,7893
145
145
  wayfinder_paths/tests/test_utils.py,sha256=pxHT0QKFlyJeJo8bFnKXzWcOdi6t8rbJ0JFCBaFCBRQ,2112
146
- wayfinder_paths-0.1.7.dist-info/LICENSE,sha256=dYKnlkC_xosBAEQNUvB6cHMuhFgcUtN0oBR7E8_aR2Y,1066
147
- wayfinder_paths-0.1.7.dist-info/METADATA,sha256=S_D2fakNObohzexQc3K9WaSbKnRYFf-Oguz-E4yZDtA,31378
148
- wayfinder_paths-0.1.7.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
149
- wayfinder_paths-0.1.7.dist-info/RECORD,,
146
+ wayfinder_paths-0.1.8.dist-info/LICENSE,sha256=dYKnlkC_xosBAEQNUvB6cHMuhFgcUtN0oBR7E8_aR2Y,1066
147
+ wayfinder_paths-0.1.8.dist-info/METADATA,sha256=xHD03utEsSU9sZijwc-UwDmK0kXBiUNUIcQgD0eDfXA,31404
148
+ wayfinder_paths-0.1.8.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
149
+ wayfinder_paths-0.1.8.dist-info/RECORD,,