wayfinder-paths 0.1.15__py3-none-any.whl → 0.1.17__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 (47) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +19 -20
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +66 -37
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +2 -8
  4. wayfinder_paths/adapters/brap_adapter/README.md +22 -19
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +33 -34
  6. wayfinder_paths/adapters/brap_adapter/test_adapter.py +2 -18
  7. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +45 -59
  8. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +1 -8
  9. wayfinder_paths/adapters/moonwell_adapter/README.md +29 -31
  10. wayfinder_paths/adapters/moonwell_adapter/adapter.py +301 -662
  11. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +275 -179
  12. wayfinder_paths/core/config.py +8 -47
  13. wayfinder_paths/core/constants/base.py +0 -1
  14. wayfinder_paths/core/constants/erc20_abi.py +13 -13
  15. wayfinder_paths/core/strategies/Strategy.py +6 -2
  16. wayfinder_paths/core/utils/erc20_service.py +100 -0
  17. wayfinder_paths/core/utils/evm_helpers.py +1 -1
  18. wayfinder_paths/core/utils/transaction.py +191 -0
  19. wayfinder_paths/core/utils/web3.py +66 -0
  20. wayfinder_paths/run_strategy.py +37 -6
  21. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +200 -224
  22. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +128 -151
  23. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +0 -1
  24. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +52 -78
  25. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -12
  26. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +0 -1
  27. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +39 -64
  28. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +0 -1
  29. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +42 -85
  30. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -8
  31. wayfinder_paths/templates/strategy/README.md +1 -5
  32. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.17.dist-info}/METADATA +3 -41
  33. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.17.dist-info}/RECORD +35 -44
  34. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.17.dist-info}/WHEEL +1 -1
  35. wayfinder_paths/core/clients/sdk_example.py +0 -125
  36. wayfinder_paths/core/engine/__init__.py +0 -5
  37. wayfinder_paths/core/services/__init__.py +0 -0
  38. wayfinder_paths/core/services/base.py +0 -131
  39. wayfinder_paths/core/services/local_evm_txn.py +0 -350
  40. wayfinder_paths/core/services/local_token_txn.py +0 -238
  41. wayfinder_paths/core/services/web3_service.py +0 -43
  42. wayfinder_paths/core/wallets/README.md +0 -88
  43. wayfinder_paths/core/wallets/WalletManager.py +0 -56
  44. wayfinder_paths/core/wallets/__init__.py +0 -7
  45. wayfinder_paths/scripts/run_strategy.py +0 -152
  46. wayfinder_paths/strategies/config.py +0 -85
  47. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.17.dist-info}/LICENSE +0 -0
@@ -12,12 +12,16 @@ from wayfinder_paths.core.clients.HyperlendClient import (
12
12
  MarketEntry,
13
13
  StableMarketsHeadroomResponse,
14
14
  )
15
- from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
16
15
  from wayfinder_paths.core.constants.hyperlend_abi import (
17
16
  POOL_ABI,
18
17
  WRAPPED_TOKEN_GATEWAY_ABI,
19
18
  )
20
- from wayfinder_paths.core.services.base import Web3Service
19
+ from wayfinder_paths.core.utils.erc20_service import (
20
+ build_approve_transaction,
21
+ get_token_allowance,
22
+ )
23
+ from wayfinder_paths.core.utils.transaction import send_transaction
24
+ from wayfinder_paths.core.utils.web3 import web3_from_chain_id
21
25
 
22
26
  HYPERLEND_DEFAULTS = {
23
27
  "pool": "0x00A89d7a5A02160f20150EbEA7a2b5E4879A1A8b",
@@ -34,15 +38,16 @@ class HyperlendAdapter(BaseAdapter):
34
38
  def __init__(
35
39
  self,
36
40
  config: dict[str, Any],
37
- web3_service: Web3Service,
41
+ simulation: bool = False,
42
+ strategy_wallet_signing_callback=None,
38
43
  ) -> None:
39
44
  super().__init__("hyperlend_adapter", config)
40
45
  cfg = config or {}
41
46
  adapter_cfg = cfg.get("hyperlend_adapter") or {}
42
47
 
48
+ self.simulation = simulation
49
+ self.strategy_wallet_signing_callback = strategy_wallet_signing_callback
43
50
  self.hyperlend_client = HyperlendClient()
44
- self.web3 = web3_service
45
- self.token_txn_service = web3_service.token_transactions
46
51
 
47
52
  self.strategy_wallet = cfg.get("strategy_wallet") or {}
48
53
  self.pool_address = self._checksum(
@@ -77,20 +82,6 @@ class HyperlendAdapter(BaseAdapter):
77
82
  buffer_bps=buffer_bps,
78
83
  min_buffer_tokens=min_buffer_tokens,
79
84
  )
80
- # Strategies expect a dict with "markets" and "notes"; normalize if API returns a list
81
- if isinstance(data, list):
82
- markets: dict[str, Any] = {}
83
- for i, item in enumerate(data):
84
- if isinstance(item, dict) and "address" in item:
85
- markets[item["address"]] = item
86
- elif isinstance(item, dict):
87
- markets[str(i)] = item
88
- data = {"markets": markets, "notes": []}
89
- elif isinstance(data, dict) and ("markets" not in data or "notes" not in data):
90
- data = {
91
- "markets": data.get("markets", {}),
92
- "notes": data.get("notes", []),
93
- }
94
85
  return True, data
95
86
  except Exception as exc:
96
87
  return False, str(exc)
@@ -179,7 +170,7 @@ class HyperlendAdapter(BaseAdapter):
179
170
  from_address=strategy,
180
171
  chain_id=chain_id,
181
172
  )
182
- return await self._execute(tx)
173
+ return await self._send_tx(tx)
183
174
 
184
175
  async def unlend(
185
176
  self,
@@ -200,7 +191,8 @@ class HyperlendAdapter(BaseAdapter):
200
191
  target=self.gateway_address,
201
192
  abi=WRAPPED_TOKEN_GATEWAY_ABI,
202
193
  fn_name="withdrawETH",
203
- args=[self._gateway_first_arg(underlying_token), qty, strategy],
194
+ args=[self._gateway_first_arg(
195
+ underlying_token), qty, strategy],
204
196
  from_address=strategy,
205
197
  chain_id=chain_id,
206
198
  )
@@ -214,12 +206,19 @@ class HyperlendAdapter(BaseAdapter):
214
206
  from_address=strategy,
215
207
  chain_id=chain_id,
216
208
  )
217
- return await self._execute(tx)
209
+ return await self._send_tx(tx)
218
210
 
219
211
  # ------------------------------------------------------------------ #
220
212
  # Helpers #
221
213
  # ------------------------------------------------------------------ #
222
214
 
215
+ async def _send_tx(self, tx: dict[str, Any]) -> tuple[bool, Any]:
216
+ """Send transaction with simulation check."""
217
+ if self.simulation:
218
+ return True, {"simulation": tx}
219
+ txn_hash = await send_transaction(tx, self.strategy_wallet_signing_callback)
220
+ return True, txn_hash
221
+
223
222
  async def _ensure_allowance(
224
223
  self,
225
224
  *,
@@ -229,32 +228,17 @@ class HyperlendAdapter(BaseAdapter):
229
228
  amount: int,
230
229
  chain_id: int,
231
230
  ) -> tuple[bool, Any]:
232
- chain = {"id": chain_id}
233
- allowance = await self.token_txn_service.read_erc20_allowance(
234
- chain, token_address, owner, spender
235
- )
236
- if allowance.get("allowance", 0) >= amount:
231
+ allowance = await get_token_allowance(token_address, chain_id, owner, spender)
232
+ if allowance >= amount:
237
233
  return True, {}
238
- build_success, approve_tx = self.token_txn_service.build_erc20_approve(
234
+ approve_tx = await build_approve_transaction(
235
+ from_address=owner,
239
236
  chain_id=chain_id,
240
237
  token_address=token_address,
241
- from_address=owner,
242
238
  spender=spender,
243
239
  amount=amount,
244
240
  )
245
- if not build_success:
246
- return False, approve_tx
247
- return await self._broadcast_transaction(approve_tx)
248
-
249
- async def _execute(self, tx: dict[str, Any]) -> tuple[bool, Any]:
250
- return await self.web3.broadcast_transaction(
251
- tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
252
- )
253
-
254
- async def _broadcast_transaction(self, tx: dict[str, Any]) -> tuple[bool, Any]:
255
- return await self.web3.evm_transactions.broadcast_transaction(
256
- tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
257
- )
241
+ return await self._send_tx(approve_tx)
258
242
 
259
243
  async def _encode_call(
260
244
  self,
@@ -268,23 +252,24 @@ class HyperlendAdapter(BaseAdapter):
268
252
  value: int = 0,
269
253
  ) -> dict[str, Any]:
270
254
  """Encode calldata without touching network."""
271
- web3 = self.web3.get_web3(chain_id)
272
- contract = web3.eth.contract(address=target, abi=abi)
273
- try:
274
- data = await getattr(contract.functions, fn_name)(*args).build_transaction(
275
- {"from": from_address}
276
- )["data"]
277
- except ValueError as exc:
278
- raise ValueError(f"Failed to encode {fn_name}: {exc}") from exc
279
255
 
280
- tx: dict[str, Any] = {
281
- "chainId": int(chain_id),
282
- "from": to_checksum_address(from_address),
283
- "to": to_checksum_address(target),
284
- "data": data,
285
- "value": int(value),
286
- }
287
- return tx
256
+ async with web3_from_chain_id(chain_id) as web3:
257
+ contract = web3.eth.contract(address=target, abi=abi)
258
+ try:
259
+ data = await getattr(contract.functions, fn_name)(
260
+ *args
261
+ ).build_transaction({"from": from_address})["data"]
262
+ except ValueError as exc:
263
+ raise ValueError(f"Failed to encode {fn_name}: {exc}") from exc
264
+
265
+ tx: dict[str, Any] = {
266
+ "chainId": int(chain_id),
267
+ "from": to_checksum_address(from_address),
268
+ "to": to_checksum_address(target),
269
+ "data": data,
270
+ "value": int(value),
271
+ }
272
+ return tx
288
273
 
289
274
  def _strategy_address(self) -> str:
290
275
  addr = None
@@ -307,5 +292,6 @@ class HyperlendAdapter(BaseAdapter):
307
292
 
308
293
  def _checksum(self, address: str | None) -> str:
309
294
  if not address:
310
- raise ValueError("Missing required contract address in HyperLend config")
295
+ raise ValueError(
296
+ "Missing required contract address in HyperLend config")
311
297
  return to_checksum_address(address)
@@ -1,4 +1,3 @@
1
- from types import SimpleNamespace
2
1
  from unittest.mock import AsyncMock
3
2
 
4
3
  import pytest
@@ -16,16 +15,10 @@ class TestHyperlendAdapter:
16
15
  return mock_client
17
16
 
18
17
  @pytest.fixture
19
- def mock_web3_service(self):
20
- """Minimal Web3Service stub for adapter construction."""
21
- return SimpleNamespace(token_transactions=SimpleNamespace())
22
-
23
- @pytest.fixture
24
- def adapter(self, mock_hyperlend_client, mock_web3_service):
18
+ def adapter(self, mock_hyperlend_client):
25
19
  """Create a HyperlendAdapter instance with mocked client for testing"""
26
20
  adapter = HyperlendAdapter(
27
21
  config={},
28
- web3_service=mock_web3_service,
29
22
  )
30
23
  adapter.hyperlend_client = mock_hyperlend_client
31
24
  return adapter
@@ -8,6 +8,7 @@ Adapter for interacting with the [Moonwell](https://moonwell.fi/) lending protoc
8
8
  ## Capabilities
9
9
 
10
10
  The adapter provides the following capabilities:
11
+
11
12
  - Lending: Supply and withdraw tokens
12
13
  - Borrowing: Borrow and repay tokens
13
14
  - Collateral management: Enable/disable markets as collateral
@@ -17,6 +18,7 @@ The adapter provides the following capabilities:
17
18
  ## Overview
18
19
 
19
20
  The MoonwellAdapter provides functionality for:
21
+
20
22
  - **Lending**: Supply tokens to earn yield
21
23
  - **Borrowing**: Borrow against collateral
22
24
  - **Collateral Management**: Enable/disable markets as collateral
@@ -25,10 +27,10 @@ The MoonwellAdapter provides functionality for:
25
27
 
26
28
  ## Supported Markets (Base Chain)
27
29
 
28
- | Token | mToken Address | Underlying Address |
29
- |-------|----------------|-------------------|
30
- | USDC | `0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
31
- | WETH | `0x628ff693426583D9a7FB391E54366292F509D457` | `0x4200000000000000000000000000000000000006` |
30
+ | Token | mToken Address | Underlying Address |
31
+ | ------ | -------------------------------------------- | -------------------------------------------- |
32
+ | USDC | `0xEdc817A28E8B93B03976FBd4a3dDBc9f7D176c22` | `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913` |
33
+ | WETH | `0x628ff693426583D9a7FB391E54366292F509D457` | `0x4200000000000000000000000000000000000006` |
32
34
  | wstETH | `0x627Fe393Bc6EdDA28e99AE648fD6fF362514304b` | `0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452` |
33
35
 
34
36
  ## Protocol Addresses (Base Chain)
@@ -40,7 +42,6 @@ The MoonwellAdapter provides functionality for:
40
42
  ## Construction
41
43
 
42
44
  ```python
43
- from wayfinder_paths.core.services.web3_service import DefaultWeb3Service
44
45
  from wayfinder_paths.adapters.moonwell_adapter import MoonwellAdapter
45
46
 
46
47
  config = {
@@ -49,12 +50,9 @@ config = {
49
50
  "chain_id": 8453, # Base chain (default)
50
51
  }
51
52
  }
52
- web3_service = DefaultWeb3Service(config)
53
- adapter = MoonwellAdapter(config=config, web3_service=web3_service)
53
+ adapter = MoonwellAdapter(config=config)
54
54
  ```
55
55
 
56
- `web3_service` is required so the adapter can share the same wallet provider as the rest of the strategy.
57
-
58
56
  ## Usage
59
57
 
60
58
  ```python
@@ -102,45 +100,45 @@ await adapter.claim_rewards()
102
100
 
103
101
  ### Lending Operations
104
102
 
105
- | Method | Description |
106
- |--------|-------------|
107
- | `lend(mtoken, underlying_token, amount)` | Supply tokens to earn yield |
108
- | `unlend(mtoken, amount)` | Withdraw by redeeming mTokens |
103
+ | Method | Description |
104
+ | ---------------------------------------- | ----------------------------- |
105
+ | `lend(mtoken, underlying_token, amount)` | Supply tokens to earn yield |
106
+ | `unlend(mtoken, amount)` | Withdraw by redeeming mTokens |
109
107
 
110
108
  ### Borrowing Operations
111
109
 
112
- | Method | Description |
113
- |--------|-------------|
114
- | `borrow(mtoken, amount)` | Borrow underlying tokens |
115
- | `repay(mtoken, underlying_token, amount)` | Repay borrowed tokens |
110
+ | Method | Description |
111
+ | ----------------------------------------- | ------------------------ |
112
+ | `borrow(mtoken, amount)` | Borrow underlying tokens |
113
+ | `repay(mtoken, underlying_token, amount)` | Repay borrowed tokens |
116
114
 
117
115
  ### Collateral Management
118
116
 
119
- | Method | Description |
120
- |--------|-------------|
121
- | `set_collateral(mtoken)` | Enable market as collateral (enterMarkets) |
122
- | `remove_collateral(mtoken)` | Disable market as collateral (exitMarket) |
117
+ | Method | Description |
118
+ | --------------------------- | ------------------------------------------ |
119
+ | `set_collateral(mtoken)` | Enable market as collateral (enterMarkets) |
120
+ | `remove_collateral(mtoken)` | Disable market as collateral (exitMarket) |
123
121
 
124
122
  ### Position & Market Data
125
123
 
126
- | Method | Description |
127
- |--------|-------------|
128
- | `get_pos(mtoken, account)` | Get position data (balances, debt, rewards) |
129
- | `get_collateral_factor(mtoken)` | Get collateral factor (LTV) |
130
- | `get_apy(mtoken, apy_type)` | Get supply or borrow APY |
131
- | `get_borrowable_amount(account)` | Get max borrowable in USD |
124
+ | Method | Description |
125
+ | ------------------------------------------ | -------------------------------------------------- |
126
+ | `get_pos(mtoken, account)` | Get position data (balances, debt, rewards) |
127
+ | `get_collateral_factor(mtoken)` | Get collateral factor (LTV) |
128
+ | `get_apy(mtoken, apy_type)` | Get supply or borrow APY |
129
+ | `get_borrowable_amount(account)` | Get max borrowable in USD |
132
130
  | `max_withdrawable_mtoken(mtoken, account)` | Calculate safe withdrawal amount via binary search |
133
131
 
134
132
  ### Rewards
135
133
 
136
- | Method | Description |
137
- |--------|-------------|
134
+ | Method | Description |
135
+ | -------------------------------- | --------------------------------------------- |
138
136
  | `claim_rewards(min_rewards_usd)` | Claim WELL rewards (skips if below threshold) |
139
137
 
140
138
  ### Utilities
141
139
 
142
- | Method | Description |
143
- |--------|-------------|
140
+ | Method | Description |
141
+ | ------------------ | ---------------- |
144
142
  | `wrap_eth(amount)` | Wrap ETH to WETH |
145
143
 
146
144
  All methods return `(success: bool, payload: Any)` tuples. On failure the payload is an error string.