wayfinder-paths 0.1.7__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 +399 -0
- wayfinder_paths/__init__.py +22 -0
- wayfinder_paths/abis/generic/erc20.json +383 -0
- wayfinder_paths/adapters/__init__.py +0 -0
- wayfinder_paths/adapters/balance_adapter/README.md +94 -0
- wayfinder_paths/adapters/balance_adapter/adapter.py +238 -0
- wayfinder_paths/adapters/balance_adapter/examples.json +6 -0
- wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +59 -0
- wayfinder_paths/adapters/brap_adapter/README.md +249 -0
- wayfinder_paths/adapters/brap_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/brap_adapter/adapter.py +726 -0
- wayfinder_paths/adapters/brap_adapter/examples.json +175 -0
- wayfinder_paths/adapters/brap_adapter/manifest.yaml +11 -0
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +286 -0
- wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +305 -0
- wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +10 -0
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +274 -0
- wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +18 -0
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1093 -0
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +549 -0
- wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +8 -0
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +1050 -0
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +126 -0
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +219 -0
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +220 -0
- wayfinder_paths/adapters/hyperliquid_adapter/utils.py +134 -0
- wayfinder_paths/adapters/ledger_adapter/README.md +145 -0
- wayfinder_paths/adapters/ledger_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/ledger_adapter/adapter.py +289 -0
- wayfinder_paths/adapters/ledger_adapter/examples.json +137 -0
- wayfinder_paths/adapters/ledger_adapter/manifest.yaml +11 -0
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +205 -0
- wayfinder_paths/adapters/pool_adapter/README.md +206 -0
- wayfinder_paths/adapters/pool_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/pool_adapter/adapter.py +282 -0
- wayfinder_paths/adapters/pool_adapter/examples.json +143 -0
- wayfinder_paths/adapters/pool_adapter/manifest.yaml +10 -0
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +220 -0
- wayfinder_paths/adapters/token_adapter/README.md +101 -0
- wayfinder_paths/adapters/token_adapter/__init__.py +3 -0
- wayfinder_paths/adapters/token_adapter/adapter.py +96 -0
- wayfinder_paths/adapters/token_adapter/examples.json +26 -0
- wayfinder_paths/adapters/token_adapter/manifest.yaml +6 -0
- wayfinder_paths/adapters/token_adapter/test_adapter.py +125 -0
- wayfinder_paths/config.example.json +22 -0
- wayfinder_paths/conftest.py +31 -0
- wayfinder_paths/core/__init__.py +18 -0
- wayfinder_paths/core/adapters/BaseAdapter.py +65 -0
- wayfinder_paths/core/adapters/__init__.py +5 -0
- wayfinder_paths/core/adapters/base.py +5 -0
- wayfinder_paths/core/adapters/models.py +46 -0
- wayfinder_paths/core/analytics/__init__.py +11 -0
- wayfinder_paths/core/analytics/bootstrap.py +57 -0
- wayfinder_paths/core/analytics/stats.py +48 -0
- wayfinder_paths/core/analytics/test_analytics.py +170 -0
- wayfinder_paths/core/clients/AuthClient.py +83 -0
- wayfinder_paths/core/clients/BRAPClient.py +109 -0
- wayfinder_paths/core/clients/ClientManager.py +210 -0
- wayfinder_paths/core/clients/HyperlendClient.py +192 -0
- wayfinder_paths/core/clients/LedgerClient.py +443 -0
- wayfinder_paths/core/clients/PoolClient.py +128 -0
- wayfinder_paths/core/clients/SimulationClient.py +192 -0
- wayfinder_paths/core/clients/TokenClient.py +89 -0
- wayfinder_paths/core/clients/TransactionClient.py +63 -0
- wayfinder_paths/core/clients/WalletClient.py +94 -0
- wayfinder_paths/core/clients/WayfinderClient.py +269 -0
- wayfinder_paths/core/clients/__init__.py +48 -0
- wayfinder_paths/core/clients/protocols.py +392 -0
- wayfinder_paths/core/clients/sdk_example.py +110 -0
- wayfinder_paths/core/config.py +458 -0
- wayfinder_paths/core/constants/__init__.py +26 -0
- wayfinder_paths/core/constants/base.py +42 -0
- wayfinder_paths/core/constants/erc20_abi.py +118 -0
- wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
- wayfinder_paths/core/engine/StrategyJob.py +188 -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 +179 -0
- wayfinder_paths/core/services/local_evm_txn.py +430 -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 +280 -0
- wayfinder_paths/core/strategies/__init__.py +5 -0
- wayfinder_paths/core/strategies/base.py +7 -0
- wayfinder_paths/core/strategies/descriptors.py +81 -0
- wayfinder_paths/core/utils/__init__.py +1 -0
- wayfinder_paths/core/utils/evm_helpers.py +206 -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/policies/enso.py +17 -0
- wayfinder_paths/policies/erc20.py +34 -0
- wayfinder_paths/policies/evm.py +21 -0
- wayfinder_paths/policies/hyper_evm.py +19 -0
- wayfinder_paths/policies/hyperlend.py +12 -0
- wayfinder_paths/policies/hyperliquid.py +30 -0
- wayfinder_paths/policies/moonwell.py +54 -0
- wayfinder_paths/policies/prjx.py +30 -0
- wayfinder_paths/policies/util.py +27 -0
- wayfinder_paths/run_strategy.py +411 -0
- wayfinder_paths/scripts/__init__.py +0 -0
- wayfinder_paths/scripts/create_strategy.py +181 -0
- wayfinder_paths/scripts/make_wallets.py +169 -0
- wayfinder_paths/scripts/run_strategy.py +124 -0
- wayfinder_paths/scripts/validate_manifests.py +213 -0
- wayfinder_paths/strategies/__init__.py +0 -0
- wayfinder_paths/strategies/basis_trading_strategy/README.md +213 -0
- wayfinder_paths/strategies/basis_trading_strategy/__init__.py +3 -0
- wayfinder_paths/strategies/basis_trading_strategy/constants.py +1 -0
- wayfinder_paths/strategies/basis_trading_strategy/examples.json +16 -0
- wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +23 -0
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1011 -0
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +4522 -0
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +727 -0
- wayfinder_paths/strategies/basis_trading_strategy/types.py +39 -0
- wayfinder_paths/strategies/config.py +85 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +100 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +8 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +2270 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +352 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +96 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/examples.json +17 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1810 -0
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +520 -0
- wayfinder_paths/templates/adapter/README.md +105 -0
- wayfinder_paths/templates/adapter/adapter.py +26 -0
- wayfinder_paths/templates/adapter/examples.json +8 -0
- wayfinder_paths/templates/adapter/manifest.yaml +6 -0
- wayfinder_paths/templates/adapter/test_adapter.py +49 -0
- wayfinder_paths/templates/strategy/README.md +153 -0
- wayfinder_paths/templates/strategy/examples.json +11 -0
- wayfinder_paths/templates/strategy/manifest.yaml +8 -0
- wayfinder_paths/templates/strategy/strategy.py +57 -0
- wayfinder_paths/templates/strategy/test_strategy.py +197 -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-0.1.7.dist-info/LICENSE +21 -0
- wayfinder_paths-0.1.7.dist-info/METADATA +777 -0
- wayfinder_paths-0.1.7.dist-info/RECORD +149 -0
- wayfinder_paths-0.1.7.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ERC20 ABI constants for consistent use across the codebase.
|
|
3
|
+
This centralizes ABI definitions to avoid duplication and improve maintainability.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
# Standard ERC20 ABI - includes common functions needed for token operations
|
|
7
|
+
ERC20_ABI = [
|
|
8
|
+
{
|
|
9
|
+
"constant": True,
|
|
10
|
+
"inputs": [
|
|
11
|
+
{"name": "_owner", "type": "address"},
|
|
12
|
+
{"name": "_spender", "type": "address"},
|
|
13
|
+
],
|
|
14
|
+
"name": "allowance",
|
|
15
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
16
|
+
"type": "function",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"constant": False,
|
|
20
|
+
"inputs": [
|
|
21
|
+
{"name": "_spender", "type": "address"},
|
|
22
|
+
{"name": "_value", "type": "uint256"},
|
|
23
|
+
],
|
|
24
|
+
"name": "approve",
|
|
25
|
+
"outputs": [{"name": "", "type": "bool"}],
|
|
26
|
+
"type": "function",
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"constant": True,
|
|
30
|
+
"inputs": [{"name": "account", "type": "address"}],
|
|
31
|
+
"name": "balanceOf",
|
|
32
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
33
|
+
"type": "function",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"constant": True,
|
|
37
|
+
"inputs": [],
|
|
38
|
+
"name": "decimals",
|
|
39
|
+
"outputs": [{"name": "", "type": "uint8"}],
|
|
40
|
+
"type": "function",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"constant": True,
|
|
44
|
+
"inputs": [],
|
|
45
|
+
"name": "name",
|
|
46
|
+
"outputs": [{"name": "", "type": "string"}],
|
|
47
|
+
"type": "function",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
"constant": True,
|
|
51
|
+
"inputs": [],
|
|
52
|
+
"name": "symbol",
|
|
53
|
+
"outputs": [{"name": "", "type": "string"}],
|
|
54
|
+
"type": "function",
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"constant": True,
|
|
58
|
+
"inputs": [],
|
|
59
|
+
"name": "totalSupply",
|
|
60
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
61
|
+
"type": "function",
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"constant": False,
|
|
65
|
+
"inputs": [
|
|
66
|
+
{"name": "_to", "type": "address"},
|
|
67
|
+
{"name": "_value", "type": "uint256"},
|
|
68
|
+
],
|
|
69
|
+
"name": "transfer",
|
|
70
|
+
"outputs": [{"name": "", "type": "bool"}],
|
|
71
|
+
"type": "function",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"constant": False,
|
|
75
|
+
"inputs": [
|
|
76
|
+
{"name": "_from", "type": "address"},
|
|
77
|
+
{"name": "_to", "type": "address"},
|
|
78
|
+
{"name": "_value", "type": "uint256"},
|
|
79
|
+
],
|
|
80
|
+
"name": "transferFrom",
|
|
81
|
+
"outputs": [{"name": "", "type": "bool"}],
|
|
82
|
+
"type": "function",
|
|
83
|
+
},
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
# Minimal ABI for specific use cases (e.g., when you only need certain functions)
|
|
87
|
+
ERC20_MINIMAL_ABI = [
|
|
88
|
+
{
|
|
89
|
+
"constant": True,
|
|
90
|
+
"inputs": [{"name": "account", "type": "address"}],
|
|
91
|
+
"name": "balanceOf",
|
|
92
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
93
|
+
"type": "function",
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
ERC20_APPROVAL_ABI = [
|
|
98
|
+
{
|
|
99
|
+
"constant": True,
|
|
100
|
+
"inputs": [
|
|
101
|
+
{"name": "_owner", "type": "address"},
|
|
102
|
+
{"name": "_spender", "type": "address"},
|
|
103
|
+
],
|
|
104
|
+
"name": "allowance",
|
|
105
|
+
"outputs": [{"name": "", "type": "uint256"}],
|
|
106
|
+
"type": "function",
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
"constant": False,
|
|
110
|
+
"inputs": [
|
|
111
|
+
{"name": "_spender", "type": "address"},
|
|
112
|
+
{"name": "_value", "type": "uint256"},
|
|
113
|
+
],
|
|
114
|
+
"name": "approve",
|
|
115
|
+
"outputs": [{"name": "", "type": "bool"}],
|
|
116
|
+
"type": "function",
|
|
117
|
+
},
|
|
118
|
+
]
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HyperLend ABI constants for smart contract interactions.
|
|
3
|
+
|
|
4
|
+
This module contains ABI definitions for HyperLend protocol contracts,
|
|
5
|
+
including Pool, Protocol Data Provider, Wrapped Token Gateway, and WETH contracts.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
# Minimal Pool ABI for supply and deposit operations
|
|
9
|
+
POOL_ABI = [
|
|
10
|
+
{
|
|
11
|
+
"name": "supply",
|
|
12
|
+
"type": "function",
|
|
13
|
+
"stateMutability": "nonpayable",
|
|
14
|
+
"inputs": [
|
|
15
|
+
{"name": "asset", "type": "address"},
|
|
16
|
+
{"name": "amount", "type": "uint256"},
|
|
17
|
+
{"name": "onBehalfOf", "type": "address"},
|
|
18
|
+
{"name": "referralCode", "type": "uint16"},
|
|
19
|
+
],
|
|
20
|
+
"outputs": [],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"name": "deposit",
|
|
24
|
+
"type": "function",
|
|
25
|
+
"stateMutability": "nonpayable",
|
|
26
|
+
"inputs": [
|
|
27
|
+
{"name": "asset", "type": "address"},
|
|
28
|
+
{"name": "amount", "type": "uint256"},
|
|
29
|
+
{"name": "onBehalfOf", "type": "address"},
|
|
30
|
+
{"name": "referralCode", "type": "uint16"},
|
|
31
|
+
],
|
|
32
|
+
"outputs": [],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"name": "withdraw",
|
|
36
|
+
"type": "function",
|
|
37
|
+
"stateMutability": "nonpayable",
|
|
38
|
+
"inputs": [
|
|
39
|
+
{"name": "asset", "type": "address"},
|
|
40
|
+
{"name": "amount", "type": "uint256"},
|
|
41
|
+
{"name": "to", "type": "address"},
|
|
42
|
+
],
|
|
43
|
+
"outputs": [],
|
|
44
|
+
},
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
# Protocol Data Provider ABI for reserve token addresses and user data
|
|
48
|
+
PROTOCOL_DATA_PROVIDER_ABI = [
|
|
49
|
+
{
|
|
50
|
+
"type": "function",
|
|
51
|
+
"stateMutability": "view",
|
|
52
|
+
"name": "getReserveTokensAddresses",
|
|
53
|
+
"inputs": [{"name": "asset", "type": "address"}],
|
|
54
|
+
"outputs": [
|
|
55
|
+
{"name": "aTokenAddress", "type": "address"},
|
|
56
|
+
{"name": "stableDebtTokenAddress", "type": "address"},
|
|
57
|
+
{"name": "variableDebtTokenAddress", "type": "address"},
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"type": "function",
|
|
62
|
+
"stateMutability": "view",
|
|
63
|
+
"name": "getUserReserveData",
|
|
64
|
+
"inputs": [
|
|
65
|
+
{"name": "asset", "type": "address"},
|
|
66
|
+
{"name": "user", "type": "address"},
|
|
67
|
+
],
|
|
68
|
+
"outputs": [
|
|
69
|
+
{"name": "currentATokenBalance", "type": "uint256"},
|
|
70
|
+
{"name": "currentStableDebt", "type": "uint256"},
|
|
71
|
+
{"name": "currentVariableDebt", "type": "uint256"},
|
|
72
|
+
{"name": "liquidityRate", "type": "uint256"},
|
|
73
|
+
{"name": "stableBorrowRate", "type": "uint256"},
|
|
74
|
+
{"name": "variableBorrowRate", "type": "uint256"},
|
|
75
|
+
{"name": "liquidityIndex", "type": "uint256"},
|
|
76
|
+
{"name": "healthFactor", "type": "uint256"},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
]
|
|
80
|
+
|
|
81
|
+
# Wrapped Token Gateway ABI for native token operations
|
|
82
|
+
WRAPPED_TOKEN_GATEWAY_ABI = [
|
|
83
|
+
{
|
|
84
|
+
"type": "function",
|
|
85
|
+
"stateMutability": "view",
|
|
86
|
+
"name": "getWETHAddress",
|
|
87
|
+
"inputs": [],
|
|
88
|
+
"outputs": [{"type": "address"}],
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"type": "function",
|
|
92
|
+
"stateMutability": "payable",
|
|
93
|
+
"name": "depositETH",
|
|
94
|
+
"inputs": [
|
|
95
|
+
{"type": "address"},
|
|
96
|
+
{"type": "address"},
|
|
97
|
+
{"type": "uint16"},
|
|
98
|
+
],
|
|
99
|
+
"outputs": [],
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
"type": "function",
|
|
103
|
+
"stateMutability": "nonpayable",
|
|
104
|
+
"name": "withdrawETH",
|
|
105
|
+
"inputs": [
|
|
106
|
+
{"type": "address"},
|
|
107
|
+
{"type": "uint256"},
|
|
108
|
+
{"type": "address"},
|
|
109
|
+
],
|
|
110
|
+
"outputs": [],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
"type": "function",
|
|
114
|
+
"stateMutability": "payable",
|
|
115
|
+
"name": "repayETH",
|
|
116
|
+
"inputs": [
|
|
117
|
+
{"type": "address"},
|
|
118
|
+
{"type": "uint256"},
|
|
119
|
+
{"type": "address"},
|
|
120
|
+
],
|
|
121
|
+
"outputs": [],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
"type": "function",
|
|
125
|
+
"stateMutability": "nonpayable",
|
|
126
|
+
"name": "borrowETH",
|
|
127
|
+
"inputs": [
|
|
128
|
+
{"type": "address"},
|
|
129
|
+
{"type": "uint256"},
|
|
130
|
+
{"type": "uint16"},
|
|
131
|
+
],
|
|
132
|
+
"outputs": [],
|
|
133
|
+
},
|
|
134
|
+
]
|
|
135
|
+
|
|
136
|
+
# WETH ABI for native token wrapping
|
|
137
|
+
WETH_ABI = [
|
|
138
|
+
{
|
|
139
|
+
"inputs": [],
|
|
140
|
+
"name": "deposit",
|
|
141
|
+
"outputs": [],
|
|
142
|
+
"stateMutability": "payable",
|
|
143
|
+
"type": "function",
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"inputs": [{"name": "wad", "type": "uint256"}],
|
|
147
|
+
"name": "withdraw",
|
|
148
|
+
"outputs": [],
|
|
149
|
+
"stateMutability": "nonpayable",
|
|
150
|
+
"type": "function",
|
|
151
|
+
},
|
|
152
|
+
]
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from loguru import logger
|
|
6
|
+
|
|
7
|
+
from wayfinder_paths.core.clients.ClientManager import ClientManager
|
|
8
|
+
from wayfinder_paths.core.config import StrategyJobConfig
|
|
9
|
+
from wayfinder_paths.core.strategies.Strategy import Strategy
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class StrategyJob:
|
|
13
|
+
def __init__(
|
|
14
|
+
self,
|
|
15
|
+
strategy: Strategy,
|
|
16
|
+
config: StrategyJobConfig,
|
|
17
|
+
clients: dict[str, Any] | None = None,
|
|
18
|
+
skip_auth: bool = False,
|
|
19
|
+
api_key: str | None = None,
|
|
20
|
+
):
|
|
21
|
+
"""
|
|
22
|
+
Initialize a StrategyJob.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
strategy: The strategy to execute.
|
|
26
|
+
config: Strategy job configuration.
|
|
27
|
+
clients: Optional dict of pre-instantiated clients to inject directly.
|
|
28
|
+
skip_auth: If True, skips authentication (for SDK usage).
|
|
29
|
+
api_key: Optional API key for service account authentication.
|
|
30
|
+
If provided, will be passed to ClientManager and strategy.
|
|
31
|
+
"""
|
|
32
|
+
self.strategy = strategy
|
|
33
|
+
self.config = config
|
|
34
|
+
|
|
35
|
+
self.job_id = strategy.name or "unknown"
|
|
36
|
+
self.clients = ClientManager(
|
|
37
|
+
clients=clients, skip_auth=skip_auth, api_key=api_key
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
def _setup_strategy(self):
|
|
41
|
+
"""Setup the strategy instance"""
|
|
42
|
+
if not self.strategy:
|
|
43
|
+
raise ValueError("No strategy provided to StrategyJob")
|
|
44
|
+
|
|
45
|
+
self.strategy.log = self.log
|
|
46
|
+
|
|
47
|
+
def _is_using_api_key(self) -> bool:
|
|
48
|
+
"""Check if API key authentication is being used."""
|
|
49
|
+
if self.clients._api_key:
|
|
50
|
+
return True
|
|
51
|
+
|
|
52
|
+
if self.clients.auth:
|
|
53
|
+
try:
|
|
54
|
+
creds = self.clients.auth._load_config_credentials()
|
|
55
|
+
if creds.get("api_key"):
|
|
56
|
+
return True
|
|
57
|
+
if os.getenv("WAYFINDER_API_KEY"):
|
|
58
|
+
return True
|
|
59
|
+
except Exception:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
return False
|
|
63
|
+
|
|
64
|
+
async def setup(self):
|
|
65
|
+
"""
|
|
66
|
+
Initialize the strategy job and strategy.
|
|
67
|
+
|
|
68
|
+
Sets up authentication and initializes the strategy with merged configuration.
|
|
69
|
+
"""
|
|
70
|
+
self._setup_strategy()
|
|
71
|
+
|
|
72
|
+
# Ensure auth token is set for API calls
|
|
73
|
+
if not self.clients._skip_auth:
|
|
74
|
+
is_api_key_auth = self._is_using_api_key()
|
|
75
|
+
|
|
76
|
+
if is_api_key_auth:
|
|
77
|
+
logger.debug("Using API key authentication")
|
|
78
|
+
if self.clients.auth:
|
|
79
|
+
await self.clients.auth._ensure_bearer_token()
|
|
80
|
+
else:
|
|
81
|
+
# Try to ensure bearer token is set, authenticate if needed
|
|
82
|
+
try:
|
|
83
|
+
if self.clients.auth:
|
|
84
|
+
await self.clients.auth._ensure_bearer_token()
|
|
85
|
+
except (PermissionError, Exception) as e:
|
|
86
|
+
if not isinstance(e, PermissionError):
|
|
87
|
+
logger.warning(
|
|
88
|
+
f"Authentication failed: {e}, trying OAuth fallback"
|
|
89
|
+
)
|
|
90
|
+
username = self.config.user.username
|
|
91
|
+
password = self.config.user.password
|
|
92
|
+
refresh_token = self.config.user.refresh_token
|
|
93
|
+
if refresh_token or (username and password):
|
|
94
|
+
await self.clients.authenticate(
|
|
95
|
+
username=username,
|
|
96
|
+
password=password,
|
|
97
|
+
refresh_token=refresh_token,
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
"Authentication required: provide api_key parameter for service account auth, "
|
|
102
|
+
"or username+password/refresh_token in config.json for personal access"
|
|
103
|
+
) from e
|
|
104
|
+
|
|
105
|
+
existing_cfg = dict(getattr(self.strategy, "config", {}) or {})
|
|
106
|
+
strategy_cfg = dict(self.config.strategy_config or {})
|
|
107
|
+
merged_cfg = {**strategy_cfg, **existing_cfg}
|
|
108
|
+
self.strategy.config = merged_cfg
|
|
109
|
+
self.strategy.clients = self.clients
|
|
110
|
+
await self.strategy.setup()
|
|
111
|
+
|
|
112
|
+
async def execute_strategy(self, action: str, **kwargs) -> dict[str, Any]:
|
|
113
|
+
"""Execute a strategy action (deposit, withdraw, update, status, partial_liquidate)"""
|
|
114
|
+
try:
|
|
115
|
+
if action == "deposit":
|
|
116
|
+
result = await self.strategy.deposit(**kwargs)
|
|
117
|
+
elif action == "withdraw":
|
|
118
|
+
result = await self.strategy.withdraw(**kwargs)
|
|
119
|
+
elif action == "update":
|
|
120
|
+
result = await self.strategy.update()
|
|
121
|
+
elif action == "status":
|
|
122
|
+
result = await self.strategy.status()
|
|
123
|
+
elif action == "partial_liquidate":
|
|
124
|
+
usd_value = kwargs.get("usd_value")
|
|
125
|
+
if usd_value is None:
|
|
126
|
+
result = (
|
|
127
|
+
False,
|
|
128
|
+
"usd_value parameter is required for partial_liquidate",
|
|
129
|
+
)
|
|
130
|
+
else:
|
|
131
|
+
result = await self.strategy.partial_liquidate(usd_value)
|
|
132
|
+
else:
|
|
133
|
+
result = {"success": False, "message": f"Unknown action: {action}"}
|
|
134
|
+
|
|
135
|
+
await self.log(f"Strategy action '{action}' completed: {result}")
|
|
136
|
+
return result
|
|
137
|
+
|
|
138
|
+
except Exception as e:
|
|
139
|
+
error_msg = f"Strategy action '{action}' failed: {str(e)}"
|
|
140
|
+
await self.log(error_msg)
|
|
141
|
+
await self.handle_error({"error": str(e), "action": action})
|
|
142
|
+
return {"success": False, "error": str(e)}
|
|
143
|
+
|
|
144
|
+
async def run_continuous(self, interval_seconds: int | None = None):
|
|
145
|
+
"""Run the strategy continuously at specified intervals"""
|
|
146
|
+
interval = interval_seconds or self.config.system.update_interval
|
|
147
|
+
logger.info(
|
|
148
|
+
f"Starting continuous execution for strategy: {self.strategy.name} with interval {interval}s"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
while True:
|
|
152
|
+
try:
|
|
153
|
+
await self.execute_strategy("update")
|
|
154
|
+
await asyncio.sleep(interval)
|
|
155
|
+
|
|
156
|
+
except asyncio.CancelledError:
|
|
157
|
+
logger.info("Continuous execution cancelled")
|
|
158
|
+
break
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error(f"Error in continuous execution: {str(e)}")
|
|
161
|
+
await asyncio.sleep(interval)
|
|
162
|
+
|
|
163
|
+
async def log(self, msg: str):
|
|
164
|
+
"""Log messages for the job"""
|
|
165
|
+
logger.info(f"Job {self.job_id}: {msg}")
|
|
166
|
+
|
|
167
|
+
async def handle_error(self, error_data: dict[str, Any]) -> None:
|
|
168
|
+
"""
|
|
169
|
+
Handle errors that occur during strategy execution.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
error_data: Dictionary containing error information. Expected keys:
|
|
173
|
+
- error: Error message or exception string
|
|
174
|
+
- action: Strategy action that failed (e.g., "deposit", "update")
|
|
175
|
+
|
|
176
|
+
Note:
|
|
177
|
+
Base implementation is a no-op. Subclasses or external systems
|
|
178
|
+
can override this method to implement custom error handling,
|
|
179
|
+
logging, alerting, or recovery logic.
|
|
180
|
+
"""
|
|
181
|
+
pass
|
|
182
|
+
|
|
183
|
+
async def stop(self):
|
|
184
|
+
"""Stop the strategy job and cleanup"""
|
|
185
|
+
if hasattr(self.strategy, "stop"):
|
|
186
|
+
await self.strategy.stop()
|
|
187
|
+
|
|
188
|
+
logger.info(f"Strategy job {self.job_id} stopped")
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
import yaml
|
|
4
|
+
from pydantic import BaseModel, Field, validator
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class AdapterRequirement(BaseModel):
|
|
8
|
+
name: str = Field(
|
|
9
|
+
..., description="Adapter symbolic name (e.g., BALANCE, HYPERLIQUID)"
|
|
10
|
+
)
|
|
11
|
+
capabilities: list[str] = Field(default_factory=list)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AdapterManifest(BaseModel):
|
|
15
|
+
schema_version: str = Field(default="0.1")
|
|
16
|
+
entrypoint: str
|
|
17
|
+
capabilities: list[str] = Field(default_factory=list)
|
|
18
|
+
dependencies: list[str] = Field(default_factory=list)
|
|
19
|
+
|
|
20
|
+
@validator("entrypoint")
|
|
21
|
+
def validate_entrypoint(cls, v: str) -> str:
|
|
22
|
+
if "." not in v:
|
|
23
|
+
raise ValueError("entrypoint must be a full import path")
|
|
24
|
+
return v
|
|
25
|
+
|
|
26
|
+
@validator("capabilities")
|
|
27
|
+
def validate_capabilities(cls, v: list[str]) -> list[str]:
|
|
28
|
+
if not v:
|
|
29
|
+
raise ValueError("capabilities cannot be empty")
|
|
30
|
+
return v
|
|
31
|
+
|
|
32
|
+
@validator("dependencies")
|
|
33
|
+
def validate_dependencies(cls, v: list[str]) -> list[str]:
|
|
34
|
+
if not v:
|
|
35
|
+
raise ValueError("dependencies cannot be empty")
|
|
36
|
+
return v
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class StrategyManifest(BaseModel):
|
|
40
|
+
schema_version: str = Field(default="0.1")
|
|
41
|
+
entrypoint: str = Field(
|
|
42
|
+
...,
|
|
43
|
+
description="Python path to class, e.g. strategies.funding_rate_strategy.FundingRateStrategy",
|
|
44
|
+
)
|
|
45
|
+
name: str | None = Field(
|
|
46
|
+
default=None,
|
|
47
|
+
description="Unique name identifier for this strategy instance. Used to look up dedicated wallet in wallets.json by label.",
|
|
48
|
+
)
|
|
49
|
+
permissions: dict[str, Any] = Field(default_factory=dict)
|
|
50
|
+
adapters: list[AdapterRequirement] = Field(default_factory=list)
|
|
51
|
+
|
|
52
|
+
@validator("entrypoint")
|
|
53
|
+
def validate_entrypoint(cls, v: str) -> str:
|
|
54
|
+
if "." not in v:
|
|
55
|
+
raise ValueError(
|
|
56
|
+
"entrypoint must be a full import path to a Strategy class"
|
|
57
|
+
)
|
|
58
|
+
return v
|
|
59
|
+
|
|
60
|
+
@validator("permissions")
|
|
61
|
+
def validate_permissions(cls, v: dict) -> dict:
|
|
62
|
+
if "policy" not in v:
|
|
63
|
+
raise ValueError("permissions.policy is required")
|
|
64
|
+
if not v["policy"]:
|
|
65
|
+
raise ValueError("permissions.policy cannot be empty")
|
|
66
|
+
return v
|
|
67
|
+
|
|
68
|
+
@validator("adapters")
|
|
69
|
+
def validate_adapters(cls, v: list) -> list:
|
|
70
|
+
if not v:
|
|
71
|
+
raise ValueError("adapters cannot be empty")
|
|
72
|
+
return v
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def load_adapter_manifest(path: str) -> AdapterManifest:
|
|
76
|
+
with open(path) as f:
|
|
77
|
+
data = yaml.safe_load(f)
|
|
78
|
+
return AdapterManifest(**data)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def load_strategy_manifest(path: str) -> StrategyManifest:
|
|
82
|
+
with open(path) as f:
|
|
83
|
+
data = yaml.safe_load(f)
|
|
84
|
+
return StrategyManifest(**data)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def load_manifest(path: str) -> StrategyManifest:
|
|
88
|
+
"""Legacy function for backward compatibility."""
|
|
89
|
+
return load_strategy_manifest(path)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def validate_manifest(manifest: StrategyManifest) -> None:
|
|
93
|
+
# Simple v0.1 rules: require at least one adapter and permissions.policy
|
|
94
|
+
if not manifest.adapters:
|
|
95
|
+
raise ValueError("Manifest must declare at least one adapter")
|
|
96
|
+
if "policy" not in manifest.permissions:
|
|
97
|
+
raise ValueError("Manifest.permissions must include 'policy'")
|
|
File without changes
|