wayfinder-paths 0.1.23__py3-none-any.whl → 0.1.24__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 (122) hide show
  1. wayfinder_paths/adapters/balance_adapter/adapter.py +250 -0
  2. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -11
  4. wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
  5. wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
  6. wayfinder_paths/adapters/boros_adapter/client.py +476 -0
  7. wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
  8. wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
  9. wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
  10. wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
  11. wayfinder_paths/adapters/boros_adapter/types.py +70 -0
  12. wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
  13. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -1
  14. wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
  15. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +161 -26
  16. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
  17. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +77 -13
  18. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
  19. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +585 -61
  20. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
  21. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
  22. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
  23. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
  24. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
  27. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
  28. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
  29. wayfinder_paths/adapters/moonwell_adapter/adapter.py +592 -400
  30. wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
  31. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +126 -219
  32. wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
  33. wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
  34. wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
  35. wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
  36. wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
  37. wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
  38. wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
  39. wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
  40. wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
  41. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
  42. wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
  43. wayfinder_paths/adapters/token_adapter/examples.json +0 -4
  44. wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
  45. wayfinder_paths/conftest.py +24 -17
  46. wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
  47. wayfinder_paths/core/adapters/models.py +17 -7
  48. wayfinder_paths/core/clients/BRAPClient.py +1 -1
  49. wayfinder_paths/core/clients/TokenClient.py +47 -1
  50. wayfinder_paths/core/clients/WayfinderClient.py +1 -2
  51. wayfinder_paths/core/clients/protocols.py +21 -22
  52. wayfinder_paths/core/clients/test_ledger_client.py +448 -0
  53. wayfinder_paths/core/config.py +12 -0
  54. wayfinder_paths/core/constants/__init__.py +15 -0
  55. wayfinder_paths/core/constants/base.py +6 -1
  56. wayfinder_paths/core/constants/contracts.py +39 -26
  57. wayfinder_paths/core/constants/erc20_abi.py +0 -1
  58. wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
  59. wayfinder_paths/core/constants/hyperliquid.py +16 -0
  60. wayfinder_paths/core/constants/moonwell_abi.py +0 -15
  61. wayfinder_paths/core/engine/manifest.py +66 -0
  62. wayfinder_paths/core/strategies/Strategy.py +0 -61
  63. wayfinder_paths/core/strategies/__init__.py +10 -1
  64. wayfinder_paths/core/strategies/opa_loop.py +167 -0
  65. wayfinder_paths/core/utils/test_transaction.py +289 -0
  66. wayfinder_paths/core/utils/transaction.py +44 -1
  67. wayfinder_paths/core/utils/web3.py +3 -0
  68. wayfinder_paths/mcp/__init__.py +5 -0
  69. wayfinder_paths/mcp/preview.py +185 -0
  70. wayfinder_paths/mcp/scripting.py +84 -0
  71. wayfinder_paths/mcp/server.py +52 -0
  72. wayfinder_paths/mcp/state/profile_store.py +195 -0
  73. wayfinder_paths/mcp/state/store.py +89 -0
  74. wayfinder_paths/mcp/test_scripting.py +267 -0
  75. wayfinder_paths/mcp/tools/__init__.py +0 -0
  76. wayfinder_paths/mcp/tools/balances.py +290 -0
  77. wayfinder_paths/mcp/tools/discovery.py +158 -0
  78. wayfinder_paths/mcp/tools/execute.py +770 -0
  79. wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
  80. wayfinder_paths/mcp/tools/quotes.py +288 -0
  81. wayfinder_paths/mcp/tools/run_script.py +286 -0
  82. wayfinder_paths/mcp/tools/strategies.py +188 -0
  83. wayfinder_paths/mcp/tools/tokens.py +46 -0
  84. wayfinder_paths/mcp/tools/wallets.py +354 -0
  85. wayfinder_paths/mcp/utils.py +129 -0
  86. wayfinder_paths/policies/hyperliquid.py +1 -1
  87. wayfinder_paths/policies/lifi.py +18 -0
  88. wayfinder_paths/policies/util.py +8 -2
  89. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +28 -119
  90. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
  91. wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
  92. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
  93. wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
  94. wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
  95. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
  96. wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
  97. wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
  98. wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
  99. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
  100. wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
  101. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
  102. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
  103. wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +202 -0
  104. wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
  105. wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
  106. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +3 -12
  107. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +7 -29
  108. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +63 -40
  109. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
  110. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -34
  111. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +11 -34
  112. wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
  113. wayfinder_paths/tests/test_test_coverage.py +1 -4
  114. wayfinder_paths-0.1.24.dist-info/METADATA +378 -0
  115. wayfinder_paths-0.1.24.dist-info/RECORD +185 -0
  116. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.24.dist-info}/WHEEL +1 -1
  117. wayfinder_paths/scripts/create_strategy.py +0 -139
  118. wayfinder_paths/scripts/make_wallets.py +0 -142
  119. wayfinder_paths-0.1.23.dist-info/METADATA +0 -354
  120. wayfinder_paths-0.1.23.dist-info/RECORD +0 -120
  121. /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
  122. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.24.dist-info}/LICENSE +0 -0
@@ -1,139 +0,0 @@
1
- #!/usr/bin/env python3
2
-
3
- import argparse
4
- import re
5
- import shutil
6
- from pathlib import Path
7
-
8
- from wayfinder_paths.core.utils.wallets import make_random_wallet, write_wallet_to_json
9
-
10
-
11
- def sanitize_name(name: str) -> str:
12
- # Replace spaces and special chars with underscores, lowercase
13
- name = re.sub(r"[^a-zA-Z0-9_-]", "_", name)
14
- name = re.sub(r"_+", "_", name)
15
- name = name.strip("_")
16
- return name.lower()
17
-
18
-
19
- def update_strategy_file(strategy_path: Path, class_name: str) -> None:
20
- content = strategy_path.read_text()
21
- # Replace MyStrategy with the new class name
22
- content = content.replace("MyStrategy", class_name)
23
- # Replace my_strategy references in docstrings/comments
24
- content = re.sub(
25
- r"my_strategy", class_name.lower().replace("Strategy", ""), content
26
- )
27
- strategy_path.write_text(content)
28
-
29
-
30
- def main():
31
- parser = argparse.ArgumentParser(
32
- description="Create a new strategy from template with dedicated wallet"
33
- )
34
- parser.add_argument(
35
- "name",
36
- help="Strategy name (e.g., 'my_awesome_strategy' or 'My Awesome Strategy')",
37
- )
38
- parser.add_argument(
39
- "--template-dir",
40
- type=Path,
41
- default=Path(__file__).parent.parent / "templates" / "strategy",
42
- help="Path to strategy template directory",
43
- )
44
- parser.add_argument(
45
- "--strategies-dir",
46
- type=Path,
47
- default=Path(__file__).parent.parent / "strategies",
48
- help="Path to strategies directory",
49
- )
50
- parser.add_argument(
51
- "--wallets-file",
52
- type=Path,
53
- default=Path(__file__).parent.parent.parent / "config.json",
54
- help="Path to config.json file",
55
- )
56
- parser.add_argument(
57
- "--override",
58
- action="store_true",
59
- help="Override existing strategy directory if it exists",
60
- )
61
- args = parser.parse_args()
62
-
63
- # Sanitize name for directory
64
- dir_name = sanitize_name(args.name)
65
- strategy_dir = args.strategies_dir / dir_name
66
-
67
- if strategy_dir.exists() and not args.override:
68
- raise SystemExit(
69
- f"Strategy directory already exists: {strategy_dir}\n"
70
- "Use --override to replace it"
71
- )
72
-
73
- if not args.template_dir.exists():
74
- raise SystemExit(f"Template directory not found: {args.template_dir}")
75
-
76
- if strategy_dir.exists():
77
- print(f"Removing existing directory: {strategy_dir}")
78
- shutil.rmtree(strategy_dir)
79
- strategy_dir.mkdir(parents=True, exist_ok=True)
80
- print(f"Created strategy directory: {strategy_dir}")
81
-
82
- # Copy template files
83
- template_files = [
84
- "strategy.py",
85
- "test_strategy.py",
86
- "examples.json",
87
- "README.md",
88
- ]
89
- for filename in template_files:
90
- src = args.template_dir / filename
91
- if src.exists():
92
- dst = strategy_dir / filename
93
- shutil.copy2(src, dst)
94
- print(f" Copied {filename}")
95
-
96
- # Generate class name from strategy name
97
- class_name = "".join(word.capitalize() for word in dir_name.split("_"))
98
- if not class_name.endswith("Strategy"):
99
- class_name += "Strategy"
100
-
101
- strategy_file = strategy_dir / "strategy.py"
102
- if strategy_file.exists():
103
- update_strategy_file(strategy_file, class_name)
104
- print(f" Updated strategy.py with class name: {class_name}")
105
-
106
- # Generate wallet with label matching directory name (strategy identifier)
107
- # If config.json doesn't exist, create it with a main wallet first
108
- if not args.wallets_file.exists():
109
- print(" Creating new config.json with main wallet...")
110
- main_wallet = make_random_wallet()
111
- main_wallet["label"] = "main"
112
- write_wallet_to_json(
113
- main_wallet,
114
- out_dir=args.wallets_file.parent,
115
- filename=args.wallets_file.name,
116
- )
117
- print(f" Generated main wallet: {main_wallet['address']}")
118
-
119
- # Generate strategy wallet (will append to existing config.json)
120
- wallet = make_random_wallet()
121
- wallet["label"] = dir_name
122
- write_wallet_to_json(
123
- wallet, out_dir=args.wallets_file.parent, filename=args.wallets_file.name
124
- )
125
- print(f" Generated strategy wallet: {wallet['address']} (label: {dir_name})")
126
-
127
- print("\n✅ Strategy created successfully!")
128
- print(f" Directory: {strategy_dir}")
129
- print(f" Name: {dir_name}")
130
- print(f" Class: {class_name}")
131
- print(f" Wallet: {wallet['address']}")
132
- print("\nNext steps:")
133
- print(f" 1. Edit {strategy_dir / 'strategy.py'} to implement your strategy")
134
- print(" 2. Add required adapters in __init__")
135
- print(f" 3. Test with: just test-strategy {dir_name}")
136
-
137
-
138
- if __name__ == "__main__":
139
- main()
@@ -1,142 +0,0 @@
1
- import argparse
2
- import json
3
- from pathlib import Path
4
-
5
- from eth_account import Account
6
-
7
- from wayfinder_paths.core.utils.wallets import (
8
- load_wallets,
9
- make_random_wallet,
10
- write_wallet_to_json,
11
- )
12
-
13
-
14
- def to_keystore_json(private_key_hex: str, password: str):
15
- return Account.encrypt(private_key_hex, password)
16
-
17
-
18
- def main():
19
- parser = argparse.ArgumentParser(description="Generate local dev wallets")
20
- parser.add_argument(
21
- "-n",
22
- type=int,
23
- default=0,
24
- help="Number of wallets to create (ignored if --label is used)",
25
- )
26
- parser.add_argument(
27
- "--out-dir",
28
- type=Path,
29
- default=Path("."),
30
- help="Output directory for config.json (and keystore files)",
31
- )
32
- parser.add_argument(
33
- "--keystore-password",
34
- type=str,
35
- default=None,
36
- help="Optional password to write geth-compatible keystores",
37
- )
38
- parser.add_argument(
39
- "--label",
40
- type=str,
41
- default=None,
42
- help="Create a wallet with a custom label (e.g., strategy name). If not provided, auto-generates labels.",
43
- )
44
- parser.add_argument(
45
- "--default",
46
- action="store_true",
47
- help="Create a default 'main' wallet if none exists (used by CI)",
48
- )
49
- args = parser.parse_args()
50
-
51
- # --default is equivalent to -n 1 (create main wallet if needed)
52
- if args.default and args.n == 0 and not args.label:
53
- args.n = 1
54
-
55
- args.out_dir.mkdir(parents=True, exist_ok=True)
56
-
57
- existing = load_wallets(args.out_dir, "config.json")
58
- has_main = any(w.get("label") in ("main", "default") for w in existing)
59
-
60
- rows: list[dict[str, str]] = []
61
- index = 0
62
-
63
- # Custom labeled wallet (e.g., for strategy name)
64
- if args.label:
65
- # Check if label already exists - if so, skip (don't create duplicate)
66
- if any(w.get("label") == args.label for w in existing):
67
- print(f"Wallet with label '{args.label}' already exists, skipping...")
68
- else:
69
- w = make_random_wallet()
70
- w["label"] = args.label
71
- rows.append(w)
72
- print(f"[{index}] {w['address']} (label: {args.label})")
73
- write_wallet_to_json(w, out_dir=args.out_dir, filename="config.json")
74
- if args.keystore_password:
75
- ks = to_keystore_json(w["private_key_hex"], args.keystore_password)
76
- ks_path = args.out_dir / f"keystore_{w['address']}.json"
77
- ks_path.write_text(json.dumps(ks))
78
- index += 1
79
-
80
- # If no wallets existed before, also create a "main" wallet
81
- if not existing:
82
- main_w = make_random_wallet()
83
- main_w["label"] = "main"
84
- rows.append(main_w)
85
- print(f"[{index}] {main_w['address']} (main)")
86
- write_wallet_to_json(
87
- main_w, out_dir=args.out_dir, filename="config.json"
88
- )
89
- if args.keystore_password:
90
- ks = to_keystore_json(
91
- main_w["private_key_hex"], args.keystore_password
92
- )
93
- ks_path = args.out_dir / f"keystore_{main_w['address']}.json"
94
- ks_path.write_text(json.dumps(ks))
95
- index += 1
96
- else:
97
- if args.n == 0:
98
- args.n = 1
99
-
100
- # Find next temporary number
101
- existing_labels = {
102
- w.get("label", "")
103
- for w in existing
104
- if w.get("label", "").startswith("temporary_")
105
- }
106
- temp_numbers = set()
107
- for label in existing_labels:
108
- try:
109
- num = int(label.replace("temporary_", ""))
110
- temp_numbers.add(num)
111
- except ValueError:
112
- pass
113
- next_temp_num = 1
114
- if temp_numbers:
115
- next_temp_num = max(temp_numbers) + 1
116
-
117
- for i in range(args.n):
118
- w = make_random_wallet()
119
- # Label first wallet as "main" if main doesn't exist, otherwise use temporary_N
120
- if i == 0 and not has_main:
121
- w["label"] = "main"
122
- rows.append(w)
123
- print(f"[{index}] {w['address']} (main)")
124
- else:
125
- # Find next available temporary number
126
- while next_temp_num in temp_numbers:
127
- next_temp_num += 1
128
- w["label"] = f"temporary_{next_temp_num}"
129
- temp_numbers.add(next_temp_num)
130
- rows.append(w)
131
- print(f"[{index}] {w['address']} (label: temporary_{next_temp_num})")
132
-
133
- write_wallet_to_json(w, out_dir=args.out_dir, filename="config.json")
134
- if args.keystore_password:
135
- ks = to_keystore_json(w["private_key_hex"], args.keystore_password)
136
- ks_path = args.out_dir / f"keystore_{w['address']}.json"
137
- ks_path.write_text(json.dumps(ks))
138
- index += 1
139
-
140
-
141
- if __name__ == "__main__":
142
- main()
@@ -1,354 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: wayfinder-paths
3
- Version: 0.1.23
4
- Summary: Wayfinder Path: strategies and adapters
5
- Author: Wayfinder
6
- Author-email: dev@wayfinder.ai
7
- Requires-Python: >=3.12,<4.0
8
- Classifier: Programming Language :: Python :: 3
9
- Classifier: Programming Language :: Python :: 3.12
10
- Requires-Dist: aiohttp (>=3.13.0,<4.0.0)
11
- Requires-Dist: eth-account (>=0.13.7,<0.14.0)
12
- Requires-Dist: httpx (>=0.28.1,<0.29.0)
13
- Requires-Dist: hyperliquid-felix
14
- Requires-Dist: loguru (>=0.7.3,<0.8.0)
15
- Requires-Dist: numpy (>=1.26.0,<2.0.0)
16
- Requires-Dist: pandas (>=2.2.0,<3.0.0)
17
- Requires-Dist: pydantic (>=2.11.9,<3.0.0)
18
- Requires-Dist: pyyaml (>=6.0.1,<7.0.0)
19
- Requires-Dist: web3 (>=7.13.0,<8.0.0)
20
- Description-Content-Type: text/markdown
21
-
22
- # Wayfinder Paths
23
-
24
- [![Python 3.12](https://img.shields.io/badge/python-3.12-blue.svg)](https://www.python.org/downloads/)
25
- [![PyPI](https://img.shields.io/pypi/v/wayfinder-paths.svg)](https://pypi.org/project/wayfinder-paths/)
26
- [![Discord](https://img.shields.io/badge/discord-join-7289da.svg)](https://discord.gg/fUVwGMXjm3)
27
-
28
- Open-source framework for building automated crypto trading strategies and protocol integrations. Develop, test, and deploy strategies with direct wallet integration across multiple chains.
29
-
30
- ## Quick Start
31
-
32
- ```bash
33
- # Clone the repository
34
- git clone https://github.com/WayfinderFoundation/wayfinder-paths.git
35
- cd wayfinder-paths
36
-
37
- # Install Poetry if needed
38
- curl -sSL https://install.python-poetry.org | python3 -
39
-
40
- # Install dependencies
41
- poetry install
42
-
43
- # Generate test wallets (creates config.json with main wallet)
44
- just create-wallets
45
- # Or: poetry run python wayfinder_paths/scripts/make_wallets.py -n 1
46
-
47
- # Create a strategy-specific wallet
48
- just create-wallet stablecoin_yield_strategy
49
- # Or: poetry run python wayfinder_paths/scripts/make_wallets.py --label stablecoin_yield_strategy
50
-
51
- # Add your API key to config.json under system.api_key
52
-
53
- # Run a strategy
54
- poetry run python wayfinder_paths/run_strategy.py stablecoin_yield_strategy --action status --config config.json
55
- ```
56
-
57
- ## Repository Structure
58
-
59
- ```
60
- wayfinder-paths/
61
- ├── wayfinder_paths/
62
- │ ├── core/ # Core framework (maintained by team)
63
- │ │ ├── adapters/ # BaseAdapter class
64
- │ │ ├── clients/ # API clients (Token, Wallet, Pool, BRAP, Ledger, etc.)
65
- │ │ ├── engine/ # StrategyJob execution engine
66
- │ │ ├── strategies/ # Strategy base class and descriptors
67
- │ │ ├── utils/ # Web3, EVM helpers, transaction utilities
68
- │ │ └── config.py # Configuration system
69
- │ ├── adapters/ # Protocol integrations (community contributions)
70
- │ │ ├── balance_adapter/ # Wallet/token balances and transfers
71
- │ │ ├── brap_adapter/ # Cross-chain swaps and bridges
72
- │ │ ├── ledger_adapter/ # Transaction recording
73
- │ │ ├── moonwell_adapter/ # Moonwell lending protocol
74
- │ │ ├── pool_adapter/ # DeFi pool data
75
- │ │ └── token_adapter/ # Token metadata and prices
76
- │ ├── strategies/ # Trading strategies (community contributions)
77
- │ │ ├── basis_trading_strategy/
78
- │ │ ├── hyperlend_stable_yield_strategy/
79
- │ │ ├── moonwell_wsteth_loop_strategy/
80
- │ │ └── stablecoin_yield_strategy/
81
- │ ├── templates/ # Starter templates
82
- │ │ ├── adapter/
83
- │ │ └── strategy/
84
- │ ├── scripts/ # Utility scripts
85
- │ └── run_strategy.py # CLI entry point
86
- ├── config.json # Local config (not committed)
87
- ├── pyproject.toml # Project dependencies
88
- └── README.md
89
- ```
90
-
91
- ## Architecture
92
-
93
- ### Layered Design
94
-
95
- ```
96
- Strategy Layer - Trading logic (deposit, update, withdraw, exit)
97
-
98
- Adapter Layer - Protocol integrations (BalanceAdapter, PoolAdapter, etc.)
99
-
100
- Client Layer - API wrappers (TokenClient, PoolClient, LedgerClient, etc.)
101
-
102
- Network - RPCs, Wayfinder API, external services
103
- ```
104
-
105
- **Key principle**: Strategies call adapters, adapters compose clients, clients handle network communication.
106
-
107
- ### Strategies
108
-
109
- Strategies implement trading logic by extending the `Strategy` base class:
110
-
111
- ```python
112
- from wayfinder_paths.core.strategies.Strategy import Strategy, StatusDict, StatusTuple
113
-
114
- class MyStrategy(Strategy):
115
- name = "My Strategy"
116
-
117
- def __init__(self, config=None, **kwargs):
118
- super().__init__(config, **kwargs)
119
- # Register adapters
120
- balance_adapter = BalanceAdapter(config, **kwargs)
121
- self.register_adapters([balance_adapter])
122
- self.balance_adapter = balance_adapter
123
-
124
- async def deposit(self, main_token_amount=0.0, gas_token_amount=0.0) -> StatusTuple:
125
- """Move funds from main wallet into strategy wallet."""
126
- return (True, "Deposited successfully")
127
-
128
- async def update(self) -> StatusTuple:
129
- """Rebalance or optimize positions."""
130
- return (True, "Updated successfully")
131
-
132
- async def exit(self, **kwargs) -> StatusTuple:
133
- """Transfer funds from strategy wallet back to main wallet."""
134
- return (True, "Exited successfully")
135
-
136
- async def _status(self) -> StatusDict:
137
- """Report current state."""
138
- return {
139
- "portfolio_value": 0.0,
140
- "net_deposit": 0.0,
141
- "strategy_status": {"message": "healthy"},
142
- "gas_available": 0.0,
143
- "gassed_up": True,
144
- }
145
- ```
146
-
147
- **Required methods**: `deposit`, `update`, `exit`, `_status`
148
-
149
- **Optional methods**: `withdraw` (has default implementation), `partial_liquidate`, `setup`, `health_check`
150
-
151
- ### Adapters
152
-
153
- Adapters wrap protocol-specific logic and expose capabilities to strategies:
154
-
155
- ```python
156
- from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
157
-
158
- class MyAdapter(BaseAdapter):
159
- adapter_type = "MY_ADAPTER"
160
-
161
- def __init__(self, config=None):
162
- super().__init__("my_adapter", config)
163
- self.client = SomeClient()
164
-
165
- async def connect(self) -> bool:
166
- return True
167
-
168
- async def do_something(self, param: str) -> tuple[bool, Any]:
169
- try:
170
- result = await self.client.call(param)
171
- return (True, result)
172
- except Exception as e:
173
- return (False, str(e))
174
- ```
175
-
176
- All adapter methods return `(success: bool, data: Any)` tuples.
177
-
178
- ### Built-in Adapters
179
-
180
- | Adapter | Type | Purpose |
181
- |---------|------|---------|
182
- | BalanceAdapter | BALANCE | Wallet/token balances, cross-wallet transfers with ledger tracking |
183
- | PoolAdapter | POOL | DeFi pool metadata and yield analytics |
184
- | BRAPAdapter | BRAP | Cross-chain swap quotes and execution |
185
- | LedgerAdapter | LEDGER | Transaction recording and net deposit tracking |
186
- | TokenAdapter | TOKEN | Token metadata and price feeds |
187
- | MoonwellAdapter | MOONWELL | Moonwell lending/borrowing on Base |
188
-
189
- ### Built-in Strategies
190
-
191
- | Strategy | Description | Chain |
192
- |----------|-------------|-------|
193
- | stablecoin_yield_strategy | USDC yield optimization on Base | Base |
194
- | hyperlend_stable_yield_strategy | Stablecoin yield on HyperLend | HyperEVM |
195
- | moonwell_wsteth_loop_strategy | Leveraged wstETH carry trade | Base |
196
- | basis_trading_strategy | Delta-neutral funding rate capture | Hyperliquid |
197
-
198
- ## Configuration
199
-
200
- Configuration lives in `config.json`:
201
-
202
- ```json
203
- {
204
- "system": {
205
- "api_base_url": "https://api.wayfinder.ai",
206
- "api_key": "sk_live_..."
207
- },
208
- "strategy": {
209
- "rpc_urls": {
210
- "1": "https://eth.llamarpc.com",
211
- "8453": "https://mainnet.base.org",
212
- "42161": "https://arb1.arbitrum.io/rpc"
213
- }
214
- },
215
- "wallets": [
216
- {
217
- "label": "main",
218
- "address": "0x...",
219
- "private_key_hex": "0x..."
220
- },
221
- {
222
- "label": "stablecoin_yield_strategy",
223
- "address": "0x...",
224
- "private_key_hex": "0x..."
225
- }
226
- ]
227
- }
228
- ```
229
-
230
- - **system.api_key**: Required for Wayfinder API authentication (sent as `X-API-KEY` header)
231
- - **wallets**: Array of wallets with labels; strategies look up wallets by label matching their directory name
232
- - **strategy.rpc_urls**: Custom RPC endpoints by chain ID
233
-
234
- See [CONFIG_GUIDE.md](CONFIG_GUIDE.md) for detailed configuration documentation.
235
-
236
- ## CLI Usage
237
-
238
- ```bash
239
- # Check strategy status
240
- poetry run python wayfinder_paths/run_strategy.py <strategy_name> --action status --config config.json
241
-
242
- # Deposit funds
243
- poetry run python wayfinder_paths/run_strategy.py <strategy_name> --action deposit \
244
- --main-token-amount 100 --gas-token-amount 0.01 --config config.json
245
-
246
- # Run update cycle
247
- poetry run python wayfinder_paths/run_strategy.py <strategy_name> --action update --config config.json
248
-
249
- # Withdraw funds
250
- poetry run python wayfinder_paths/run_strategy.py <strategy_name> --action withdraw --config config.json
251
-
252
- # Exit (return funds to main wallet)
253
- poetry run python wayfinder_paths/run_strategy.py <strategy_name> --action exit --config config.json
254
-
255
- # Run continuously
256
- poetry run python wayfinder_paths/run_strategy.py <strategy_name> --action run --config config.json
257
-
258
- # Partial liquidation
259
- poetry run python wayfinder_paths/run_strategy.py <strategy_name> --action partial-liquidate \
260
- --amount 50 --config config.json
261
- ```
262
-
263
- **Available actions**: `status`, `deposit`, `update`, `withdraw`, `exit`, `run`, `partial-liquidate`, `policy`, `script`
264
-
265
- ## Testing
266
-
267
- ```bash
268
- # Generate test wallets first
269
- just create-wallets
270
-
271
- # Run all smoke tests
272
- poetry run pytest -k smoke -v
273
-
274
- # Test specific strategy
275
- poetry run pytest wayfinder_paths/strategies/my_strategy/ -v
276
-
277
- # Test specific adapter
278
- poetry run pytest wayfinder_paths/adapters/my_adapter/ -v
279
-
280
- # Run with coverage
281
- poetry run pytest --cov=wayfinder_paths -v
282
- ```
283
-
284
- See [TESTING.md](TESTING.md) for detailed testing guidance.
285
-
286
- ## Contributing
287
-
288
- ### Creating a New Strategy
289
-
290
- ```bash
291
- # Use the convenience command (creates wallet automatically)
292
- just create-strategy "My Strategy Name"
293
-
294
- # Or manually copy the template
295
- cp -r wayfinder_paths/templates/strategy wayfinder_paths/strategies/my_strategy
296
- ```
297
-
298
- Then:
299
- 1. Rename the class in `strategy.py`
300
- 2. Implement `deposit`, `update`, `exit`, and `_status` methods
301
- 3. Add tests in `test_strategy.py`
302
- 4. Create `examples.json` with test data
303
- 5. Update the README
304
-
305
- ### Creating a New Adapter
306
-
307
- ```bash
308
- cp -r wayfinder_paths/templates/adapter wayfinder_paths/adapters/my_adapter
309
- ```
310
-
311
- Then:
312
- 1. Rename the class in `adapter.py`
313
- 2. Implement protocol-specific methods
314
- 3. Add tests in `test_adapter.py`
315
- 4. Update the README
316
-
317
- ### Guidelines
318
-
319
- - Strategies call adapters, not clients directly
320
- - All adapter methods return `(success, data)` tuples
321
- - Use `examples.json` for strategy test data
322
- - Never hardcode API keys or private keys
323
- - Add tests before submitting PRs
324
-
325
- ## Publishing
326
-
327
- ```bash
328
- # Must be on main branch
329
- export PUBLISH_TOKEN="your_pypi_token"
330
- just publish
331
- ```
332
-
333
- **Version bumping**: Update `version` in `pyproject.toml` before publishing. Follow [SemVer](https://semver.org/):
334
- - PATCH: Bug fixes
335
- - MINOR: New features (backward compatible)
336
- - MAJOR: Breaking changes
337
-
338
- ## Security
339
-
340
- - Never commit `config.json` (contains private keys)
341
- - Use test wallets for development
342
- - Test on testnets when available
343
- - Validate all inputs
344
- - Set appropriate gas limits
345
-
346
- ## Community
347
-
348
- - [Discord](https://discord.gg/fUVwGMXjm3)
349
- - [GitHub Issues](https://github.com/WayfinderFoundation/wayfinder-paths/issues)
350
-
351
- ## License
352
-
353
- MIT License - see [LICENSE](LICENSE) for details.
354
-