wayfinder-paths 0.1.11__py3-none-any.whl → 0.1.14__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 (66) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +13 -14
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +36 -39
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +123 -0
  4. wayfinder_paths/adapters/brap_adapter/README.md +11 -16
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +87 -75
  6. wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
  7. wayfinder_paths/adapters/brap_adapter/test_adapter.py +121 -59
  8. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +22 -23
  9. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +114 -60
  10. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1 -1
  11. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +44 -5
  12. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +104 -0
  13. wayfinder_paths/adapters/moonwell_adapter/adapter.py +0 -3
  14. wayfinder_paths/adapters/pool_adapter/README.md +11 -27
  15. wayfinder_paths/adapters/pool_adapter/adapter.py +11 -37
  16. wayfinder_paths/adapters/pool_adapter/examples.json +6 -7
  17. wayfinder_paths/adapters/pool_adapter/test_adapter.py +8 -8
  18. wayfinder_paths/adapters/token_adapter/README.md +2 -14
  19. wayfinder_paths/adapters/token_adapter/adapter.py +16 -10
  20. wayfinder_paths/adapters/token_adapter/examples.json +4 -8
  21. wayfinder_paths/adapters/token_adapter/test_adapter.py +5 -3
  22. wayfinder_paths/core/clients/BRAPClient.py +103 -62
  23. wayfinder_paths/core/clients/ClientManager.py +1 -68
  24. wayfinder_paths/core/clients/HyperlendClient.py +127 -66
  25. wayfinder_paths/core/clients/LedgerClient.py +1 -4
  26. wayfinder_paths/core/clients/PoolClient.py +126 -88
  27. wayfinder_paths/core/clients/TokenClient.py +92 -37
  28. wayfinder_paths/core/clients/WalletClient.py +28 -58
  29. wayfinder_paths/core/clients/WayfinderClient.py +33 -166
  30. wayfinder_paths/core/clients/__init__.py +0 -2
  31. wayfinder_paths/core/clients/protocols.py +35 -52
  32. wayfinder_paths/core/clients/sdk_example.py +37 -22
  33. wayfinder_paths/core/config.py +60 -224
  34. wayfinder_paths/core/engine/StrategyJob.py +7 -55
  35. wayfinder_paths/core/services/local_evm_txn.py +28 -10
  36. wayfinder_paths/core/services/local_token_txn.py +1 -1
  37. wayfinder_paths/core/strategies/Strategy.py +3 -5
  38. wayfinder_paths/core/strategies/descriptors.py +7 -0
  39. wayfinder_paths/core/utils/evm_helpers.py +7 -3
  40. wayfinder_paths/core/utils/wallets.py +12 -19
  41. wayfinder_paths/core/wallets/README.md +1 -1
  42. wayfinder_paths/run_strategy.py +8 -17
  43. wayfinder_paths/scripts/create_strategy.py +5 -5
  44. wayfinder_paths/scripts/make_wallets.py +5 -5
  45. wayfinder_paths/scripts/run_strategy.py +3 -3
  46. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -1
  47. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +206 -526
  48. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +228 -11
  49. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
  50. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +41 -25
  51. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
  52. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +1 -1
  53. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +10 -9
  54. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +12 -6
  55. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +3 -3
  56. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +110 -78
  57. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +44 -21
  58. wayfinder_paths/templates/adapter/README.md +1 -1
  59. wayfinder_paths/templates/strategy/README.md +3 -3
  60. wayfinder_paths/templates/strategy/test_strategy.py +3 -2
  61. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/METADATA +21 -59
  62. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/RECORD +64 -65
  63. wayfinder_paths/core/clients/AuthClient.py +0 -83
  64. wayfinder_paths/core/settings.py +0 -61
  65. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/LICENSE +0 -0
  66. {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.14.dist-info}/WHEEL +0 -0
@@ -43,12 +43,12 @@ def resolve_chain_id(token_info: dict[str, Any], logger_instance=None) -> int |
43
43
  """
44
44
  log = logger_instance or logger
45
45
  chain_meta = token_info.get("chain") or {}
46
- chain_id = chain_meta.get("chain_id")
46
+ chain_id = chain_meta.get("id")
47
47
  try:
48
48
  if chain_id is not None:
49
49
  return int(chain_id)
50
50
  except (ValueError, TypeError):
51
- log.debug("Invalid chain_id in token_info.chain_id: %s", chain_id)
51
+ log.debug("Invalid chain_id in token_info.chain: %s", chain_id)
52
52
  return chain_code_to_chain_id(chain_meta.get("code"))
53
53
 
54
54
 
@@ -78,9 +78,13 @@ def resolve_rpc_url(
78
78
  if chain_id is not None and isinstance(mapping, dict):
79
79
  by_int = mapping.get(chain_id)
80
80
  if by_int:
81
+ if isinstance(by_int, list):
82
+ return str(by_int[0])
81
83
  return str(by_int)
82
84
  by_str = mapping.get(str(chain_id))
83
85
  if by_str:
86
+ if isinstance(by_str, list):
87
+ return str(by_str[0])
84
88
  return str(by_str)
85
89
  raise ValueError("RPC URL not provided. Set strategy.rpc_urls in config.json.")
86
90
 
@@ -133,7 +137,7 @@ def resolve_private_key_for_from_address(
133
137
  if strategy_addr and from_addr_norm == (strategy_addr or "").lower():
134
138
  return strategy_pk
135
139
 
136
- # No fallback - private keys must be in config or wallets.json
140
+ # No fallback - private keys must be in config or config.json
137
141
  return None
138
142
 
139
143
 
@@ -6,11 +6,7 @@ from eth_account import Account
6
6
 
7
7
 
8
8
  def make_random_wallet() -> dict[str, str]:
9
- """Generate a new random wallet.
10
-
11
- Returns a mapping with keys: "address" and "private_key_hex" (0x-prefixed).
12
- """
13
- acct = Account.create() # uses os.urandom
9
+ acct = Account.create()
14
10
  return {
15
11
  "address": acct.address,
16
12
  "private_key_hex": acct.key.hex(),
@@ -22,33 +18,31 @@ def _load_existing_wallets(file_path: Path) -> list[dict[str, Any]]:
22
18
  return []
23
19
  try:
24
20
  parsed = json.loads(file_path.read_text())
25
- if isinstance(parsed, list):
26
- return parsed
27
21
  if isinstance(parsed, dict):
28
22
  wallets = parsed.get("wallets")
29
23
  if isinstance(wallets, list):
30
24
  return wallets
31
25
  return []
32
26
  except Exception:
33
- # If the file is malformed, start fresh rather than raising.
34
27
  return []
35
28
 
36
29
 
37
30
  def _save_wallets(file_path: Path, wallets: list[dict[str, Any]]) -> None:
38
- # Ensure stable ordering by address for readability
31
+ config = {}
32
+ if file_path.exists():
33
+ try:
34
+ config = json.loads(file_path.read_text())
35
+ except Exception:
36
+ pass
37
+
39
38
  sorted_wallets = sorted(wallets, key=lambda w: w.get("address", ""))
40
- file_path.write_text(json.dumps(sorted_wallets, indent=2))
39
+ config["wallets"] = sorted_wallets
40
+ file_path.write_text(json.dumps(config, indent=2))
41
41
 
42
42
 
43
43
  def write_wallet_to_json(
44
- wallet: dict[str, str], out_dir: str | Path = ".", filename: str = "wallets.json"
44
+ wallet: dict[str, str], out_dir: str | Path = ".", filename: str = "config.json"
45
45
  ) -> Path:
46
- """Create or update a wallets.json with the provided wallet.
47
-
48
- - Ensures the output directory exists.
49
- - Merges with existing entries keyed by address (updates if present, appends otherwise).
50
- - Writes a pretty-printed JSON list of wallet objects.
51
- """
52
46
  out_dir_path = Path(out_dir)
53
47
  out_dir_path.mkdir(parents=True, exist_ok=True)
54
48
  file_path = out_dir_path / filename
@@ -71,7 +65,6 @@ def write_wallet_to_json(
71
65
 
72
66
 
73
67
  def load_wallets(
74
- out_dir: str | Path = ".", filename: str = "wallets.json"
68
+ out_dir: str | Path = ".", filename: str = "config.json"
75
69
  ) -> list[dict[str, Any]]:
76
- """Public helper to read wallets.json as a list of wallet dicts."""
77
70
  return _load_existing_wallets(Path(out_dir) / filename)
@@ -1,6 +1,6 @@
1
1
  # Wallet Abstraction Layer
2
2
 
3
- Wayfinder strategies interact with blockchains through a single abstraction: the `EvmTxn` interface defined in `wayfinder_paths/core/services/base.py`. The default implementation (`LocalEvmTxn`) signs transactions with private keys pulled from config.json or wallets.json, while `WalletManager` resolves which provider to use at runtime.
3
+ Wayfinder strategies interact with blockchains through a single abstraction: the `EvmTxn` interface defined in `wayfinder_paths/core/services/base.py`. The default implementation (`LocalEvmTxn`) signs transactions with private keys pulled from config.json or config.json, while `WalletManager` resolves which provider to use at runtime.
4
4
 
5
5
  ## Pieces
6
6
 
@@ -20,7 +20,6 @@ def load_strategy(
20
20
  strategy_name: str,
21
21
  *,
22
22
  strategy_config: dict | None = None,
23
- api_key: str | None = None,
24
23
  ):
25
24
  """
26
25
  Dynamically load a strategy by name
@@ -28,7 +27,6 @@ def load_strategy(
28
27
  Args:
29
28
  strategy_name: Name of the strategy to load (directory name in strategies/)
30
29
  strategy_config: Configuration dict for the strategy
31
- api_key: Optional API key for service account authentication
32
30
 
33
31
  Returns:
34
32
  Strategy instance
@@ -70,7 +68,7 @@ def load_strategy(
70
68
  if strategy_class is None:
71
69
  raise ValueError(f"No Strategy class found in {module_path}")
72
70
 
73
- return strategy_class(config=strategy_config, api_key=api_key)
71
+ return strategy_class(config=strategy_config)
74
72
 
75
73
 
76
74
  def load_config(
@@ -136,15 +134,14 @@ async def run_strategy(
136
134
  logger.debug(f"Config path provided: {config_path}")
137
135
  config = load_config(config_path, strategy_name=strategy_name)
138
136
  logger.debug(
139
- "Loaded config: creds=%s wallets(main=%s strategy=%s)",
140
- "yes"
141
- if (config.user.username and config.user.password)
142
- or config.user.refresh_token
143
- else "no",
144
- (config.user.main_wallet_address or "none"),
145
- (config.user.strategy_wallet_address or "none"),
137
+ "Loaded config: wallets(main={} strategy={})",
138
+ config.user.main_wallet_address or "none",
139
+ config.user.strategy_wallet_address or "none",
146
140
  )
147
141
 
142
+ # Validate required configuration
143
+ # Authentication is via system.api_key in config.json
144
+
148
145
  # Load strategy with the enriched config
149
146
  strategy = load_strategy(
150
147
  strategy_name,
@@ -157,13 +154,7 @@ async def run_strategy(
157
154
 
158
155
  # Setup strategy job
159
156
  logger.info("Setting up strategy job...")
160
- logger.debug(
161
- "Auth mode: %s",
162
- "credentials"
163
- if (config.user.username and config.user.password)
164
- or config.user.refresh_token
165
- else "missing",
166
- )
157
+ logger.debug("Auth mode: API key (from system.api_key)")
167
158
  await strategy_job.setup()
168
159
 
169
160
  # Execute action
@@ -62,8 +62,8 @@ def main():
62
62
  parser.add_argument(
63
63
  "--wallets-file",
64
64
  type=Path,
65
- default=Path(__file__).parent.parent.parent / "wallets.json",
66
- help="Path to wallets.json file",
65
+ default=Path(__file__).parent.parent.parent / "config.json",
66
+ help="Path to config.json file",
67
67
  )
68
68
  parser.add_argument(
69
69
  "--override",
@@ -121,9 +121,9 @@ def main():
121
121
  print(f" Updated strategy.py with class name: {class_name}")
122
122
 
123
123
  # Generate wallet with label matching directory name (strategy identifier)
124
- # If wallets.json doesn't exist, create it with a main wallet first
124
+ # If config.json doesn't exist, create it with a main wallet first
125
125
  if not args.wallets_file.exists():
126
- print(" Creating new wallets.json with main wallet...")
126
+ print(" Creating new config.json with main wallet...")
127
127
  main_wallet = make_random_wallet()
128
128
  main_wallet["label"] = "main"
129
129
  write_wallet_to_json(
@@ -133,7 +133,7 @@ def main():
133
133
  )
134
134
  print(f" Generated main wallet: {main_wallet['address']}")
135
135
 
136
- # Generate strategy wallet (will append to existing wallets.json)
136
+ # Generate strategy wallet (will append to existing config.json)
137
137
  wallet = make_random_wallet()
138
138
  wallet["label"] = dir_name
139
139
  write_wallet_to_json(
@@ -27,7 +27,7 @@ def main():
27
27
  "--out-dir",
28
28
  type=Path,
29
29
  default=Path("."),
30
- help="Output directory for wallets.json (and keystore files)",
30
+ help="Output directory for config.json (and keystore files)",
31
31
  )
32
32
  parser.add_argument(
33
33
  "--keystore-password",
@@ -55,7 +55,7 @@ def main():
55
55
  args.out_dir.mkdir(parents=True, exist_ok=True)
56
56
 
57
57
  # Load existing wallets
58
- existing = load_wallets(args.out_dir, "wallets.json")
58
+ existing = load_wallets(args.out_dir, "config.json")
59
59
  has_main = any(w.get("label") in ("main", "default") for w in existing)
60
60
 
61
61
  rows: list[dict[str, str]] = []
@@ -72,7 +72,7 @@ def main():
72
72
  w["label"] = args.label
73
73
  rows.append(w)
74
74
  print(f"[{index}] {w['address']} (label: {args.label})")
75
- write_wallet_to_json(w, out_dir=args.out_dir, filename="wallets.json")
75
+ write_wallet_to_json(w, out_dir=args.out_dir, filename="config.json")
76
76
  if args.keystore_password:
77
77
  ks = to_keystore_json(w["private_key_hex"], args.keystore_password)
78
78
  ks_path = args.out_dir / f"keystore_{w['address']}.json"
@@ -86,7 +86,7 @@ def main():
86
86
  rows.append(main_w)
87
87
  print(f"[{index}] {main_w['address']} (main)")
88
88
  write_wallet_to_json(
89
- main_w, out_dir=args.out_dir, filename="wallets.json"
89
+ main_w, out_dir=args.out_dir, filename="config.json"
90
90
  )
91
91
  if args.keystore_password:
92
92
  ks = to_keystore_json(
@@ -133,7 +133,7 @@ def main():
133
133
  rows.append(w)
134
134
  print(f"[{index}] {w['address']} (label: temporary_{next_temp_num})")
135
135
 
136
- write_wallet_to_json(w, out_dir=args.out_dir, filename="wallets.json")
136
+ write_wallet_to_json(w, out_dir=args.out_dir, filename="config.json")
137
137
  if args.keystore_password:
138
138
  ks = to_keystore_json(w["private_key_hex"], args.keystore_password)
139
139
  ks_path = args.out_dir / f"keystore_{w['address']}.json"
@@ -27,7 +27,7 @@ def _find_wallet(wallets: list[dict[str, Any]], label: str) -> dict[str, Any]:
27
27
  for w in wallets:
28
28
  if w.get("label") == label:
29
29
  return w
30
- raise ValueError(f"Wallet label not found in wallets.json: {label}")
30
+ raise ValueError(f"Wallet label not found in config.json: {label}")
31
31
 
32
32
 
33
33
  def _get_strategy_class(strategy: str):
@@ -58,7 +58,7 @@ def _get_strategy_class(strategy: str):
58
58
  async def _run(args: argparse.Namespace) -> int:
59
59
  repo_root = Path(__file__).resolve().parents[2]
60
60
  wallets_path = (
61
- Path(args.wallets).resolve() if args.wallets else repo_root / "wallets.json"
61
+ Path(args.wallets).resolve() if args.wallets else repo_root / "config.json"
62
62
  )
63
63
  config_path = (
64
64
  Path(args.config).resolve() if args.config else repo_root / "config.json"
@@ -123,7 +123,7 @@ def main() -> int:
123
123
  ],
124
124
  )
125
125
  p.add_argument(
126
- "--wallets", default=None, help="Path to wallets.json (default: repo root)"
126
+ "--wallets", default=None, help="Path to config.json (default: repo root)"
127
127
  )
128
128
  p.add_argument(
129
129
  "--config", default=None, help="Path to config.json (default: repo root)"
@@ -38,7 +38,7 @@ class BasisSnapshotMixin:
38
38
  entry_cost_usd: float,
39
39
  exit_cost_usd: float,
40
40
  ) -> dict[str, Any]:
41
- """Build the `safe[horizon]` entry matching Django output shape."""
41
+ """Build the `safe[horizon]` entry matching the expected output shape."""
42
42
  L = max(1, int(leverage))
43
43
 
44
44
  depth_checks = depth_checks or {}