wayfinder-paths 0.1.1__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 (115) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +394 -0
  2. wayfinder_paths/__init__.py +21 -0
  3. wayfinder_paths/config.example.json +20 -0
  4. wayfinder_paths/conftest.py +31 -0
  5. wayfinder_paths/core/__init__.py +13 -0
  6. wayfinder_paths/core/adapters/BaseAdapter.py +48 -0
  7. wayfinder_paths/core/adapters/__init__.py +5 -0
  8. wayfinder_paths/core/adapters/base.py +5 -0
  9. wayfinder_paths/core/clients/AuthClient.py +83 -0
  10. wayfinder_paths/core/clients/BRAPClient.py +90 -0
  11. wayfinder_paths/core/clients/ClientManager.py +231 -0
  12. wayfinder_paths/core/clients/HyperlendClient.py +151 -0
  13. wayfinder_paths/core/clients/LedgerClient.py +222 -0
  14. wayfinder_paths/core/clients/PoolClient.py +96 -0
  15. wayfinder_paths/core/clients/SimulationClient.py +180 -0
  16. wayfinder_paths/core/clients/TokenClient.py +73 -0
  17. wayfinder_paths/core/clients/TransactionClient.py +47 -0
  18. wayfinder_paths/core/clients/WalletClient.py +90 -0
  19. wayfinder_paths/core/clients/WayfinderClient.py +258 -0
  20. wayfinder_paths/core/clients/__init__.py +48 -0
  21. wayfinder_paths/core/clients/protocols.py +295 -0
  22. wayfinder_paths/core/clients/sdk_example.py +115 -0
  23. wayfinder_paths/core/config.py +369 -0
  24. wayfinder_paths/core/constants/__init__.py +26 -0
  25. wayfinder_paths/core/constants/base.py +25 -0
  26. wayfinder_paths/core/constants/erc20_abi.py +118 -0
  27. wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
  28. wayfinder_paths/core/engine/VaultJob.py +182 -0
  29. wayfinder_paths/core/engine/__init__.py +5 -0
  30. wayfinder_paths/core/engine/manifest.py +97 -0
  31. wayfinder_paths/core/services/__init__.py +0 -0
  32. wayfinder_paths/core/services/base.py +177 -0
  33. wayfinder_paths/core/services/local_evm_txn.py +429 -0
  34. wayfinder_paths/core/services/local_token_txn.py +231 -0
  35. wayfinder_paths/core/services/web3_service.py +45 -0
  36. wayfinder_paths/core/settings.py +61 -0
  37. wayfinder_paths/core/strategies/Strategy.py +183 -0
  38. wayfinder_paths/core/strategies/__init__.py +5 -0
  39. wayfinder_paths/core/strategies/base.py +7 -0
  40. wayfinder_paths/core/utils/__init__.py +1 -0
  41. wayfinder_paths/core/utils/evm_helpers.py +165 -0
  42. wayfinder_paths/core/utils/wallets.py +77 -0
  43. wayfinder_paths/core/wallets/README.md +91 -0
  44. wayfinder_paths/core/wallets/WalletManager.py +56 -0
  45. wayfinder_paths/core/wallets/__init__.py +7 -0
  46. wayfinder_paths/run_strategy.py +409 -0
  47. wayfinder_paths/scripts/__init__.py +0 -0
  48. wayfinder_paths/scripts/create_strategy.py +181 -0
  49. wayfinder_paths/scripts/make_wallets.py +160 -0
  50. wayfinder_paths/scripts/validate_manifests.py +213 -0
  51. wayfinder_paths/tests/__init__.py +0 -0
  52. wayfinder_paths/tests/test_smoke_manifest.py +48 -0
  53. wayfinder_paths/tests/test_test_coverage.py +212 -0
  54. wayfinder_paths/tests/test_utils.py +64 -0
  55. wayfinder_paths/vaults/__init__.py +0 -0
  56. wayfinder_paths/vaults/adapters/__init__.py +0 -0
  57. wayfinder_paths/vaults/adapters/balance_adapter/README.md +104 -0
  58. wayfinder_paths/vaults/adapters/balance_adapter/adapter.py +257 -0
  59. wayfinder_paths/vaults/adapters/balance_adapter/examples.json +6 -0
  60. wayfinder_paths/vaults/adapters/balance_adapter/manifest.yaml +8 -0
  61. wayfinder_paths/vaults/adapters/balance_adapter/test_adapter.py +83 -0
  62. wayfinder_paths/vaults/adapters/brap_adapter/README.md +249 -0
  63. wayfinder_paths/vaults/adapters/brap_adapter/__init__.py +7 -0
  64. wayfinder_paths/vaults/adapters/brap_adapter/adapter.py +717 -0
  65. wayfinder_paths/vaults/adapters/brap_adapter/examples.json +175 -0
  66. wayfinder_paths/vaults/adapters/brap_adapter/manifest.yaml +11 -0
  67. wayfinder_paths/vaults/adapters/brap_adapter/test_adapter.py +288 -0
  68. wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +7 -0
  69. wayfinder_paths/vaults/adapters/hyperlend_adapter/adapter.py +298 -0
  70. wayfinder_paths/vaults/adapters/hyperlend_adapter/manifest.yaml +10 -0
  71. wayfinder_paths/vaults/adapters/hyperlend_adapter/test_adapter.py +267 -0
  72. wayfinder_paths/vaults/adapters/ledger_adapter/README.md +158 -0
  73. wayfinder_paths/vaults/adapters/ledger_adapter/__init__.py +7 -0
  74. wayfinder_paths/vaults/adapters/ledger_adapter/adapter.py +286 -0
  75. wayfinder_paths/vaults/adapters/ledger_adapter/examples.json +131 -0
  76. wayfinder_paths/vaults/adapters/ledger_adapter/manifest.yaml +11 -0
  77. wayfinder_paths/vaults/adapters/ledger_adapter/test_adapter.py +202 -0
  78. wayfinder_paths/vaults/adapters/pool_adapter/README.md +218 -0
  79. wayfinder_paths/vaults/adapters/pool_adapter/__init__.py +7 -0
  80. wayfinder_paths/vaults/adapters/pool_adapter/adapter.py +289 -0
  81. wayfinder_paths/vaults/adapters/pool_adapter/examples.json +143 -0
  82. wayfinder_paths/vaults/adapters/pool_adapter/manifest.yaml +10 -0
  83. wayfinder_paths/vaults/adapters/pool_adapter/test_adapter.py +222 -0
  84. wayfinder_paths/vaults/adapters/token_adapter/README.md +101 -0
  85. wayfinder_paths/vaults/adapters/token_adapter/__init__.py +3 -0
  86. wayfinder_paths/vaults/adapters/token_adapter/adapter.py +92 -0
  87. wayfinder_paths/vaults/adapters/token_adapter/examples.json +26 -0
  88. wayfinder_paths/vaults/adapters/token_adapter/manifest.yaml +6 -0
  89. wayfinder_paths/vaults/adapters/token_adapter/test_adapter.py +135 -0
  90. wayfinder_paths/vaults/strategies/__init__.py +0 -0
  91. wayfinder_paths/vaults/strategies/config.py +85 -0
  92. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/README.md +99 -0
  93. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/examples.json +16 -0
  94. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
  95. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/strategy.py +2328 -0
  96. wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/test_strategy.py +319 -0
  97. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/README.md +95 -0
  98. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/examples.json +17 -0
  99. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
  100. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/strategy.py +1684 -0
  101. wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/test_strategy.py +350 -0
  102. wayfinder_paths/vaults/templates/adapter/README.md +105 -0
  103. wayfinder_paths/vaults/templates/adapter/adapter.py +26 -0
  104. wayfinder_paths/vaults/templates/adapter/examples.json +8 -0
  105. wayfinder_paths/vaults/templates/adapter/manifest.yaml +6 -0
  106. wayfinder_paths/vaults/templates/adapter/test_adapter.py +49 -0
  107. wayfinder_paths/vaults/templates/strategy/README.md +152 -0
  108. wayfinder_paths/vaults/templates/strategy/examples.json +11 -0
  109. wayfinder_paths/vaults/templates/strategy/manifest.yaml +8 -0
  110. wayfinder_paths/vaults/templates/strategy/strategy.py +57 -0
  111. wayfinder_paths/vaults/templates/strategy/test_strategy.py +197 -0
  112. wayfinder_paths-0.1.1.dist-info/LICENSE +21 -0
  113. wayfinder_paths-0.1.1.dist-info/METADATA +727 -0
  114. wayfinder_paths-0.1.1.dist-info/RECORD +115 -0
  115. wayfinder_paths-0.1.1.dist-info/WHEEL +4 -0
@@ -0,0 +1,394 @@
1
+ # Configuration Guide
2
+
3
+ This guide explains how to configure your vault strategies for local testing.
4
+
5
+ ## Quick Setup
6
+
7
+ ```bash
8
+ # 1. Generate test wallets (required!)
9
+ poetry run python wayfinder_paths/scripts/make_wallets.py --default --vault
10
+
11
+ # 2. Create your config file
12
+ cp wayfinder_paths/config.example.json config.json
13
+
14
+ # 3. Edit config.json with your Wayfinder credentials
15
+ # NEVER commit this file - it contains your credentials!
16
+ ```
17
+
18
+ ## Configuration Structure
19
+
20
+ ### config.json Structure
21
+
22
+ ```json
23
+ {
24
+ "user": {
25
+ "username": "your_username", // OPTIONAL: For OAuth authentication
26
+ "password": "your_password", // OPTIONAL: For OAuth authentication
27
+ "refresh_token": null, // OPTIONAL: Alternative to username/password
28
+ "api_key": "sk_live_abc123..." // OPTIONAL: For service account authentication
29
+ },
30
+ "system": {
31
+ "api_base_url": "https://wayfinder.ai/api/v1",
32
+ "api_key": "sk_live_abc123...", // OPTIONAL: System-level API key (alternative to user.api_key)
33
+ "wallets_path": "wallets.json" // Path to your generated wallets.json
34
+ },
35
+ "strategy": {
36
+ "rpc_urls": { // RPC endpoints for different chains
37
+ "1": "https://eth.llamarpc.com",
38
+ "42161": "https://arb1.arbitrum.io/rpc",
39
+ "8453": "https://mainnet.base.org",
40
+ "solana": "https://api.mainnet-beta.solana.com"
41
+ }
42
+ }
43
+ }
44
+ ```
45
+
46
+ ## User Configuration
47
+
48
+ **Authentication (choose one method):**
49
+
50
+ **Option 1: Service Account (API Key) - Recommended for Production**
51
+ - `user.api_key` - Your Wayfinder API key for service account authentication
52
+ - OR `system.api_key` - System-level API key (alternative location)
53
+ - OR set `WAYFINDER_API_KEY` environment variable
54
+ - OR pass `api_key` parameter to strategy constructor
55
+
56
+ **Option 2: Personal Access (OAuth) - For Development**
57
+ - `user.username` - Your Wayfinder backend username
58
+ - `user.password` - Your Wayfinder backend password
59
+ - OR `user.refresh_token` - Alternative to username/password
60
+
61
+ **Other Optional fields:**
62
+ - `user.main_wallet_address` - Override auto-loaded main wallet
63
+ - `user.vault_wallet_address` - Override auto-loaded vault wallet
64
+
65
+ **Security Note:** Never commit `config.json` to version control. Add it to `.gitignore`.
66
+
67
+ ## System Configuration
68
+
69
+ These are managed automatically by Wayfinder:
70
+ - `api_base_url` - Wayfinder backend API endpoint
71
+ - `wallets_path` - Location of generated wallets.json
72
+ - `job_id` - Auto-generated by Wayfinder
73
+ - `job_type` - Set to "vault"
74
+
75
+ ## Strategy Configuration
76
+
77
+ ### RPC Endpoints
78
+
79
+ Default RPC URLs are provided for:
80
+ - Ethereum (chain ID: 1)
81
+ - Arbitrum (chain ID: 42161)
82
+ - Base (chain ID: 8453)
83
+ - Solana
84
+
85
+ You can override these in `config.json` if needed.
86
+
87
+ ### Strategy-Specific Settings
88
+
89
+ Individual strategies may have their own configuration parameters. Check the strategy's README for available options.
90
+
91
+ ## Wallet Generation for Testing
92
+
93
+ Use the built-in script to generate test wallets for local development:
94
+
95
+ ```bash
96
+ # Generate default and vault wallets (recommended)
97
+ poetry run python wayfinder_paths/scripts/make_wallets.py --default --vault
98
+
99
+ # Add additional wallets for multi-account testing
100
+ poetry run python wayfinder_paths/scripts/make_wallets.py --default --vault -n 3
101
+
102
+ # Replace existing wallets
103
+ poetry run python wayfinder_paths/scripts/make_wallets.py --default --vault --override
104
+
105
+ # Generate keystore files (for geth/web3 compatibility)
106
+ poetry run python wayfinder_paths/scripts/make_wallets.py --default --vault --keystore-password "your-password"
107
+ ```
108
+
109
+ This creates `wallets.json` in the repository root with:
110
+ - **default** wallet - your main wallet for testing
111
+ - Additional unlabeled wallets if specified
112
+
113
+ **Note:** Generated wallets are for testing only. Wallet addresses are automatically loaded from `wallets.json` when not explicitly set in config.
114
+
115
+ ## Per-Strategy Wallets
116
+
117
+ Each strategy should have its own dedicated wallet for isolation and security. The system automatically looks up wallets by strategy directory name.
118
+
119
+ ### How It Works
120
+
121
+ When you run a strategy:
122
+ 1. The system uses the strategy directory name (e.g., `hyperlend_stable_yield_strategy`) to look up a wallet
123
+ 2. It searches `wallets.json` for a wallet with a matching `label`
124
+ 3. If found, that wallet is used as the strategy's `vault_wallet`
125
+ 4. Falls back to `vault_wallet_address` from config if explicitly provided
126
+
127
+ ### Creating a Strategy with Wallet
128
+
129
+ The easiest way to create a new strategy with its own wallet:
130
+
131
+ ```bash
132
+ # Create a new strategy with dedicated wallet
133
+ just create-strategy "My Strategy Name"
134
+
135
+ # This automatically:
136
+ # - Creates the strategy directory
137
+ # - Generates a wallet with label matching the directory name
138
+ # - Updates the manifest with the correct name and entrypoint
139
+ ```
140
+
141
+ ### Wallet Structure
142
+
143
+ Wallets in `wallets.json` are stored with labels that match strategy directory names:
144
+
145
+ ```json
146
+ [
147
+ {
148
+ "address": "0x...",
149
+ "private_key_hex": "...",
150
+ "label": "default"
151
+ },
152
+ {
153
+ "address": "0x...",
154
+ "private_key_hex": "...",
155
+ "label": "hyperlend_stable_yield_strategy"
156
+ },
157
+ {
158
+ "address": "0x...",
159
+ "private_key_hex": "...",
160
+ "label": "my_awesome_strategy"
161
+ }
162
+ ]
163
+ ```
164
+
165
+ ### Manual Wallet Creation
166
+
167
+ If you need to manually create a wallet for an existing strategy:
168
+
169
+ ```bash
170
+ # Generate a wallet
171
+ poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
172
+
173
+ # Then edit wallets.json to add a label matching your strategy directory name:
174
+ # {
175
+ # "address": "0x...",
176
+ # "private_key_hex": "...",
177
+ # "label": "your_strategy_directory_name"
178
+ # }
179
+ ```
180
+
181
+ ### Strategy Access
182
+
183
+ Strategies access wallets the same way as before:
184
+
185
+ ```python
186
+ # In strategy code
187
+ vault_address = self.config.get("vault_wallet").get("address")
188
+ main_address = self.config.get("main_wallet").get("address")
189
+ ```
190
+
191
+ The `vault_wallet` is automatically populated from the wallet with label matching the strategy directory name.
192
+
193
+ ## Loading Configuration
194
+
195
+ The configuration is loaded automatically when running strategies via `run_strategy.py`:
196
+
197
+ ```bash
198
+ poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy --config config.json
199
+ ```
200
+
201
+ For programmatic use:
202
+
203
+ ```python
204
+ from pathlib import Path
205
+ from core.config import VaultConfig
206
+ import json
207
+
208
+ # Load from file
209
+ with open("config.json") as f:
210
+ config_data = json.load(f)
211
+
212
+ config = VaultConfig.from_dict(config_data)
213
+
214
+ # Configuration now has:
215
+ # - config.user.username & password (for Wayfinder backend)
216
+ # - config.system.api_base_url & wallets_path
217
+ # - config.strategy_config (strategy-specific settings)
218
+ ```
219
+
220
+ ## Wallet Abstraction
221
+
222
+ The vault system supports multiple wallet types through a wallet abstraction layer. By default, adapters use local private keys (self-custodial wallets), but you can inject custom wallet providers for custodial wallets like Privy or Turnkey.
223
+
224
+ ### Default Behavior (Local Wallets)
225
+
226
+ By default, adapters use `LocalWalletProvider` which resolves private keys from:
227
+ - `wallets.json` (matched by address)
228
+ - Environment variables (`PRIVATE_KEY`, `PRIVATE_KEY_VAULT`)
229
+ - Wallet config in `config.json`
230
+
231
+ No code changes are required - existing strategies continue to work.
232
+
233
+ ### Using Custom Wallet Providers
234
+
235
+ To use a custodial wallet provider (e.g., Privy, Turnkey), inject it directly into adapters:
236
+
237
+ ```python
238
+ from vaults.adapters.evm_transaction_adapter.adapter import EvmTransactionAdapter
239
+ from my_privy_integration import PrivyWalletProvider
240
+
241
+ # Create your custom wallet provider
242
+ privy_provider = PrivyWalletProvider(privy_api_key, privy_wallet_id)
243
+
244
+ # Inject it into adapters
245
+ adapter = EvmTransactionAdapter(config, wallet_provider=privy_provider)
246
+ ```
247
+
248
+ See `core/wallets/README.md` for details on implementing custom wallet providers.
249
+
250
+ ## Security Best Practices
251
+
252
+ 1. **Never commit `config.json`** - add it to `.gitignore`
253
+ 2. **Never commit `wallets.json`** - contains private keys
254
+ 3. **Use test wallets** - the script generates throwaway wallets for testing
255
+ 4. **Keep credentials secure** - Wayfinder username/password grant access to backend resources
256
+ 5. **Set conservative parameters** for initial testing:
257
+ - Lower leverage ratios
258
+ - Higher slippage tolerance
259
+ - Lower position sizes
260
+
261
+ ## Authentication with Wayfinder Backend
262
+
263
+ Wayfinder Vaults supports **dual authentication** for different use cases:
264
+
265
+ ### 1. Service Account Authentication (API Key)
266
+
267
+ **Best for:** Backend services, automated systems, and production deployments with higher rate limits.
268
+
269
+ API keys provide service account authentication and are automatically discovered by all clients. You can provide an API key in three ways:
270
+
271
+ #### Option A: Strategy Constructor (Programmatic)
272
+ ```python
273
+ from wayfinder_paths.vaults.strategies.stablecoin_yield_strategy.strategy import StablecoinYieldStrategy
274
+
275
+ strategy = StablecoinYieldStrategy(
276
+ config={...},
277
+ api_key="sk_live_abc123..." # Sets WAYFINDER_API_KEY env var automatically
278
+ )
279
+ ```
280
+
281
+ #### Option B: Environment Variable
282
+ ```bash
283
+ export WAYFINDER_API_KEY="sk_live_abc123..."
284
+ ```
285
+
286
+ #### Option C: config.json
287
+ ```json
288
+ {
289
+ "user": {
290
+ "api_key": "sk_live_abc123..."
291
+ },
292
+ "system": {
293
+ "api_key": "sk_live_abc123..." // Alternative: system-level API key
294
+ }
295
+ }
296
+ ```
297
+
298
+ **Priority Order:** Constructor parameter > `config.json` (user.api_key or system.api_key) > `WAYFINDER_API_KEY` environment variable
299
+
300
+ **How It Works:**
301
+ - When a strategy receives an `api_key`, it sets `os.environ["WAYFINDER_API_KEY"]`
302
+ - All clients created by adapters automatically discover the API key from:
303
+ - Constructor parameter (if passed)
304
+ - `config.json` (via `_load_config_credentials()`)
305
+ - `WAYFINDER_API_KEY` environment variable
306
+ - API keys are included in **all** API requests (including public endpoints) for rate limiting
307
+ - No need to pass API keys explicitly to adapters or clients—they auto-discover it
308
+
309
+ ### 2. Personal Access Authentication (OAuth)
310
+
311
+ **Best for:** Standalone SDK users and local development.
312
+
313
+ The `username` and `password` in your config authenticate with the Wayfinder backend to access:
314
+ - Wallet management
315
+ - Transaction signing services
316
+ - Vault execution services
317
+
318
+ ```json
319
+ {
320
+ "user": {
321
+ "username": "your_username",
322
+ "password": "your_password",
323
+ "refresh_token": null // Optional: use refresh token instead of username/password
324
+ }
325
+ }
326
+ ```
327
+
328
+ **Fallback Behavior:**
329
+ - If an API key is not found or authentication fails, the system automatically falls back to OAuth
330
+ - OAuth tokens are automatically refreshed when they expire
331
+
332
+ ### Security Best Practices
333
+
334
+ - **Never commit credentials** to version control - add `config.json` to `.gitignore`
335
+ - **Use API keys for production** - they provide better rate limits and don't require token refresh
336
+ - **Use OAuth for development** - simpler setup for local testing
337
+ - **Rotate credentials regularly** - especially if exposed or compromised
338
+
339
+ ## Configuration in Strategies
340
+
341
+ Strategies receive configuration automatically through VaultJob:
342
+
343
+ ```python
344
+ from core.strategies.Strategy import Strategy
345
+
346
+ class MyStrategy(Strategy):
347
+ async def setup(self):
348
+ # Access strategy-specific config
349
+ target_leverage = self.config.get("target_leverage", 1.0)
350
+
351
+ # Access RPC URLs
352
+ eth_rpc = self.config.get("rpc_urls", {}).get("1")
353
+
354
+ # Configuration is already loaded from config.json
355
+ ```
356
+
357
+ ## Advanced: Custom RPC Endpoints
358
+
359
+ To use custom RPC endpoints, update the `strategy.rpc_urls` section in `config.json`:
360
+
361
+ ```json
362
+ {
363
+ "strategy": {
364
+ "rpc_urls": {
365
+ "1": "https://your-custom-ethereum-rpc.com",
366
+ "8453": "https://your-custom-base-rpc.com"
367
+ }
368
+ }
369
+ }
370
+ ```
371
+
372
+ ## Troubleshooting
373
+
374
+ **Issue:** "Authentication failed"
375
+ - **If using API key:**
376
+ - Verify API key is correct and not expired
377
+ - Check that API key is set in one of: constructor parameter, `config.json` (user.api_key or system.api_key), or `WAYFINDER_API_KEY` env var
378
+ - Ensure API key has proper permissions for the operations you're performing
379
+ - **If using OAuth:**
380
+ - Check that `username` and `password` are correct in `config.json`
381
+ - Verify your Wayfinder account credentials
382
+ - Try using `refresh_token` instead if you have one
383
+ - **General:**
384
+ - The system automatically falls back from API key to OAuth if API key authentication fails
385
+ - Check logs for specific authentication error messages
386
+
387
+ **Issue:** "Wallet not found"
388
+ - Run the wallet generation script first
389
+ - Check that `wallets.json` exists in repository root
390
+ - Verify `system.wallets_path` in config points to the correct location
391
+
392
+ **Issue:** "Invalid config"
393
+ - Ensure `config.json` follows the correct structure
394
+ - Check that all required fields are present
@@ -0,0 +1,21 @@
1
+ """Wayfinder Path - Trading strategies and adapters for automated vault management"""
2
+
3
+ __version__ = "0.1.0"
4
+
5
+ # Re-export commonly used items for convenience
6
+ from wayfinder_paths.core import (
7
+ BaseAdapter,
8
+ StatusDict,
9
+ StatusTuple,
10
+ Strategy,
11
+ VaultJob,
12
+ )
13
+
14
+ __all__ = [
15
+ "__version__",
16
+ "BaseAdapter",
17
+ "Strategy",
18
+ "StatusDict",
19
+ "StatusTuple",
20
+ "VaultJob",
21
+ ]
@@ -0,0 +1,20 @@
1
+ {
2
+ "user": {
3
+ "username": "your_username",
4
+ "password": "your_password",
5
+ "refresh_token": null
6
+ },
7
+ "system": {
8
+ "api_base_url": "https://wayfinder.ai/api/v1",
9
+ "wallets_path": "wallets.json"
10
+ },
11
+ "strategy": {
12
+ "rpc_urls": {
13
+ "1": "https://eth.llamarpc.com",
14
+ "42161": "https://arb1.arbitrum.io/rpc",
15
+ "8453": "https://mainnet.base.org",
16
+ "solana": "https://api.mainnet-beta.solana.com",
17
+ "999": "https://rpc.hyperliquid.xyz/evm"
18
+ }
19
+ }
20
+ }
@@ -0,0 +1,31 @@
1
+ """
2
+ Conftest for wayfinder-paths package tests.
3
+ Adds wayfinder-paths directory to Python path for imports.
4
+ This must run early, so imports like 'from tests.test_utils' work.
5
+ """
6
+
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ # Add wayfinder-paths directory to Python path for imports (for tests.test_utils)
11
+ # This needs to be at index 0 to take precedence over repo root 'tests/' directory
12
+ _wayfinder_path_dir = Path(__file__).parent
13
+ _wayfinder_path_str = str(_wayfinder_path_dir)
14
+
15
+
16
+ def pytest_configure(config):
17
+ """Configure pytest - runs early to set up imports."""
18
+ if _wayfinder_path_str not in sys.path:
19
+ sys.path.insert(0, _wayfinder_path_str)
20
+ elif sys.path.index(_wayfinder_path_str) > 0:
21
+ # Move to front if it exists but isn't first
22
+ sys.path.remove(_wayfinder_path_str)
23
+ sys.path.insert(0, _wayfinder_path_str)
24
+
25
+
26
+ # Also set it immediately (in case pytest_configure hasn't run yet)
27
+ if _wayfinder_path_str not in sys.path:
28
+ sys.path.insert(0, _wayfinder_path_str)
29
+ elif sys.path.index(_wayfinder_path_str) > 0:
30
+ sys.path.remove(_wayfinder_path_str)
31
+ sys.path.insert(0, _wayfinder_path_str)
@@ -0,0 +1,13 @@
1
+ """Wayfinder Vaults Core Engine"""
2
+
3
+ from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
4
+ from wayfinder_paths.core.engine.VaultJob import VaultJob
5
+ from wayfinder_paths.core.strategies.Strategy import StatusDict, StatusTuple, Strategy
6
+
7
+ __all__ = [
8
+ "Strategy",
9
+ "StatusDict",
10
+ "StatusTuple",
11
+ "BaseAdapter",
12
+ "VaultJob",
13
+ ]
@@ -0,0 +1,48 @@
1
+ from abc import ABC
2
+ from typing import Any
3
+
4
+ from loguru import logger
5
+
6
+
7
+ class BaseAdapter(ABC):
8
+ """Base adapter class for exchange/protocol integrations"""
9
+
10
+ adapter_type: str = None
11
+
12
+ def __init__(self, name: str, config: dict[str, Any] | None = None):
13
+ self.name = name
14
+ self.config = config or {}
15
+ self.logger = logger.bind(adapter=self.__class__.__name__)
16
+
17
+ async def connect(self) -> bool:
18
+ """Optional: establish connectivity. Defaults to True."""
19
+ return True
20
+
21
+ async def get_balance(self, asset: str) -> dict[str, Any]:
22
+ """Optional: provide balance. Default is not implemented."""
23
+ raise NotImplementedError(
24
+ f"get_balance not supported by {self.__class__.__name__}"
25
+ )
26
+
27
+ async def health_check(self) -> dict[str, Any]:
28
+ """
29
+ Check adapter health and connectivity
30
+ Returns: Health status dictionary
31
+ """
32
+ try:
33
+ connected = await self.connect()
34
+ return {
35
+ "status": "healthy" if connected else "unhealthy",
36
+ "connected": connected,
37
+ "adapter": self.adapter_type or self.__class__.__name__,
38
+ }
39
+ except Exception as e:
40
+ return {
41
+ "status": "error",
42
+ "error": str(e),
43
+ "adapter": self.adapter_type or self.__class__.__name__,
44
+ }
45
+
46
+ async def close(self):
47
+ """Clean up resources"""
48
+ pass
@@ -0,0 +1,5 @@
1
+ """Core Adapter Module - SDK surface re-export"""
2
+
3
+ from .base import BaseAdapter
4
+
5
+ __all__ = ["BaseAdapter"]
@@ -0,0 +1,5 @@
1
+ from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
2
+
3
+ __all__ = [
4
+ "BaseAdapter",
5
+ ]
@@ -0,0 +1,83 @@
1
+ import os
2
+ from typing import Any
3
+
4
+ from loguru import logger
5
+
6
+ from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
7
+ from wayfinder_paths.core.settings import settings
8
+
9
+
10
+ class AuthClient(WayfinderClient):
11
+ def __init__(self, api_key: str | None = None):
12
+ """
13
+ Initialize AuthClient.
14
+
15
+ Args:
16
+ api_key: Optional API key for service account authentication.
17
+ If provided, uses API key auth. Otherwise falls back to config.json.
18
+ """
19
+ super().__init__(api_key=api_key)
20
+
21
+ self.api_base_url = f"{settings.WAYFINDER_API_URL}"
22
+ self.logger = logger.bind(client="AuthClient")
23
+
24
+ def _is_using_api_key(self) -> bool:
25
+ """Check if API key authentication is being used."""
26
+ if self._api_key:
27
+ return True
28
+
29
+ try:
30
+ creds = self._load_config_credentials()
31
+ if creds.get("api_key"):
32
+ return True
33
+ if os.getenv("WAYFINDER_API_KEY"):
34
+ return True
35
+ except Exception:
36
+ pass
37
+
38
+ return False
39
+
40
+ async def authenticate(
41
+ self,
42
+ username: str | None = None,
43
+ password: str | None = None,
44
+ *,
45
+ refresh_token: str | None = None,
46
+ ) -> dict[str, Any]:
47
+ """
48
+ Obtain an access token via username/password or refresh token.
49
+
50
+ Expected endpoints:
51
+ - POST {api_base_url}/token/ (username, password) -> { access, refresh }
52
+ - POST {api_base_url}/token/refresh/ (refresh) -> { access }
53
+ """
54
+ if refresh_token:
55
+ self.logger.debug(
56
+ "AuthClient.authenticate -> POST /token/refresh (refresh provided)"
57
+ )
58
+ url = f"{self.api_base_url}/auth/token/refresh/"
59
+ payload = {"refresh": refresh_token}
60
+ elif username and password:
61
+ self.logger.debug(
62
+ f"AuthClient.authenticate -> POST /token (username provided={bool(username)})"
63
+ )
64
+ url = f"{self.api_base_url}/auth/token/"
65
+ payload = {"username": username, "password": password}
66
+ else:
67
+ raise ValueError(
68
+ "Credentials required: provide username+password or refresh_token"
69
+ )
70
+
71
+ response = await self._request("POST", url, json=payload)
72
+ response.raise_for_status()
73
+ data = response.json()
74
+
75
+ access = data.get("access") or data.get("access_token")
76
+ refresh = data.get("refresh") or data.get("refresh_token")
77
+ if access or refresh:
78
+ self.set_tokens(access, refresh)
79
+ self.logger.debug(
80
+ f"AuthClient.authenticate <- success (access={'yes' if access else 'no'}, refresh={'yes' if refresh else 'no'})"
81
+ )
82
+
83
+ return data