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.

Files changed (83) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +23 -18
  2. wayfinder_paths/abis/generic/erc20.json +383 -0
  3. wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/README.md +2 -2
  4. wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/adapter.py +2 -2
  5. wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/manifest.yaml +1 -1
  6. wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/test_adapter.py +12 -6
  7. wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/README.md +1 -1
  8. wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/adapter.py +2 -2
  9. wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/manifest.yaml +1 -1
  10. wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/test_adapter.py +2 -2
  11. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
  12. wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/manifest.yaml +1 -1
  13. wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/test_adapter.py +2 -2
  14. wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/README.md +1 -1
  15. wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/manifest.yaml +1 -1
  16. wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/test_adapter.py +2 -2
  17. wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/README.md +1 -1
  18. wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/manifest.yaml +1 -1
  19. wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/test_adapter.py +2 -2
  20. wayfinder_paths/{vaults/adapters → adapters}/token_adapter/README.md +1 -1
  21. wayfinder_paths/adapters/token_adapter/examples.json +26 -0
  22. wayfinder_paths/{vaults/adapters → adapters}/token_adapter/manifest.yaml +1 -1
  23. wayfinder_paths/{vaults/adapters → adapters}/token_adapter/test_adapter.py +1 -1
  24. wayfinder_paths/config.example.json +3 -1
  25. wayfinder_paths/core/engine/manifest.py +1 -1
  26. wayfinder_paths/core/strategies/Strategy.py +3 -3
  27. wayfinder_paths/core/strategies/descriptors.py +80 -0
  28. wayfinder_paths/core/utils/evm_helpers.py +39 -0
  29. wayfinder_paths/policies/enso.py +17 -0
  30. wayfinder_paths/policies/erc20.py +34 -0
  31. wayfinder_paths/policies/evm.py +21 -0
  32. wayfinder_paths/policies/hyper_evm.py +19 -0
  33. wayfinder_paths/policies/hyperlend.py +12 -0
  34. wayfinder_paths/policies/hyperliquid.py +30 -0
  35. wayfinder_paths/policies/moonwell.py +54 -0
  36. wayfinder_paths/policies/prjx.py +30 -0
  37. wayfinder_paths/policies/util.py +27 -0
  38. wayfinder_paths/run_strategy.py +3 -3
  39. wayfinder_paths/scripts/create_strategy.py +3 -3
  40. wayfinder_paths/scripts/validate_manifests.py +2 -2
  41. wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/README.md +4 -3
  42. wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/manifest.yaml +1 -1
  43. wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/strategy.py +143 -81
  44. wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/test_strategy.py +4 -2
  45. wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/README.md +4 -3
  46. wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/manifest.yaml +1 -1
  47. wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/strategy.py +117 -80
  48. wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/test_strategy.py +2 -2
  49. wayfinder_paths/{vaults/templates → templates}/adapter/README.md +4 -4
  50. wayfinder_paths/{vaults/templates → templates}/adapter/manifest.yaml +1 -1
  51. wayfinder_paths/{vaults/templates → templates}/strategy/README.md +6 -5
  52. wayfinder_paths/{vaults/templates → templates}/strategy/manifest.yaml +1 -1
  53. wayfinder_paths/{vaults/templates → templates}/strategy/test_strategy.py +1 -1
  54. {wayfinder_paths-0.1.2.dist-info → wayfinder_paths-0.1.4.dist-info}/METADATA +101 -23
  55. wayfinder_paths-0.1.4.dist-info/RECORD +125 -0
  56. wayfinder_paths/vaults/adapters/hyperlend_adapter/__init__.py +0 -7
  57. wayfinder_paths/vaults/adapters/token_adapter/examples.json +0 -26
  58. wayfinder_paths/vaults/strategies/__init__.py +0 -0
  59. wayfinder_paths-0.1.2.dist-info/RECORD +0 -115
  60. /wayfinder_paths/{vaults → adapters}/__init__.py +0 -0
  61. /wayfinder_paths/{vaults/adapters → adapters}/balance_adapter/examples.json +0 -0
  62. /wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/__init__.py +0 -0
  63. /wayfinder_paths/{vaults/adapters → adapters}/brap_adapter/examples.json +0 -0
  64. /wayfinder_paths/{vaults/adapters → adapters}/hyperlend_adapter/adapter.py +0 -0
  65. /wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/__init__.py +0 -0
  66. /wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/adapter.py +0 -0
  67. /wayfinder_paths/{vaults/adapters → adapters}/ledger_adapter/examples.json +0 -0
  68. /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/__init__.py +0 -0
  69. /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/adapter.py +0 -0
  70. /wayfinder_paths/{vaults/adapters → adapters}/pool_adapter/examples.json +0 -0
  71. /wayfinder_paths/{vaults/adapters → adapters}/token_adapter/__init__.py +0 -0
  72. /wayfinder_paths/{vaults/adapters → adapters}/token_adapter/adapter.py +0 -0
  73. /wayfinder_paths/{vaults/adapters → strategies}/__init__.py +0 -0
  74. /wayfinder_paths/{vaults/strategies → strategies}/config.py +0 -0
  75. /wayfinder_paths/{vaults/strategies → strategies}/hyperlend_stable_yield_strategy/examples.json +0 -0
  76. /wayfinder_paths/{vaults/strategies → strategies}/stablecoin_yield_strategy/examples.json +0 -0
  77. /wayfinder_paths/{vaults/templates → templates}/adapter/adapter.py +0 -0
  78. /wayfinder_paths/{vaults/templates → templates}/adapter/examples.json +0 -0
  79. /wayfinder_paths/{vaults/templates → templates}/adapter/test_adapter.py +0 -0
  80. /wayfinder_paths/{vaults/templates → templates}/strategy/examples.json +0 -0
  81. /wayfinder_paths/{vaults/templates → templates}/strategy/strategy.py +0 -0
  82. {wayfinder_paths-0.1.2.dist-info → wayfinder_paths-0.1.4.dist-info}/LICENSE +0 -0
  83. {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.vaults.adapters.ledger_adapter.adapter import LedgerAdapter
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()
@@ -1,5 +1,5 @@
1
1
  schema_version: "0.1"
2
- entrypoint: "vaults.adapters.ledger_adapter.adapter.LedgerAdapter"
2
+ entrypoint: "adapters.ledger_adapter.adapter.LedgerAdapter"
3
3
  capabilities:
4
4
  - "ledger.read"
5
5
  - "ledger.write"
@@ -2,7 +2,7 @@ from unittest.mock import AsyncMock, patch
2
2
 
3
3
  import pytest
4
4
 
5
- from wayfinder_paths.vaults.adapters.ledger_adapter.adapter import LedgerAdapter
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
- "vaults.adapters.ledger_adapter.adapter.LedgerClient",
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.vaults.adapters.pool_adapter.adapter import PoolAdapter
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()
@@ -1,5 +1,5 @@
1
1
  schema_version: "0.1"
2
- entrypoint: "vaults.adapters.pool_adapter.adapter.PoolAdapter"
2
+ entrypoint: "adapters.pool_adapter.adapter.PoolAdapter"
3
3
  capabilities:
4
4
  - "pool.read"
5
5
  - "pool.analytics"
@@ -2,7 +2,7 @@ from unittest.mock import AsyncMock, patch
2
2
 
3
3
  import pytest
4
4
 
5
- from wayfinder_paths.vaults.adapters.pool_adapter.adapter import PoolAdapter
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
- "vaults.adapters.pool_adapter.adapter.PoolClient",
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.vaults.adapters.token_adapter.adapter import TokenAdapter
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
+ }
@@ -1,5 +1,5 @@
1
1
  schema_version: "0.1"
2
- entrypoint: "vaults.adapters.token_adapter.adapter.TokenAdapter"
2
+ entrypoint: "adapters.token_adapter.adapter.TokenAdapter"
3
3
  capabilities:
4
4
  - "token.read"
5
5
  dependencies:
@@ -2,7 +2,7 @@ from unittest.mock import patch
2
2
 
3
3
  import pytest
4
4
 
5
- from wayfinder_paths.vaults.adapters.token_adapter.adapter import TokenAdapter
5
+ from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
6
6
 
7
7
 
8
8
  class TestTokenAdapter:
@@ -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. vaults.strategies.funding_rate_strategy.FundingRateStrategy",
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
- description: str = None
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
+ }
@@ -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 vaults/strategies/)
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 / "vaults" / "strategies"
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 / "vaults" / "strategies"
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 / "vaults" / "templates" / "strategy",
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 / "vaults" / "strategies",
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"vaults.strategies.{dir_name}.strategy.{class_name}"
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 / "vaults" / "adapters"
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 / "vaults" / "strategies"
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: `vaults.strategies.hyperlend_stable_yield_strategy.strategy.HyperlendStableYieldStrategy`
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 default + vault wallets (writes wallets.json)
79
- poetry run python wayfinder_paths/scripts/make_wallets.py --default --vault
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