wayfinder-paths 0.1.21__py3-none-any.whl → 0.1.23__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 (63) hide show
  1. wayfinder_paths/__init__.py +0 -4
  2. wayfinder_paths/adapters/balance_adapter/README.md +0 -1
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +65 -169
  4. wayfinder_paths/adapters/balance_adapter/test_adapter.py +41 -113
  5. wayfinder_paths/adapters/brap_adapter/README.md +22 -75
  6. wayfinder_paths/adapters/brap_adapter/adapter.py +187 -576
  7. wayfinder_paths/adapters/brap_adapter/examples.json +21 -140
  8. wayfinder_paths/adapters/brap_adapter/test_adapter.py +6 -234
  9. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +39 -86
  10. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +5 -1
  11. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +6 -5
  12. wayfinder_paths/adapters/ledger_adapter/README.md +4 -1
  13. wayfinder_paths/adapters/ledger_adapter/adapter.py +3 -3
  14. wayfinder_paths/adapters/moonwell_adapter/adapter.py +108 -198
  15. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +37 -23
  16. wayfinder_paths/adapters/token_adapter/adapter.py +14 -0
  17. wayfinder_paths/core/__init__.py +0 -3
  18. wayfinder_paths/core/clients/BRAPClient.py +3 -0
  19. wayfinder_paths/core/clients/ClientManager.py +0 -7
  20. wayfinder_paths/core/clients/LedgerClient.py +196 -172
  21. wayfinder_paths/core/clients/WayfinderClient.py +0 -1
  22. wayfinder_paths/core/clients/__init__.py +0 -5
  23. wayfinder_paths/core/clients/protocols.py +0 -13
  24. wayfinder_paths/core/config.py +0 -164
  25. wayfinder_paths/core/constants/__init__.py +58 -2
  26. wayfinder_paths/core/constants/base.py +8 -22
  27. wayfinder_paths/core/constants/chains.py +36 -0
  28. wayfinder_paths/core/constants/contracts.py +39 -0
  29. wayfinder_paths/core/constants/tokens.py +9 -0
  30. wayfinder_paths/core/strategies/Strategy.py +0 -10
  31. wayfinder_paths/core/utils/evm_helpers.py +5 -15
  32. wayfinder_paths/core/utils/tokens.py +28 -0
  33. wayfinder_paths/core/utils/transaction.py +13 -7
  34. wayfinder_paths/core/utils/web3.py +5 -3
  35. wayfinder_paths/policies/enso.py +1 -2
  36. wayfinder_paths/policies/hyper_evm.py +6 -3
  37. wayfinder_paths/policies/hyperlend.py +1 -2
  38. wayfinder_paths/policies/moonwell.py +12 -7
  39. wayfinder_paths/policies/prjx.py +1 -3
  40. wayfinder_paths/run_strategy.py +97 -300
  41. wayfinder_paths/strategies/basis_trading_strategy/constants.py +3 -1
  42. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +19 -14
  43. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +12 -11
  44. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +20 -33
  45. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +21 -18
  46. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +69 -130
  47. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +32 -42
  48. {wayfinder_paths-0.1.21.dist-info → wayfinder_paths-0.1.23.dist-info}/METADATA +3 -4
  49. {wayfinder_paths-0.1.21.dist-info → wayfinder_paths-0.1.23.dist-info}/RECORD +51 -60
  50. {wayfinder_paths-0.1.21.dist-info → wayfinder_paths-0.1.23.dist-info}/WHEEL +1 -1
  51. wayfinder_paths/core/clients/WalletClient.py +0 -41
  52. wayfinder_paths/core/engine/StrategyJob.py +0 -110
  53. wayfinder_paths/core/services/test_local_evm_txn.py +0 -145
  54. wayfinder_paths/templates/adapter/README.md +0 -150
  55. wayfinder_paths/templates/adapter/adapter.py +0 -16
  56. wayfinder_paths/templates/adapter/examples.json +0 -8
  57. wayfinder_paths/templates/adapter/test_adapter.py +0 -30
  58. wayfinder_paths/templates/strategy/README.md +0 -186
  59. wayfinder_paths/templates/strategy/examples.json +0 -11
  60. wayfinder_paths/templates/strategy/strategy.py +0 -35
  61. wayfinder_paths/templates/strategy/test_strategy.py +0 -166
  62. wayfinder_paths/tests/test_smoke_manifest.py +0 -63
  63. {wayfinder_paths-0.1.21.dist-info → wayfinder_paths-0.1.23.dist-info}/LICENSE +0 -0
@@ -1,5 +1,4 @@
1
1
  import json
2
- from dataclasses import dataclass, field
3
2
  from pathlib import Path
4
3
  from typing import Any
5
4
 
@@ -18,157 +17,6 @@ def _load_config_file() -> dict[str, Any]:
18
17
 
19
18
 
20
19
  CONFIG = _load_config_file()
21
- SUPPORTED_CHAINS = [
22
- 1,
23
- 8453,
24
- 56,
25
- 42161,
26
- 137,
27
- 999,
28
- ]
29
-
30
-
31
- @dataclass
32
- class SystemConfig:
33
- api_base_url: str = field(default="https://api.wayfinder.ai")
34
- job_id: str | None = None
35
- job_type: str = "strategy"
36
- update_interval: int = 60
37
- max_retries: int = 3
38
- retry_delay: int = 5
39
- log_path: str | None = None
40
- data_path: str | None = None
41
- wallet_id: str | None = None
42
-
43
-
44
- @dataclass
45
- class StrategyJobConfig:
46
- system: SystemConfig
47
- strategy_config: dict[str, Any] = field(default_factory=dict)
48
-
49
- def __post_init__(self) -> None:
50
- try:
51
- if not isinstance(self.strategy_config, dict):
52
- self.strategy_config = {}
53
-
54
- wallet_type = self._get_wallet_type()
55
- if wallet_type and wallet_type != "local":
56
- return
57
-
58
- by_label, by_addr = self._load_wallets_from_file()
59
-
60
- self._enrich_wallet_addresses(by_label)
61
- if wallet_type in (None, "local"):
62
- self._enrich_wallet_private_keys(by_addr)
63
- except Exception as e:
64
- logger.warning(
65
- f"Failed to enrich strategy config with wallet information: {e}"
66
- )
67
-
68
- def _get_wallet_type(self) -> str | None:
69
- wallet_type = self.strategy_config.get("wallet_type")
70
- if wallet_type:
71
- return wallet_type
72
-
73
- main_wallet = self.strategy_config.get("main_wallet")
74
- if isinstance(main_wallet, dict):
75
- wallet_type = main_wallet.get("wallet_type")
76
- if wallet_type:
77
- return wallet_type
78
-
79
- strategy_wallet = self.strategy_config.get("strategy_wallet")
80
- if isinstance(strategy_wallet, dict):
81
- wallet_type = strategy_wallet.get("wallet_type")
82
- if wallet_type:
83
- return wallet_type
84
-
85
- return None
86
-
87
- def _load_wallets_from_file(
88
- self,
89
- ) -> tuple[dict[str, dict[str, Any]], dict[str, dict[str, Any]]]:
90
- entries = _read_wallets_from_config()
91
- by_label: dict[str, dict[str, Any]] = {}
92
- by_addr: dict[str, dict[str, Any]] = {}
93
-
94
- if entries and isinstance(entries, list):
95
- for e in entries:
96
- if isinstance(e, dict):
97
- label = e.get("label")
98
- if isinstance(label, str):
99
- by_label[label] = e
100
- addr = e.get("address")
101
- if isinstance(addr, str):
102
- by_addr[addr.lower()] = e
103
-
104
- return by_label, by_addr
105
-
106
- def _enrich_wallet_addresses(self, by_label: dict[str, dict[str, Any]]) -> None:
107
- if "main_wallet" not in self.strategy_config:
108
- main_wallet = by_label.get("main")
109
- if main_wallet:
110
- self.strategy_config["main_wallet"] = {
111
- "address": main_wallet["address"]
112
- }
113
-
114
- strategy_name = self.strategy_config.get("_strategy_name")
115
- if strategy_name and isinstance(strategy_name, str):
116
- strategy_wallet = by_label.get(strategy_name)
117
- if strategy_wallet:
118
- if "strategy_wallet" not in self.strategy_config:
119
- self.strategy_config["strategy_wallet"] = {
120
- "address": strategy_wallet["address"]
121
- }
122
- elif isinstance(self.strategy_config.get("strategy_wallet"), dict):
123
- if not self.strategy_config["strategy_wallet"].get("address"):
124
- self.strategy_config["strategy_wallet"]["address"] = (
125
- strategy_wallet["address"]
126
- )
127
-
128
- def _enrich_wallet_private_keys(self, by_addr: dict[str, dict[str, Any]]) -> None:
129
- try:
130
- for key in ("main_wallet", "strategy_wallet"):
131
- wallet_obj = self.strategy_config.get(key)
132
- if isinstance(wallet_obj, dict):
133
- addr = (wallet_obj.get("address") or "").lower()
134
- entry = by_addr.get(addr)
135
- if entry:
136
- pk = entry.get("private_key") or entry.get("private_key_hex")
137
- if (
138
- pk
139
- and not wallet_obj.get("private_key")
140
- and not wallet_obj.get("private_key_hex")
141
- ):
142
- wallet_obj["private_key_hex"] = pk
143
- except Exception as e:
144
- logger.warning(
145
- f"Failed to enrich wallet private keys from config.json: {e}"
146
- )
147
-
148
- @classmethod
149
- def from_dict(
150
- cls, data: dict[str, Any], strategy_name: str | None = None
151
- ) -> "StrategyJobConfig":
152
- system_data = data.get("system", {})
153
- sys_cfg = SystemConfig(
154
- api_base_url=system_data.get("api_base_url", "https://api.wayfinder.ai"),
155
- job_id=system_data.get("job_id"),
156
- job_type=system_data.get("job_type", "strategy"),
157
- update_interval=system_data.get("update_interval", 60),
158
- max_retries=system_data.get("max_retries", 3),
159
- retry_delay=system_data.get("retry_delay", 5),
160
- log_path=system_data.get("log_path"),
161
- data_path=system_data.get("data_path"),
162
- wallet_id=system_data.get("wallet_id"),
163
- )
164
-
165
- strategy_config = data.get("strategy", {})
166
- if strategy_name:
167
- strategy_config["_strategy_name"] = strategy_name
168
- return cls(
169
- system=sys_cfg,
170
- strategy_config=strategy_config,
171
- )
172
20
 
173
21
 
174
22
  def set_rpc_urls(rpc_urls):
@@ -189,15 +37,3 @@ def get_api_base_url() -> str:
189
37
  if api_url and isinstance(api_url, str):
190
38
  return api_url.strip()
191
39
  return "https://wayfinder.ai/api/v1"
192
-
193
-
194
- def _read_wallets_from_config() -> list[dict[str, Any]]:
195
- try:
196
- wallets = CONFIG.get("wallets", [])
197
- if isinstance(wallets, list):
198
- return wallets
199
- logger.warning("Wallets section in config.json is not a list")
200
- return []
201
- except Exception as e:
202
- logger.warning(f"Failed to read wallets from config.json: {e}")
203
- return []
@@ -1,17 +1,73 @@
1
- from .base import (
2
- CHAIN_CODE_TO_ID,
1
+ from wayfinder_paths.core.constants.base import (
2
+ ADAPTER_BALANCE,
3
+ ADAPTER_BRAP,
4
+ ADAPTER_HYPERLEND,
5
+ ADAPTER_HYPERLIQUID,
6
+ ADAPTER_LEDGER,
7
+ ADAPTER_MOONWELL,
8
+ ADAPTER_POOL,
9
+ ADAPTER_TOKEN,
10
+ DEFAULT_HTTP_TIMEOUT,
3
11
  DEFAULT_NATIVE_GAS_UNITS,
12
+ DEFAULT_PAGINATION_LIMIT,
4
13
  DEFAULT_SLIPPAGE,
5
14
  GAS_BUFFER_MULTIPLIER,
15
+ MANTISSA,
16
+ MAX_UINT256,
6
17
  ONE_GWEI,
18
+ SECONDS_PER_YEAR,
19
+ SUGGESTED_GAS_PRICE_MULTIPLIER,
20
+ SUGGESTED_PRIORITY_FEE_MULTIPLIER,
21
+ )
22
+ from wayfinder_paths.core.constants.chains import (
23
+ CHAIN_CODE_TO_ID,
24
+ CHAIN_ID_ARBITRUM,
25
+ CHAIN_ID_AVALANCHE,
26
+ CHAIN_ID_BASE,
27
+ CHAIN_ID_BSC,
28
+ CHAIN_ID_ETHEREUM,
29
+ CHAIN_ID_HYPEREVM,
30
+ CHAIN_ID_POLYGON,
31
+ POA_MIDDLEWARE_CHAIN_IDS,
32
+ PRE_EIP_1559_CHAIN_IDS,
33
+ SUPPORTED_CHAINS,
34
+ )
35
+ from wayfinder_paths.core.constants.contracts import (
36
+ NATIVE_TOKEN_SENTINEL,
7
37
  ZERO_ADDRESS,
8
38
  )
9
39
 
10
40
  __all__ = [
41
+ "NATIVE_TOKEN_SENTINEL",
11
42
  "ZERO_ADDRESS",
12
43
  "CHAIN_CODE_TO_ID",
13
44
  "DEFAULT_NATIVE_GAS_UNITS",
14
45
  "GAS_BUFFER_MULTIPLIER",
15
46
  "ONE_GWEI",
47
+ "SUGGESTED_PRIORITY_FEE_MULTIPLIER",
48
+ "SUGGESTED_GAS_PRICE_MULTIPLIER",
16
49
  "DEFAULT_SLIPPAGE",
50
+ "DEFAULT_HTTP_TIMEOUT",
51
+ "DEFAULT_PAGINATION_LIMIT",
52
+ "MANTISSA",
53
+ "SECONDS_PER_YEAR",
54
+ "MAX_UINT256",
55
+ "ADAPTER_BALANCE",
56
+ "ADAPTER_BRAP",
57
+ "ADAPTER_MOONWELL",
58
+ "ADAPTER_HYPERLIQUID",
59
+ "ADAPTER_POOL",
60
+ "ADAPTER_TOKEN",
61
+ "ADAPTER_LEDGER",
62
+ "ADAPTER_HYPERLEND",
63
+ "CHAIN_ID_ETHEREUM",
64
+ "CHAIN_ID_BASE",
65
+ "CHAIN_ID_ARBITRUM",
66
+ "CHAIN_ID_BSC",
67
+ "CHAIN_ID_POLYGON",
68
+ "CHAIN_ID_AVALANCHE",
69
+ "CHAIN_ID_HYPEREVM",
70
+ "SUPPORTED_CHAINS",
71
+ "POA_MIDDLEWARE_CHAIN_IDS",
72
+ "PRE_EIP_1559_CHAIN_IDS",
17
73
  ]
@@ -1,30 +1,13 @@
1
- ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
2
-
3
- # Chain code to EVM chain id mapping
4
- CHAIN_CODE_TO_ID = {
5
- "base": 8453,
6
- "arbitrum": 42161,
7
- "arbitrum-one": 42161,
8
- "ethereum": 1,
9
- "mainnet": 1,
10
- "hyperevm": 999,
11
- }
12
-
13
- # Gas/defaults
14
1
  DEFAULT_NATIVE_GAS_UNITS = 21000
15
- # Fallback gas limit used only when RPC gas estimation fails for non-revert reasons.
16
- # Must be high enough for typical DeFi interactions (lending, swaps, etc.).
17
2
  GAS_BUFFER_MULTIPLIER = 1.1
18
3
  ONE_GWEI = 1_000_000_000
4
+ SUGGESTED_PRIORITY_FEE_MULTIPLIER = 1.5
5
+ SUGGESTED_GAS_PRICE_MULTIPLIER = 1.5
6
+
19
7
  DEFAULT_SLIPPAGE = 0.005
20
8
 
21
- # Timeout constants (seconds)
22
- # Base L2 (and some RPC providers) can occasionally take >2 minutes to index/return receipts,
23
- # even if the transaction is eventually mined. A longer timeout reduces false negatives that
24
- # can lead to unsafe retry behavior (nonce gaps, duplicate swaps, etc.).
25
- DEFAULT_HTTP_TIMEOUT = 30.0 # HTTP client timeout
9
+ DEFAULT_HTTP_TIMEOUT = 30.0
26
10
 
27
- # Adapter type identifiers
28
11
  ADAPTER_BALANCE = "BALANCE"
29
12
  ADAPTER_BRAP = "BRAP"
30
13
  ADAPTER_MOONWELL = "MOONWELL"
@@ -34,5 +17,8 @@ ADAPTER_TOKEN = "TOKEN"
34
17
  ADAPTER_LEDGER = "LEDGER"
35
18
  ADAPTER_HYPERLEND = "HYPERLEND"
36
19
 
37
- # Pagination defaults
38
20
  DEFAULT_PAGINATION_LIMIT = 50
21
+
22
+ MANTISSA = 10**18
23
+ SECONDS_PER_YEAR = 365 * 24 * 60 * 60
24
+ MAX_UINT256 = 2**256 - 1
@@ -0,0 +1,36 @@
1
+ CHAIN_ID_ETHEREUM = 1
2
+ CHAIN_ID_BASE = 8453
3
+ CHAIN_ID_ARBITRUM = 42161
4
+ CHAIN_ID_BSC = 56
5
+ CHAIN_ID_POLYGON = 137
6
+ CHAIN_ID_AVALANCHE = 43114
7
+ CHAIN_ID_HYPEREVM = 999
8
+
9
+ CHAIN_CODE_TO_ID = {
10
+ "base": CHAIN_ID_BASE,
11
+ "arbitrum": CHAIN_ID_ARBITRUM,
12
+ "arbitrum-one": CHAIN_ID_ARBITRUM,
13
+ "ethereum": CHAIN_ID_ETHEREUM,
14
+ "mainnet": CHAIN_ID_ETHEREUM,
15
+ "hyperevm": CHAIN_ID_HYPEREVM,
16
+ }
17
+
18
+ SUPPORTED_CHAINS = [
19
+ CHAIN_ID_ETHEREUM,
20
+ CHAIN_ID_BASE,
21
+ CHAIN_ID_BSC,
22
+ CHAIN_ID_ARBITRUM,
23
+ CHAIN_ID_POLYGON,
24
+ CHAIN_ID_HYPEREVM,
25
+ ]
26
+
27
+ POA_MIDDLEWARE_CHAIN_IDS: set[int] = {
28
+ CHAIN_ID_BSC,
29
+ CHAIN_ID_POLYGON,
30
+ CHAIN_ID_AVALANCHE,
31
+ }
32
+
33
+ PRE_EIP_1559_CHAIN_IDS: set[int] = {
34
+ CHAIN_ID_BSC,
35
+ CHAIN_ID_ARBITRUM,
36
+ }
@@ -0,0 +1,39 @@
1
+ ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
2
+ NATIVE_TOKEN_SENTINEL = "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"
3
+
4
+ BASE_WETH = "0x4200000000000000000000000000000000000006"
5
+ BASE_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
6
+ BASE_WSTETH = "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452"
7
+
8
+ MOONWELL_M_USDC = "0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22"
9
+ MOONWELL_M_WETH = "0x628ff693426583D9a7FB391E54366292F509D457"
10
+ MOONWELL_M_WSTETH = "0x627Fe393Bc6EdDA28e99AE648fD6fF362514304b"
11
+ MOONWELL_COMPTROLLER = "0xfbb21d0380bee3312b33c4353c8936a0f13ef26c"
12
+ MOONWELL_REWARD_DISTRIBUTOR = "0xe9005b078701e2a0948d2eac43010d35870ad9d2"
13
+ MOONWELL_WELL_TOKEN = "0xA88594D404727625A9437C3f886C7643872296AE"
14
+
15
+ ENSO_ROUTER = "0xf75584ef6673ad213a685a1b58cc0330b8ea22cf"
16
+
17
+ HYPEREVM_WHYPE = "0x5555555555555555555555555555555555555555"
18
+ HYPERCORE_SENTINEL_ADDRESS = "0x2222222222222222222222222222222222222222"
19
+ HYPERCORE_SENTINEL_VALUE = 100_000_000_000
20
+
21
+ HYPERLEND_POOL = "0x00A89d7a5A02160f20150EbEA7a2b5E4879A1A8b"
22
+ HYPERLEND_WRAPPED_TOKEN_GATEWAY = "0x49558c794ea2aC8974C9F27886DDfAa951E99171"
23
+
24
+ PRJX_ROUTER = "0x1ebdfc75ffe3ba3de61e7138a3e8706ac841af9b"
25
+ PRJX_NPM = "0xeAd19AE861c29bBb2101E834922B2FEee69B9091"
26
+
27
+ ARBITRUM_USDC = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"
28
+
29
+ HYPERLIQUID_BRIDGE = "0x2Df1c51E09aECF9cacB7bc98cB1742757f163dF7"
30
+
31
+ USDT_ETHEREUM = "0xdac17f958d2ee523a2206206994597c13d831ec7"
32
+ USDT_POLYGON = "0xc2132d05d31c914a87c6611c10748aeb04b58e8f"
33
+ USDT_BSC = "0x55d398326f99059ff775485246999027b3197955"
34
+
35
+ TOKENS_REQUIRING_APPROVAL_RESET: set[tuple[int, str]] = {
36
+ (1, USDT_ETHEREUM),
37
+ (137, USDT_POLYGON),
38
+ (56, USDT_BSC),
39
+ }
@@ -0,0 +1,9 @@
1
+ TOKEN_ID_USDC_BASE = "usd-coin-base"
2
+ TOKEN_ID_WETH_BASE = "l2-standard-bridged-weth-base-base"
3
+ TOKEN_ID_WSTETH_BASE = "superbridge-bridged-wsteth-base-base"
4
+ TOKEN_ID_ETH_BASE = "ethereum-base"
5
+ TOKEN_ID_WELL_BASE = "moonwell-artemis-base"
6
+
7
+ TOKEN_ID_STETH = "staked-ether-ethereum"
8
+
9
+ TOKEN_ID_USDC_ARBITRUM = "usd-coin-arbitrum"
@@ -95,16 +95,6 @@ class Strategy(ABC):
95
95
  pass
96
96
 
97
97
  async def withdraw(self, **kwargs) -> StatusTuple:
98
- if hasattr(self, "ledger_adapter") and self.ledger_adapter:
99
- while self.ledger_adapter.positions.operations:
100
- node = self.ledger_adapter.positions.operations[-1]
101
- adapter = self.adapters.get(node.adapter)
102
- if adapter and hasattr(adapter, "unwind_op"):
103
- await adapter.unwind_op(node)
104
- self.ledger_adapter.positions.operations.pop()
105
-
106
- await self.ledger_adapter.save()
107
-
108
98
  return (True, "Withdrawal complete")
109
99
 
110
100
  @abstractmethod
@@ -4,25 +4,15 @@ from typing import Any
4
4
 
5
5
  from loguru import logger
6
6
 
7
- from wayfinder_paths.core.constants.base import CHAIN_CODE_TO_ID
7
+ from wayfinder_paths.core.constants.chains import CHAIN_CODE_TO_ID
8
8
 
9
9
 
10
- def chain_code_to_chain_id(chain_code: str | None) -> int | None:
11
- if not chain_code:
12
- return None
13
- return CHAIN_CODE_TO_ID.get(chain_code.lower())
14
-
15
-
16
- def resolve_chain_id(token_info: dict[str, Any], logger_instance=None) -> int | None:
17
- log = logger_instance or logger
10
+ def resolve_chain_id(token_info: dict[str, Any]) -> int | None:
18
11
  chain_meta = token_info.get("chain") or {}
19
12
  chain_id = chain_meta.get("id")
20
- try:
21
- if chain_id is not None:
22
- return int(chain_id)
23
- except (ValueError, TypeError):
24
- log.debug("Invalid chain_id in token_info.chain: %s", chain_id)
25
- return chain_code_to_chain_id(chain_meta.get("code"))
13
+ if chain_id is not None:
14
+ return int(chain_id)
15
+ return CHAIN_CODE_TO_ID.get(chain_meta.get("code").lower())
26
16
 
27
17
 
28
18
  def resolve_rpc_url(
@@ -1,6 +1,10 @@
1
+ from collections.abc import Callable
2
+ from typing import Any
3
+
1
4
  from web3 import AsyncWeb3
2
5
 
3
6
  from wayfinder_paths.core.constants.erc20_abi import ERC20_ABI
7
+ from wayfinder_paths.core.utils.transaction import send_transaction
4
8
  from wayfinder_paths.core.utils.web3 import web3_from_chain_id
5
9
 
6
10
  NATIVE_TOKEN_ADDRESSES: set = {
@@ -102,3 +106,27 @@ async def build_send_transaction(
102
106
  "data": data,
103
107
  "chainId": chain_id,
104
108
  }
109
+
110
+
111
+ async def ensure_allowance(
112
+ *,
113
+ token_address: str,
114
+ owner: str,
115
+ spender: str,
116
+ amount: int,
117
+ chain_id: int,
118
+ signing_callback: Callable,
119
+ approval_amount: int | None = None,
120
+ ) -> tuple[bool, Any]:
121
+ allowance = await get_token_allowance(token_address, chain_id, owner, spender)
122
+ if allowance >= amount:
123
+ return True, {}
124
+ approve_tx = await build_approve_transaction(
125
+ from_address=owner,
126
+ chain_id=chain_id,
127
+ token_address=token_address,
128
+ spender_address=spender,
129
+ amount=approval_amount if approval_amount is not None else amount,
130
+ )
131
+ txn_hash = await send_transaction(approve_tx, signing_callback)
132
+ return True, txn_hash
@@ -4,17 +4,20 @@ from collections.abc import Callable
4
4
  from loguru import logger
5
5
  from web3 import AsyncWeb3
6
6
 
7
+ from wayfinder_paths.core.constants.base import (
8
+ SUGGESTED_GAS_PRICE_MULTIPLIER,
9
+ SUGGESTED_PRIORITY_FEE_MULTIPLIER,
10
+ )
11
+ from wayfinder_paths.core.constants.chains import (
12
+ CHAIN_ID_HYPEREVM,
13
+ PRE_EIP_1559_CHAIN_IDS,
14
+ )
7
15
  from wayfinder_paths.core.utils.web3 import (
8
16
  get_transaction_chain_id,
9
17
  web3_from_chain_id,
10
18
  web3s_from_chain_id,
11
19
  )
12
20
 
13
- PRE_EIP_1559_CHAIN_IDS: set = {56, 42161}
14
-
15
- SUGGESTED_PRIORITY_FEE_MULTIPLIER = 1.5
16
- SUGGESTED_GAS_PRICE_MULTIPLIER = 1.5
17
-
18
21
 
19
22
  def _get_transaction_from_address(transaction: dict) -> str:
20
23
  if "from" not in transaction:
@@ -71,7 +74,7 @@ async def gas_price_transaction(
71
74
  gas_price = max(gas_prices)
72
75
 
73
76
  transaction["gasPrice"] = int(gas_price * gas_price_multiplier)
74
- elif chain_id == 999:
77
+ elif chain_id == CHAIN_ID_HYPEREVM:
75
78
  # HyperEVM big blocks fetch base gas price from a different RPC method. Priority fee = 0 is # grandfathered in from Django, not sure what's right here.
76
79
  big_block_gas_prices = await asyncio.gather(
77
80
  *[web3.hype.big_block_gas_price() for web3 in web3s]
@@ -111,7 +114,7 @@ async def gas_limit_transaction(transaction: dict):
111
114
 
112
115
  async def _estimate_gas(web3: AsyncWeb3, transaction: dict) -> int:
113
116
  try:
114
- return await web3.eth.estimate_gas(transaction, block_identifier="pending")
117
+ return await web3.eth.estimate_gas(transaction, block_identifier="latest")
115
118
  except Exception as e:
116
119
  logger.info(
117
120
  f"Failed to estimate gas using {web3.provider.endpoint_uri}. Error: {e}"
@@ -174,6 +177,9 @@ async def wait_for_transaction_receipt(
174
177
  async def send_transaction(
175
178
  transaction: dict, sign_callback: Callable, wait_for_receipt=True
176
179
  ) -> str:
180
+ if sign_callback is None:
181
+ raise ValueError("sign_callback must be provided to send transaction")
182
+
177
183
  logger.info(f"Broadcasting transaction {transaction.get('to', 'unknown')[:10]}...")
178
184
  chain_id = get_transaction_chain_id(transaction)
179
185
  transaction = await gas_limit_transaction(transaction)
@@ -5,8 +5,10 @@ from web3.middleware import ExtraDataToPOAMiddleware
5
5
  from web3.module import Module
6
6
 
7
7
  from wayfinder_paths.core.config import get_rpc_urls
8
-
9
- POA_MIDDLEWARE_CHAIN_IDS: set = {56, 137, 43114}
8
+ from wayfinder_paths.core.constants.chains import (
9
+ CHAIN_ID_HYPEREVM,
10
+ POA_MIDDLEWARE_CHAIN_IDS,
11
+ )
10
12
 
11
13
 
12
14
  class HyperModule(Module):
@@ -31,7 +33,7 @@ def _get_web3(rpc: str, chain_id: int) -> AsyncWeb3:
31
33
  web3 = AsyncWeb3(AsyncHTTPProvider(rpc))
32
34
  if chain_id in POA_MIDDLEWARE_CHAIN_IDS:
33
35
  web3.middleware_onion.inject(ExtraDataToPOAMiddleware, layer=0)
34
- if chain_id == 999:
36
+ if chain_id == CHAIN_ID_HYPEREVM:
35
37
  web3.attach_modules({"hype": (HyperModule)})
36
38
  return web3
37
39
 
@@ -1,7 +1,6 @@
1
+ from wayfinder_paths.core.constants.contracts import ENSO_ROUTER
1
2
  from wayfinder_paths.policies.util import allow_functions
2
3
 
3
- ENSO_ROUTER = "0xf75584ef6673ad213a685a1b58cc0330b8ea22cf"
4
-
5
4
 
6
5
  async def enso_swap():
7
6
  return await allow_functions(
@@ -1,9 +1,12 @@
1
+ from wayfinder_paths.core.constants.contracts import (
2
+ HYPERCORE_SENTINEL_ADDRESS,
3
+ HYPERCORE_SENTINEL_VALUE,
4
+ HYPEREVM_WHYPE,
5
+ )
1
6
  from wayfinder_paths.policies.evm import native_transfer
2
7
  from wayfinder_paths.policies.util import allow_functions
3
8
 
4
- WHYPE_TOKEN = "0x5555555555555555555555555555555555555555"
5
- HYPERCORE_SENTINEL_ADDRESS = "0x2222222222222222222222222222222222222222"
6
- HYPERCORE_SENTINEL_VALUE = 100_000_000_000
9
+ WHYPE_TOKEN = HYPEREVM_WHYPE
7
10
 
8
11
 
9
12
  def hypecore_sentinel_deposit():
@@ -1,7 +1,6 @@
1
+ from wayfinder_paths.core.constants.contracts import HYPERLEND_POOL
1
2
  from wayfinder_paths.policies.util import allow_functions
2
3
 
3
- HYPERLEND_POOL = "0x00A89d7a5A02160f20150EbEA7a2b5E4879A1A8b"
4
-
5
4
 
6
5
  async def hyperlend_supply_and_withdraw():
7
6
  return await allow_functions(
@@ -1,12 +1,17 @@
1
+ from wayfinder_paths.core.constants.contracts import (
2
+ BASE_WETH,
3
+ MOONWELL_COMPTROLLER,
4
+ MOONWELL_M_USDC,
5
+ MOONWELL_M_WETH,
6
+ MOONWELL_M_WSTETH,
7
+ )
1
8
  from wayfinder_paths.policies.util import allow_functions
2
9
 
3
- WETH = "0x4200000000000000000000000000000000000006"
4
-
5
- M_USDC = "0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22"
6
- M_WETH = "0x628ff693426583D9a7FB391E54366292F509D457"
7
- M_WSTETH = "0x627Fe393Bc6EdDA28e99AE648fD6fF362514304b"
8
-
9
- COMPTROLLER = "0xfbb21d0380bee3312b33c4353c8936a0f13ef26c"
10
+ WETH = BASE_WETH
11
+ M_USDC = MOONWELL_M_USDC
12
+ M_WETH = MOONWELL_M_WETH
13
+ M_WSTETH = MOONWELL_M_WSTETH
14
+ COMPTROLLER = MOONWELL_COMPTROLLER
10
15
 
11
16
 
12
17
  async def weth_deposit():
@@ -1,8 +1,6 @@
1
+ from wayfinder_paths.core.constants.contracts import PRJX_NPM, PRJX_ROUTER
1
2
  from wayfinder_paths.policies.util import allow_functions
2
3
 
3
- PRJX_ROUTER = "0x1ebdfc75ffe3ba3de61e7138a3e8706ac841af9b"
4
- PRJX_NPM = "0xeAd19AE861c29bBb2101E834922B2FEee69B9091"
5
-
6
4
 
7
5
  async def prjx_swap():
8
6
  return await allow_functions(