wayfinder-paths 0.1.2__py3-none-any.whl → 0.1.4__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 +23 -18
- wayfinder_paths/abis/generic/erc20.json +383 -0
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/README.md +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/test_adapter.py +12 -6
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/README.md +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/test_adapter.py +2 -2
- wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
- wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/test_adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/README.md +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/test_adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/README.md +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/test_adapter.py +2 -2
- wayfinder_paths/{vaults/adapters → adapters}/token_adapter/README.md +1 -1
- wayfinder_paths/adapters/token_adapter/examples.json +26 -0
- wayfinder_paths/{vaults/adapters → adapters}/token_adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/adapters → adapters}/token_adapter/test_adapter.py +1 -1
- wayfinder_paths/config.example.json +3 -1
- wayfinder_paths/core/engine/manifest.py +1 -1
- wayfinder_paths/core/strategies/Strategy.py +3 -3
- wayfinder_paths/core/strategies/descriptors.py +80 -0
- wayfinder_paths/core/utils/evm_helpers.py +39 -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 +3 -3
- wayfinder_paths/scripts/create_strategy.py +3 -3
- wayfinder_paths/scripts/validate_manifests.py +2 -2
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/README.md +4 -3
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/manifest.yaml +1 -1
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/strategy.py +143 -81
- wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/test_strategy.py +4 -2
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/README.md +4 -3
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/manifest.yaml +1 -1
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/strategy.py +117 -80
- wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/test_strategy.py +2 -2
- wayfinder_paths/{vaults/templates → templates}/adapter/README.md +4 -4
- wayfinder_paths/{vaults/templates → templates}/adapter/manifest.yaml +1 -1
- wayfinder_paths/{vaults/templates → templates}/strategy/README.md +6 -5
- wayfinder_paths/{vaults/templates → templates}/strategy/manifest.yaml +1 -1
- wayfinder_paths/{vaults/templates → templates}/strategy/test_strategy.py +1 -1
- {wayfinder_paths-0.1.2.dist-info → wayfinder_paths-0.1.4.dist-info}/METADATA +101 -23
- wayfinder_paths-0.1.4.dist-info/RECORD +125 -0
- wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +0 -7
- wayfinder_paths/vaults/adapters/token_adapter/examples.json +0 -26
- wayfinder_paths/vaults/strategies/__init__.py +0 -0
- wayfinder_paths-0.1.2.dist-info/RECORD +0 -115
- /wayfinder_paths/{vaults → adapters}/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/token_adapter/__init__.py +0 -0
- /wayfinder_paths/{vaults/adapters → adapters}/token_adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/adapters → strategies}/__init__.py +0 -0
- /wayfinder_paths/{vaults/strategies → strategies}/config.py +0 -0
- /wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/examples.json +0 -0
- /wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/examples.json +0 -0
- /wayfinder_paths/{vaults/templates → templates}/adapter/adapter.py +0 -0
- /wayfinder_paths/{vaults/templates → templates}/adapter/examples.json +0 -0
- /wayfinder_paths/{vaults/templates → templates}/adapter/test_adapter.py +0 -0
- /wayfinder_paths/{vaults/templates → templates}/strategy/examples.json +0 -0
- /wayfinder_paths/{vaults/templates → templates}/strategy/strategy.py +0 -0
- {wayfinder_paths-0.1.2.dist-info → wayfinder_paths-0.1.4.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.2.dist-info → wayfinder_paths-0.1.4.dist-info}/WHEEL +0 -0
|
@@ -25,7 +25,7 @@ The LedgerClient will automatically:
|
|
|
25
25
|
### Initialize the Adapter
|
|
26
26
|
|
|
27
27
|
```python
|
|
28
|
-
from wayfinder_paths.
|
|
28
|
+
from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
|
|
29
29
|
|
|
30
30
|
# No configuration needed - uses LedgerClient with automatic settings
|
|
31
31
|
adapter = LedgerAdapter()
|
|
@@ -2,7 +2,7 @@ from unittest.mock import AsyncMock, patch
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from wayfinder_paths.
|
|
5
|
+
from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TestLedgerAdapter:
|
|
@@ -18,7 +18,7 @@ class TestLedgerAdapter:
|
|
|
18
18
|
def adapter(self, mock_ledger_client):
|
|
19
19
|
"""Create a LedgerAdapter instance with mocked client for testing"""
|
|
20
20
|
with patch(
|
|
21
|
-
"
|
|
21
|
+
"adapters.ledger_adapter.adapter.LedgerClient",
|
|
22
22
|
return_value=mock_ledger_client,
|
|
23
23
|
):
|
|
24
24
|
return LedgerAdapter()
|
|
@@ -24,7 +24,7 @@ The PoolClient will automatically:
|
|
|
24
24
|
### Initialize the Adapter
|
|
25
25
|
|
|
26
26
|
```python
|
|
27
|
-
from wayfinder_paths.
|
|
27
|
+
from wayfinder_paths.adapters.pool_adapter.adapter import PoolAdapter
|
|
28
28
|
|
|
29
29
|
# No configuration needed - uses PoolClient with automatic settings
|
|
30
30
|
adapter = PoolAdapter()
|
|
@@ -2,7 +2,7 @@ from unittest.mock import AsyncMock, patch
|
|
|
2
2
|
|
|
3
3
|
import pytest
|
|
4
4
|
|
|
5
|
-
from wayfinder_paths.
|
|
5
|
+
from wayfinder_paths.adapters.pool_adapter.adapter import PoolAdapter
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class TestPoolAdapter:
|
|
@@ -18,7 +18,7 @@ class TestPoolAdapter:
|
|
|
18
18
|
def adapter(self, mock_pool_client):
|
|
19
19
|
"""Create a PoolAdapter instance with mocked client for testing"""
|
|
20
20
|
with patch(
|
|
21
|
-
"
|
|
21
|
+
"adapters.pool_adapter.adapter.PoolClient",
|
|
22
22
|
return_value=mock_pool_client,
|
|
23
23
|
):
|
|
24
24
|
return PoolAdapter()
|
|
@@ -20,7 +20,7 @@ The TokenClient will automatically:
|
|
|
20
20
|
### Initialize the Adapter
|
|
21
21
|
|
|
22
22
|
```python
|
|
23
|
-
from wayfinder_paths.
|
|
23
|
+
from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
|
|
24
24
|
|
|
25
25
|
# No configuration needed - uses TokenClient with automatic settings
|
|
26
26
|
adapter = TokenAdapter()
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"basic_usage": {
|
|
3
|
+
"description": "Basic usage of TokenAdapter to get token information",
|
|
4
|
+
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Get token by address\nsuccess, data = await adapter.get_token_by_address(\"0x1234567890abcdef1234567890abcdef12345678\")\nif success:\n print(f\"Token symbol: {data.get('symbol')}\")\n print(f\"Token name: {data.get('name')}\")\n print(f\"Decimals: {data.get('decimals')}\")\nelse:\n print(f\"Error: {data}\")"
|
|
5
|
+
},
|
|
6
|
+
"get_by_token_id": {
|
|
7
|
+
"description": "Get token information using token ID",
|
|
8
|
+
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Get token by ID\nsuccess, data = await adapter.get_token_by_id(\"token-12345\")\nif success:\n print(f\"Token data: {data}\")\nelse:\n print(f\"Error: {data}\")"
|
|
9
|
+
},
|
|
10
|
+
"flexible_lookup": {
|
|
11
|
+
"description": "Use flexible get_token method that tries both address and token_id",
|
|
12
|
+
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Try both address and token_id\nsuccess, data = await adapter.get_token(\n address=\"0x1234567890abcdef1234567890abcdef12345678\",\n token_id=\"token-12345\"\n)\nif success:\n print(f\"Found token: {data}\")\nelse:\n print(f\"Token not found: {data}\")"
|
|
13
|
+
},
|
|
14
|
+
"error_handling": {
|
|
15
|
+
"description": "Proper error handling for various scenarios",
|
|
16
|
+
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Handle missing parameters\ntry:\n success, data = await adapter.get_token()\n if not success:\n print(f\"Error: {data}\")\nexcept Exception as e:\n print(f\"Unexpected error: {e}\")\n\n# Handle API errors\nsuccess, data = await adapter.get_token_by_address(\"invalid-address\")\nif not success:\n print(f\"API error: {data}\")\nelse:\n print(f\"Token found: {data}\")"
|
|
17
|
+
},
|
|
18
|
+
"health_check": {
|
|
19
|
+
"description": "Check adapter health and connectivity",
|
|
20
|
+
"code": "from adapters.token_adapter.adapter import TokenAdapter\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# Check health\nhealth = await adapter.health_check()\nprint(f\"Adapter status: {health['status']}\")\nprint(f\"Connected: {health['connected']}\")\nprint(f\"Adapter type: {health['adapter']}\")\n\nif health['status'] == 'healthy':\n print(\"Adapter is ready to use\")\nelse:\n print(f\"Adapter has issues: {health.get('error', 'Unknown error')}\")"
|
|
21
|
+
},
|
|
22
|
+
"batch_operations": {
|
|
23
|
+
"description": "Perform multiple token lookups efficiently",
|
|
24
|
+
"code": "from adapters.token_adapter.adapter import TokenAdapter\nimport asyncio\n\n# Initialize adapter (no config needed)\nadapter = TokenAdapter()\n\n# List of token addresses to lookup\ntoken_addresses = [\n \"0x1234567890abcdef1234567890abcdef12345678\",\n \"0xabcdef1234567890abcdef1234567890abcdef12\",\n \"0x9876543210fedcba9876543210fedcba98765432\"\n]\n\n# Batch lookup\ntoken_data = {}\nfor address in token_addresses:\n success, data = await adapter.get_token_by_address(address)\n if success:\n token_data[address] = data\n else:\n print(f\"Failed to get token for {address}: {data}\")\n\nprint(f\"Successfully retrieved {len(token_data)} tokens\")\nfor address, data in token_data.items():\n print(f\"{address}: {data.get('symbol', 'Unknown')} - {data.get('name', 'Unknown')}\")"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
"user": {
|
|
3
3
|
"username": "your_username",
|
|
4
4
|
"password": "your_password",
|
|
5
|
-
"refresh_token": null
|
|
5
|
+
"refresh_token": null,
|
|
6
|
+
"api_key": "sk_live_abc123..."
|
|
6
7
|
},
|
|
7
8
|
"system": {
|
|
8
9
|
"api_base_url": "https://wayfinder.ai/api/v1",
|
|
10
|
+
"api_key": "sk_live_abc123...",
|
|
9
11
|
"wallets_path": "wallets.json"
|
|
10
12
|
},
|
|
11
13
|
"strategy": {
|
|
@@ -40,7 +40,7 @@ class StrategyManifest(BaseModel):
|
|
|
40
40
|
schema_version: str = Field(default="0.1")
|
|
41
41
|
entrypoint: str = Field(
|
|
42
42
|
...,
|
|
43
|
-
description="Python path to class, e.g.
|
|
43
|
+
description="Python path to class, e.g. strategies.funding_rate_strategy.FundingRateStrategy",
|
|
44
44
|
)
|
|
45
45
|
name: str | None = Field(
|
|
46
46
|
default=None,
|
|
@@ -6,6 +6,7 @@ from typing import Any, TypedDict
|
|
|
6
6
|
from loguru import logger
|
|
7
7
|
|
|
8
8
|
from wayfinder_paths.core.services.base import Web3Service
|
|
9
|
+
from wayfinder_paths.core.strategies.descriptors import StratDescriptor
|
|
9
10
|
|
|
10
11
|
|
|
11
12
|
class StatusDict(TypedDict):
|
|
@@ -19,8 +20,7 @@ StatusTuple = tuple[bool, str]
|
|
|
19
20
|
|
|
20
21
|
class Strategy(ABC):
|
|
21
22
|
name: str = None
|
|
22
|
-
|
|
23
|
-
summary: str = None
|
|
23
|
+
INFO: StratDescriptor = None
|
|
24
24
|
|
|
25
25
|
def __init__(
|
|
26
26
|
self,
|
|
@@ -90,7 +90,7 @@ class Strategy(ABC):
|
|
|
90
90
|
pass
|
|
91
91
|
|
|
92
92
|
@staticmethod
|
|
93
|
-
def policies() -> list[str]:
|
|
93
|
+
async def policies() -> list[str]:
|
|
94
94
|
"""Return policy strings for this strategy (Django-compatible)."""
|
|
95
95
|
raise NotImplementedError
|
|
96
96
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Volatility(Enum):
|
|
8
|
+
LOW = "low"
|
|
9
|
+
MEDIUM = "medium"
|
|
10
|
+
HIGH = "high"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Frequency(Enum):
|
|
14
|
+
LOW = "low"
|
|
15
|
+
MEDIUM = "medium"
|
|
16
|
+
HIGH = "high"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Directionality(Enum):
|
|
20
|
+
LONG = "Long"
|
|
21
|
+
SHORT = "Short"
|
|
22
|
+
MARKET_NEUTRAL = "market_neutral"
|
|
23
|
+
DELTA_NEUTRAL = "delta_neutral"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class Complexity(Enum):
|
|
27
|
+
LOW = "low"
|
|
28
|
+
MEDIUM = "medium"
|
|
29
|
+
HIGH = "high"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TokenExposure(Enum):
|
|
33
|
+
STABLECOINS = "Stablecoins"
|
|
34
|
+
MAJORS = "Majors"
|
|
35
|
+
ALTS = "Alts"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Default token rewards for all strategies
|
|
39
|
+
DEFAULT_TOKEN_REWARDS = [
|
|
40
|
+
{
|
|
41
|
+
"symbol": "B3",
|
|
42
|
+
"name": "B3",
|
|
43
|
+
"address": "0xB3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3B3",
|
|
44
|
+
"image_url": "https://storage.googleapis.com/prod-wayfinder-app-assets/asset-icons/B3.png",
|
|
45
|
+
},
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class StratDescriptor(BaseModel):
|
|
50
|
+
description: str
|
|
51
|
+
|
|
52
|
+
summary: str
|
|
53
|
+
|
|
54
|
+
gas_token_symbol: str
|
|
55
|
+
gas_token_id: str
|
|
56
|
+
deposit_token_id: str
|
|
57
|
+
minimum_net_deposit: float
|
|
58
|
+
gas_maximum: float
|
|
59
|
+
gas_threshold: float
|
|
60
|
+
|
|
61
|
+
available_rewards: dict[str, Any] = {
|
|
62
|
+
"token_rewards": DEFAULT_TOKEN_REWARDS,
|
|
63
|
+
"point_rewards": None,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# risk indicators
|
|
67
|
+
volatility: Volatility
|
|
68
|
+
volatility_description_short: str
|
|
69
|
+
directionality: Directionality
|
|
70
|
+
directionality_description: str
|
|
71
|
+
complexity: Complexity
|
|
72
|
+
complexity_description: str
|
|
73
|
+
token_exposure: TokenExposure
|
|
74
|
+
token_exposure_description: str
|
|
75
|
+
frequency: Frequency
|
|
76
|
+
frequency_description: str
|
|
77
|
+
|
|
78
|
+
return_drivers: list[str]
|
|
79
|
+
|
|
80
|
+
config: dict[str, Any]
|
|
@@ -5,7 +5,9 @@ This module provides reusable functions for EVM-related operations that are shar
|
|
|
5
5
|
across multiple adapters, extracted from evm_transaction_adapter.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
+
import json
|
|
8
9
|
import os
|
|
10
|
+
from pathlib import Path
|
|
9
11
|
from typing import Any
|
|
10
12
|
|
|
11
13
|
from loguru import logger
|
|
@@ -163,3 +165,40 @@ def resolve_private_key_for_from_address(
|
|
|
163
165
|
|
|
164
166
|
# Fallback to environment variables
|
|
165
167
|
return os.getenv("PRIVATE_KEY_VAULT") or os.getenv("PRIVATE_KEY")
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
async def _get_abi(chain_id: int, address: str) -> str | None:
|
|
171
|
+
os.makedirs(f"abis/{chain_id}/", exist_ok=True)
|
|
172
|
+
|
|
173
|
+
abi_file = f"abis/{chain_id}/{address}.json"
|
|
174
|
+
if not os.path.exists(abi_file):
|
|
175
|
+
raise ValueError(
|
|
176
|
+
f"There is no downloaded ABI for {address} on chain {chain_id} -- please download it to ({abi_file}) (make sure to get the implementation if this address is a proxy)"
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
with open(abi_file) as f:
|
|
180
|
+
abi = f.read()
|
|
181
|
+
|
|
182
|
+
return abi
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# We filter ABIs for Privy Policy since most of the abi is useless, and we don't wanna upload big ABIs for both size and readability reasons.
|
|
186
|
+
async def get_abi_filtered(
|
|
187
|
+
chain_id: int, address: str, function_names: list[str]
|
|
188
|
+
) -> list | None:
|
|
189
|
+
full_abi = await _get_abi(chain_id, address)
|
|
190
|
+
if full_abi is None:
|
|
191
|
+
raise Exception("Could not pull ABI, get_abi returned None")
|
|
192
|
+
abi_json = json.loads(full_abi)
|
|
193
|
+
filtered_abi = [
|
|
194
|
+
item
|
|
195
|
+
for item in abi_json
|
|
196
|
+
if item.get("type") == "function" and item.get("name") in function_names
|
|
197
|
+
]
|
|
198
|
+
return filtered_abi
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
with open(Path(__file__).parent.parent.parent.joinpath("abis/generic/erc20.json")) as f:
|
|
202
|
+
erc20_abi_raw = f.read()
|
|
203
|
+
|
|
204
|
+
ERC20_ABI = json.loads(erc20_abi_raw)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from wayfinder_paths.policies.util import allow_functions
|
|
2
|
+
|
|
3
|
+
ENSO_ROUTER = "0xf75584ef6673ad213a685a1b58cc0330b8ea22cf"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def enso_swap():
|
|
7
|
+
return await allow_functions(
|
|
8
|
+
policy_name="Allow Enso Swap",
|
|
9
|
+
abi_chain_id=8453,
|
|
10
|
+
address=ENSO_ROUTER,
|
|
11
|
+
function_names=[
|
|
12
|
+
"routeMulti",
|
|
13
|
+
"routeSingle",
|
|
14
|
+
"safeRouteMulti",
|
|
15
|
+
"safeRouteSingle",
|
|
16
|
+
],
|
|
17
|
+
)
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from wayfinder_paths.core.utils.evm_helpers import ERC20_ABI
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def any_erc20_function(token_address: str) -> dict:
|
|
5
|
+
return {
|
|
6
|
+
"name": "Allow Any ERC20 Transfer To Address",
|
|
7
|
+
"method": "eth_signTransaction",
|
|
8
|
+
"action": "ALLOW",
|
|
9
|
+
"conditions": [
|
|
10
|
+
{
|
|
11
|
+
"field_source": "ethereum_transaction",
|
|
12
|
+
"field": "to",
|
|
13
|
+
"operator": "eq",
|
|
14
|
+
"value": token_address,
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def erc20_spender_for_any_token(spender_address: str) -> dict:
|
|
21
|
+
return {
|
|
22
|
+
"name": "Allow Any ERC20 Approve To Spender",
|
|
23
|
+
"method": "eth_signTransaction",
|
|
24
|
+
"action": "ALLOW",
|
|
25
|
+
"conditions": [
|
|
26
|
+
{
|
|
27
|
+
"field_source": "ethereum_calldata",
|
|
28
|
+
"field": "approve.spender",
|
|
29
|
+
"abi": ERC20_ABI,
|
|
30
|
+
"operator": "eq",
|
|
31
|
+
"value": spender_address,
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
def native_transfer(destination_address: str, value: int) -> dict:
|
|
2
|
+
# TODO THIS FUNCTION IS NOT DONE CAUSE POLICIES DONT KNOW THE WALLET ADDRESS YET.
|
|
3
|
+
return {
|
|
4
|
+
"name": "Allow Native Transfer To Address",
|
|
5
|
+
"method": "eth_signTransaction",
|
|
6
|
+
"action": "ALLOW",
|
|
7
|
+
"conditions": [
|
|
8
|
+
{
|
|
9
|
+
"field_source": "ethereum_transaction",
|
|
10
|
+
"field": "to",
|
|
11
|
+
"operator": "eq",
|
|
12
|
+
"value": destination_address,
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"field_source": "ethereum_transaction",
|
|
16
|
+
"field": "value",
|
|
17
|
+
"operator": "eq",
|
|
18
|
+
"value": hex(value),
|
|
19
|
+
},
|
|
20
|
+
],
|
|
21
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from wayfinder_paths.policies.evm import native_transfer
|
|
2
|
+
from wayfinder_paths.policies.util import allow_functions
|
|
3
|
+
|
|
4
|
+
WHYPE_TOKEN = "0x5555555555555555555555555555555555555555"
|
|
5
|
+
HYPERCORE_SENTINEL_ADDRESS = "0x2222222222222222222222222222222222222222"
|
|
6
|
+
HYPERCORE_SENTINEL_VALUE = 100_000_000_000
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def hypecore_sentinel_deposit():
|
|
10
|
+
return native_transfer(HYPERCORE_SENTINEL_ADDRESS, HYPERCORE_SENTINEL_VALUE)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
async def whype_deposit_and_withdraw():
|
|
14
|
+
return await allow_functions(
|
|
15
|
+
policy_name="Allow WHYPE Deposit and Withdraw",
|
|
16
|
+
abi_chain_id=999,
|
|
17
|
+
address=WHYPE_TOKEN,
|
|
18
|
+
function_names=["deposit", "withdraw"],
|
|
19
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from wayfinder_paths.policies.util import allow_functions
|
|
2
|
+
|
|
3
|
+
HYPERLEND_POOL = "0x00A89d7a5A02160f20150EbEA7a2b5E4879A1A8b"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
async def hyperlend_supply_and_withdraw():
|
|
7
|
+
return await allow_functions(
|
|
8
|
+
policy_name="Allow Hyperlend Supply and Withdraw",
|
|
9
|
+
abi_chain_id=999,
|
|
10
|
+
address=HYPERLEND_POOL,
|
|
11
|
+
function_names=["supply", "withdraw"],
|
|
12
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
def any_hyperliquid_l1_payload():
|
|
2
|
+
return {
|
|
3
|
+
"name": "Allow Hypecore L1 Payload",
|
|
4
|
+
"method": "eth_signTypedData_v4",
|
|
5
|
+
"action": "ALLOW",
|
|
6
|
+
"conditions": [
|
|
7
|
+
{
|
|
8
|
+
"field": "chainId",
|
|
9
|
+
"field_source": "ethereum_typed_data_domain",
|
|
10
|
+
"operator": "eq",
|
|
11
|
+
"value": "1337",
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def any_hyperliquid_user_payload():
|
|
18
|
+
return {
|
|
19
|
+
"name": "Allow User Signed Payload",
|
|
20
|
+
"method": "eth_signTypedData_v4",
|
|
21
|
+
"action": "ALLOW",
|
|
22
|
+
"conditions": [
|
|
23
|
+
{
|
|
24
|
+
"field": "chainId",
|
|
25
|
+
"field_source": "ethereum_typed_data_domain",
|
|
26
|
+
"operator": "eq",
|
|
27
|
+
"value": "421614",
|
|
28
|
+
}
|
|
29
|
+
],
|
|
30
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
from wayfinder_paths.policies.util import allow_functions
|
|
2
|
+
|
|
3
|
+
WETH = "0x4200000000000000000000000000000000000006"
|
|
4
|
+
|
|
5
|
+
M_USDC = "0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22"
|
|
6
|
+
M_WETH = "0x628ff693426583D9a7FB391E54366292F509D457"
|
|
7
|
+
M_WSTETH = "0x627Fe393Bc6EdDA28e99AE648fD6fF362514304b"
|
|
8
|
+
|
|
9
|
+
COMPTROLLER = "0xfbb21d0380bee3312b33c4353c8936a0f13ef26c"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
async def weth_deposit():
|
|
13
|
+
return await allow_functions(
|
|
14
|
+
policy_name="Allow WETH Deposit",
|
|
15
|
+
abi_chain_id=8453,
|
|
16
|
+
address=WETH,
|
|
17
|
+
function_names=["deposit"],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def musdc_mint_or_approve_or_redeem():
|
|
22
|
+
return await allow_functions(
|
|
23
|
+
policy_name="Allow MUSDC Mint or Approve or Redeem",
|
|
24
|
+
abi_chain_id=8453,
|
|
25
|
+
address=M_USDC,
|
|
26
|
+
function_names=["mint", "approve", "redeem"],
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def mweth_approve_or_borrow_or_repay():
|
|
31
|
+
return await allow_functions(
|
|
32
|
+
policy_name="Allow MWETH Approve or Borrow or Repay",
|
|
33
|
+
abi_chain_id=8453,
|
|
34
|
+
address=M_WETH,
|
|
35
|
+
function_names=["approve", "borrow", "repayBorrow"],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
async def mwsteth_approve_or_mint_or_redeem():
|
|
40
|
+
return await allow_functions(
|
|
41
|
+
policy_name="Allow MWSTETH Approve or Mint or Redeem",
|
|
42
|
+
abi_chain_id=8453,
|
|
43
|
+
address=M_WSTETH,
|
|
44
|
+
function_names=["approve", "mint", "redeem"],
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def moonwell_comptroller_enter_markets_or_claim_rewards():
|
|
49
|
+
return await allow_functions(
|
|
50
|
+
policy_name="Allow Moonwell Comptroller Enter Markets or Claim Rewards",
|
|
51
|
+
abi_chain_id=8453,
|
|
52
|
+
address=COMPTROLLER,
|
|
53
|
+
function_names=["enterMarkets", "claimReward"],
|
|
54
|
+
)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from wayfinder_paths.policies.util import allow_functions
|
|
2
|
+
|
|
3
|
+
PRJX_ROUTER = "0x1ebdfc75ffe3ba3de61e7138a3e8706ac841af9b"
|
|
4
|
+
PRJX_NPM = "0xeAd19AE861c29bBb2101E834922B2FEee69B9091"
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
async def prjx_swap():
|
|
8
|
+
return await allow_functions(
|
|
9
|
+
policy_name="Allow PRJX Swap",
|
|
10
|
+
abi_chain_id=999,
|
|
11
|
+
address=PRJX_ROUTER,
|
|
12
|
+
function_names=[
|
|
13
|
+
"exactInput",
|
|
14
|
+
"exactInputSingle",
|
|
15
|
+
"exactOutput",
|
|
16
|
+
"exactOutputSingle",
|
|
17
|
+
],
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
async def prjx_npm():
|
|
22
|
+
return await allow_functions(
|
|
23
|
+
policy_name="Allow PRJX NPM",
|
|
24
|
+
abi_chain_id=999,
|
|
25
|
+
address=PRJX_NPM,
|
|
26
|
+
function_names=[
|
|
27
|
+
"increaseLiquidity",
|
|
28
|
+
"decreaseLiquidity",
|
|
29
|
+
],
|
|
30
|
+
)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from wayfinder_paths.core.utils.evm_helpers import get_abi_filtered
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
async def allow_functions(
|
|
5
|
+
policy_name: str, abi_chain_id: int, address: str, function_names: list[str]
|
|
6
|
+
):
|
|
7
|
+
# Note: ChainID is just for fetching ABI, doesn't appear in the final policy. Doesn't bind a strict chain.
|
|
8
|
+
return {
|
|
9
|
+
"name": policy_name,
|
|
10
|
+
"method": "eth_signTransaction",
|
|
11
|
+
"action": "ALLOW",
|
|
12
|
+
"conditions": [
|
|
13
|
+
{
|
|
14
|
+
"field_source": "ethereum_transaction",
|
|
15
|
+
"field": "to",
|
|
16
|
+
"operator": "eq",
|
|
17
|
+
"value": address,
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"field_source": "ethereum_calldata",
|
|
21
|
+
"field": "function_name",
|
|
22
|
+
"abi": await get_abi_filtered(abi_chain_id, address, function_names),
|
|
23
|
+
"operator": "in",
|
|
24
|
+
"value": function_names,
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
}
|
wayfinder_paths/run_strategy.py
CHANGED
|
@@ -28,7 +28,7 @@ def load_strategy(
|
|
|
28
28
|
Dynamically load a strategy by name using its manifest
|
|
29
29
|
|
|
30
30
|
Args:
|
|
31
|
-
strategy_name: Name of the strategy to load (directory name in
|
|
31
|
+
strategy_name: Name of the strategy to load (directory name in strategies/)
|
|
32
32
|
strategy_config: Configuration dict for the strategy
|
|
33
33
|
simulation: Enable simulation mode for testing
|
|
34
34
|
api_key: Optional API key for service account authentication
|
|
@@ -37,7 +37,7 @@ def load_strategy(
|
|
|
37
37
|
Strategy instance
|
|
38
38
|
"""
|
|
39
39
|
# Find strategy manifest by scanning for manifest.yaml in the strategy directory
|
|
40
|
-
strategies_dir = Path(__file__).parent / "
|
|
40
|
+
strategies_dir = Path(__file__).parent / "strategies"
|
|
41
41
|
strategy_dir = strategies_dir / strategy_name
|
|
42
42
|
manifest_path = strategy_dir / "manifest.yaml"
|
|
43
43
|
|
|
@@ -120,7 +120,7 @@ async def run_strategy(
|
|
|
120
120
|
# Extract directory name from manifest path for wallet lookup
|
|
121
121
|
# Use the directory name (strategy identifier) for wallet lookup
|
|
122
122
|
manifest_dir = Path(manifest_path).parent
|
|
123
|
-
strategies_dir = Path(__file__).parent / "
|
|
123
|
+
strategies_dir = Path(__file__).parent / "strategies"
|
|
124
124
|
try:
|
|
125
125
|
# Try to get relative path - if it's under strategies_dir, use directory name
|
|
126
126
|
rel_path = manifest_dir.relative_to(strategies_dir)
|
|
@@ -64,13 +64,13 @@ def main():
|
|
|
64
64
|
parser.add_argument(
|
|
65
65
|
"--template-dir",
|
|
66
66
|
type=Path,
|
|
67
|
-
default=Path(__file__).parent.parent / "
|
|
67
|
+
default=Path(__file__).parent.parent / "templates" / "strategy",
|
|
68
68
|
help="Path to strategy template directory",
|
|
69
69
|
)
|
|
70
70
|
parser.add_argument(
|
|
71
71
|
"--strategies-dir",
|
|
72
72
|
type=Path,
|
|
73
|
-
default=Path(__file__).parent.parent / "
|
|
73
|
+
default=Path(__file__).parent.parent / "strategies",
|
|
74
74
|
help="Path to strategies directory",
|
|
75
75
|
)
|
|
76
76
|
parser.add_argument(
|
|
@@ -136,7 +136,7 @@ def main():
|
|
|
136
136
|
print(f" Updated strategy.py with class name: {class_name}")
|
|
137
137
|
|
|
138
138
|
# Generate entrypoint path
|
|
139
|
-
entrypoint = f"
|
|
139
|
+
entrypoint = f"strategies.{dir_name}.strategy.{class_name}"
|
|
140
140
|
|
|
141
141
|
# Update manifest with name (using directory name) and entrypoint
|
|
142
142
|
manifest_path = strategy_dir / "manifest.yaml"
|
|
@@ -138,7 +138,7 @@ def validate_strategy_manifest(manifest_path: str) -> tuple[bool, list[str]]:
|
|
|
138
138
|
def find_adapter_manifests() -> list[Path]:
|
|
139
139
|
"""Find all adapter manifest files."""
|
|
140
140
|
manifests = []
|
|
141
|
-
adapter_dir = Path(__file__).parent.parent / "
|
|
141
|
+
adapter_dir = Path(__file__).parent.parent / "adapters"
|
|
142
142
|
if adapter_dir.exists():
|
|
143
143
|
for adapter_path in adapter_dir.iterdir():
|
|
144
144
|
manifest_path = adapter_path / "manifest.yaml"
|
|
@@ -150,7 +150,7 @@ def find_adapter_manifests() -> list[Path]:
|
|
|
150
150
|
def find_strategy_manifests() -> list[Path]:
|
|
151
151
|
"""Find all strategy manifest files."""
|
|
152
152
|
manifests = []
|
|
153
|
-
strategy_dir = Path(__file__).parent.parent / "
|
|
153
|
+
strategy_dir = Path(__file__).parent.parent / "strategies"
|
|
154
154
|
if strategy_dir.exists():
|
|
155
155
|
for strategy_path in strategy_dir.iterdir():
|
|
156
156
|
manifest_path = strategy_path / "manifest.yaml"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Hyperlend Stable Yield Strategy
|
|
2
2
|
|
|
3
|
-
- Entrypoint: `
|
|
3
|
+
- Entrypoint: `strategies.hyperlend_stable_yield_strategy.strategy.HyperlendStableYieldStrategy`
|
|
4
4
|
- Manifest: `manifest.yaml`
|
|
5
5
|
- Examples: `examples.json`
|
|
6
6
|
- Tests: `test_strategy.py`
|
|
@@ -75,8 +75,9 @@ The manifest policy simply locks transactions to the vault wallet ID:
|
|
|
75
75
|
# Install dependencies
|
|
76
76
|
poetry install
|
|
77
77
|
|
|
78
|
-
# Generate
|
|
79
|
-
|
|
78
|
+
# Generate main wallet (writes wallets.json)
|
|
79
|
+
# Creates a main wallet (or use 'just create-strategy' which auto-creates wallets)
|
|
80
|
+
poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
|
|
80
81
|
|
|
81
82
|
# Copy config and edit credentials (or rely on env vars)
|
|
82
83
|
cp wayfinder_paths/config.example.json config.json
|