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.
- wayfinder_paths/CONFIG_GUIDE.md +394 -0
- wayfinder_paths/__init__.py +21 -0
- wayfinder_paths/config.example.json +20 -0
- wayfinder_paths/conftest.py +31 -0
- wayfinder_paths/core/__init__.py +13 -0
- wayfinder_paths/core/adapters/BaseAdapter.py +48 -0
- wayfinder_paths/core/adapters/__init__.py +5 -0
- wayfinder_paths/core/adapters/base.py +5 -0
- wayfinder_paths/core/clients/AuthClient.py +83 -0
- wayfinder_paths/core/clients/BRAPClient.py +90 -0
- wayfinder_paths/core/clients/ClientManager.py +231 -0
- wayfinder_paths/core/clients/HyperlendClient.py +151 -0
- wayfinder_paths/core/clients/LedgerClient.py +222 -0
- wayfinder_paths/core/clients/PoolClient.py +96 -0
- wayfinder_paths/core/clients/SimulationClient.py +180 -0
- wayfinder_paths/core/clients/TokenClient.py +73 -0
- wayfinder_paths/core/clients/TransactionClient.py +47 -0
- wayfinder_paths/core/clients/WalletClient.py +90 -0
- wayfinder_paths/core/clients/WayfinderClient.py +258 -0
- wayfinder_paths/core/clients/__init__.py +48 -0
- wayfinder_paths/core/clients/protocols.py +295 -0
- wayfinder_paths/core/clients/sdk_example.py +115 -0
- wayfinder_paths/core/config.py +369 -0
- wayfinder_paths/core/constants/__init__.py +26 -0
- wayfinder_paths/core/constants/base.py +25 -0
- wayfinder_paths/core/constants/erc20_abi.py +118 -0
- wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
- wayfinder_paths/core/engine/VaultJob.py +182 -0
- wayfinder_paths/core/engine/__init__.py +5 -0
- wayfinder_paths/core/engine/manifest.py +97 -0
- wayfinder_paths/core/services/__init__.py +0 -0
- wayfinder_paths/core/services/base.py +177 -0
- wayfinder_paths/core/services/local_evm_txn.py +429 -0
- wayfinder_paths/core/services/local_token_txn.py +231 -0
- wayfinder_paths/core/services/web3_service.py +45 -0
- wayfinder_paths/core/settings.py +61 -0
- wayfinder_paths/core/strategies/Strategy.py +183 -0
- wayfinder_paths/core/strategies/__init__.py +5 -0
- wayfinder_paths/core/strategies/base.py +7 -0
- wayfinder_paths/core/utils/__init__.py +1 -0
- wayfinder_paths/core/utils/evm_helpers.py +165 -0
- wayfinder_paths/core/utils/wallets.py +77 -0
- wayfinder_paths/core/wallets/README.md +91 -0
- wayfinder_paths/core/wallets/WalletManager.py +56 -0
- wayfinder_paths/core/wallets/__init__.py +7 -0
- wayfinder_paths/run_strategy.py +409 -0
- wayfinder_paths/scripts/__init__.py +0 -0
- wayfinder_paths/scripts/create_strategy.py +181 -0
- wayfinder_paths/scripts/make_wallets.py +160 -0
- wayfinder_paths/scripts/validate_manifests.py +213 -0
- wayfinder_paths/tests/__init__.py +0 -0
- wayfinder_paths/tests/test_smoke_manifest.py +48 -0
- wayfinder_paths/tests/test_test_coverage.py +212 -0
- wayfinder_paths/tests/test_utils.py +64 -0
- wayfinder_paths/vaults/__init__.py +0 -0
- wayfinder_paths/vaults/adapters/__init__.py +0 -0
- wayfinder_paths/vaults/adapters/balance_adapter/README.md +104 -0
- wayfinder_paths/vaults/adapters/balance_adapter/adapter.py +257 -0
- wayfinder_paths/vaults/adapters/balance_adapter/examples.json +6 -0
- wayfinder_paths/vaults/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/vaults/adapters/balance_adapter/test_adapter.py +83 -0
- wayfinder_paths/vaults/adapters/brap_adapter/README.md +249 -0
- wayfinder_paths/vaults/adapters/brap_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/brap_adapter/adapter.py +717 -0
- wayfinder_paths/vaults/adapters/brap_adapter/examples.json +175 -0
- wayfinder_paths/vaults/adapters/brap_adapter/manifest.yaml +11 -0
- wayfinder_paths/vaults/adapters/brap_adapter/test_adapter.py +288 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/adapter.py +298 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/manifest.yaml +10 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/test_adapter.py +267 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/README.md +158 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/adapter.py +286 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/examples.json +131 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/manifest.yaml +11 -0
- wayfinder_paths/vaults/adapters/ledger_adapter/test_adapter.py +202 -0
- wayfinder_paths/vaults/adapters/pool_adapter/README.md +218 -0
- wayfinder_paths/vaults/adapters/pool_adapter/__init__.py +7 -0
- wayfinder_paths/vaults/adapters/pool_adapter/adapter.py +289 -0
- wayfinder_paths/vaults/adapters/pool_adapter/examples.json +143 -0
- wayfinder_paths/vaults/adapters/pool_adapter/manifest.yaml +10 -0
- wayfinder_paths/vaults/adapters/pool_adapter/test_adapter.py +222 -0
- wayfinder_paths/vaults/adapters/token_adapter/README.md +101 -0
- wayfinder_paths/vaults/adapters/token_adapter/__init__.py +3 -0
- wayfinder_paths/vaults/adapters/token_adapter/adapter.py +92 -0
- wayfinder_paths/vaults/adapters/token_adapter/examples.json +26 -0
- wayfinder_paths/vaults/adapters/token_adapter/manifest.yaml +6 -0
- wayfinder_paths/vaults/adapters/token_adapter/test_adapter.py +135 -0
- wayfinder_paths/vaults/strategies/__init__.py +0 -0
- wayfinder_paths/vaults/strategies/config.py +85 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/README.md +99 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/examples.json +16 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/strategy.py +2328 -0
- wayfinder_paths/vaults/strategies/hyperlend_stable_yield_strategy/test_strategy.py +319 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/README.md +95 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/examples.json +17 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/strategy.py +1684 -0
- wayfinder_paths/vaults/strategies/stablecoin_yield_strategy/test_strategy.py +350 -0
- wayfinder_paths/vaults/templates/adapter/README.md +105 -0
- wayfinder_paths/vaults/templates/adapter/adapter.py +26 -0
- wayfinder_paths/vaults/templates/adapter/examples.json +8 -0
- wayfinder_paths/vaults/templates/adapter/manifest.yaml +6 -0
- wayfinder_paths/vaults/templates/adapter/test_adapter.py +49 -0
- wayfinder_paths/vaults/templates/strategy/README.md +152 -0
- wayfinder_paths/vaults/templates/strategy/examples.json +11 -0
- wayfinder_paths/vaults/templates/strategy/manifest.yaml +8 -0
- wayfinder_paths/vaults/templates/strategy/strategy.py +57 -0
- wayfinder_paths/vaults/templates/strategy/test_strategy.py +197 -0
- wayfinder_paths-0.1.1.dist-info/LICENSE +21 -0
- wayfinder_paths-0.1.1.dist-info/METADATA +727 -0
- wayfinder_paths-0.1.1.dist-info/RECORD +115 -0
- 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,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
|