wayfinder-paths 0.1.23__py3-none-any.whl → 0.1.25__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 (124) hide show
  1. wayfinder_paths/__init__.py +2 -0
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +250 -0
  3. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  4. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -11
  5. wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
  6. wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
  7. wayfinder_paths/adapters/boros_adapter/client.py +476 -0
  8. wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
  9. wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
  10. wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
  11. wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
  12. wayfinder_paths/adapters/boros_adapter/types.py +70 -0
  13. wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
  14. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -1
  15. wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
  16. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +161 -26
  17. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
  18. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +77 -13
  19. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
  20. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +585 -61
  21. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
  22. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
  23. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
  24. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
  27. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
  28. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
  29. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
  30. wayfinder_paths/adapters/moonwell_adapter/adapter.py +592 -400
  31. wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
  32. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +126 -219
  33. wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
  34. wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
  35. wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
  36. wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
  37. wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
  38. wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
  39. wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
  40. wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
  41. wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
  42. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
  43. wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
  44. wayfinder_paths/adapters/token_adapter/examples.json +0 -4
  45. wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
  46. wayfinder_paths/conftest.py +24 -17
  47. wayfinder_paths/core/__init__.py +2 -0
  48. wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
  49. wayfinder_paths/core/adapters/models.py +17 -7
  50. wayfinder_paths/core/clients/BRAPClient.py +1 -1
  51. wayfinder_paths/core/clients/TokenClient.py +47 -1
  52. wayfinder_paths/core/clients/WayfinderClient.py +1 -2
  53. wayfinder_paths/core/clients/protocols.py +21 -22
  54. wayfinder_paths/core/clients/test_ledger_client.py +448 -0
  55. wayfinder_paths/core/config.py +12 -0
  56. wayfinder_paths/core/constants/__init__.py +15 -0
  57. wayfinder_paths/core/constants/base.py +6 -1
  58. wayfinder_paths/core/constants/contracts.py +39 -26
  59. wayfinder_paths/core/constants/erc20_abi.py +0 -1
  60. wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
  61. wayfinder_paths/core/constants/hyperliquid.py +16 -0
  62. wayfinder_paths/core/constants/moonwell_abi.py +0 -15
  63. wayfinder_paths/core/engine/manifest.py +66 -0
  64. wayfinder_paths/core/strategies/Strategy.py +0 -61
  65. wayfinder_paths/core/strategies/__init__.py +10 -1
  66. wayfinder_paths/core/strategies/opa_loop.py +167 -0
  67. wayfinder_paths/core/utils/test_transaction.py +289 -0
  68. wayfinder_paths/core/utils/transaction.py +44 -1
  69. wayfinder_paths/core/utils/web3.py +3 -0
  70. wayfinder_paths/mcp/__init__.py +5 -0
  71. wayfinder_paths/mcp/preview.py +185 -0
  72. wayfinder_paths/mcp/scripting.py +84 -0
  73. wayfinder_paths/mcp/server.py +52 -0
  74. wayfinder_paths/mcp/state/profile_store.py +195 -0
  75. wayfinder_paths/mcp/state/store.py +89 -0
  76. wayfinder_paths/mcp/test_scripting.py +267 -0
  77. wayfinder_paths/mcp/tools/__init__.py +0 -0
  78. wayfinder_paths/mcp/tools/balances.py +290 -0
  79. wayfinder_paths/mcp/tools/discovery.py +158 -0
  80. wayfinder_paths/mcp/tools/execute.py +770 -0
  81. wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
  82. wayfinder_paths/mcp/tools/quotes.py +288 -0
  83. wayfinder_paths/mcp/tools/run_script.py +286 -0
  84. wayfinder_paths/mcp/tools/strategies.py +188 -0
  85. wayfinder_paths/mcp/tools/tokens.py +46 -0
  86. wayfinder_paths/mcp/tools/wallets.py +354 -0
  87. wayfinder_paths/mcp/utils.py +129 -0
  88. wayfinder_paths/policies/hyperliquid.py +1 -1
  89. wayfinder_paths/policies/lifi.py +18 -0
  90. wayfinder_paths/policies/util.py +8 -2
  91. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +28 -119
  92. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
  93. wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
  94. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
  95. wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
  96. wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
  97. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
  98. wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
  99. wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
  100. wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
  101. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
  102. wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
  103. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
  104. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
  105. wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +202 -0
  106. wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
  107. wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
  108. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +3 -12
  109. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +7 -29
  110. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +63 -40
  111. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
  112. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -34
  113. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +11 -34
  114. wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
  115. wayfinder_paths/tests/test_test_coverage.py +1 -4
  116. wayfinder_paths-0.1.25.dist-info/METADATA +377 -0
  117. wayfinder_paths-0.1.25.dist-info/RECORD +185 -0
  118. wayfinder_paths/scripts/create_strategy.py +0 -139
  119. wayfinder_paths/scripts/make_wallets.py +0 -142
  120. wayfinder_paths-0.1.23.dist-info/METADATA +0 -354
  121. wayfinder_paths-0.1.23.dist-info/RECORD +0 -120
  122. /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
  123. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/LICENSE +0 -0
  124. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/WHEEL +0 -0
@@ -0,0 +1,14 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "adapters.moonwell_adapter.adapter.MoonwellAdapter"
3
+ capabilities:
4
+ - "lending.lend"
5
+ - "lending.unlend"
6
+ - "lending.borrow"
7
+ - "lending.repay"
8
+ - "collateral.set"
9
+ - "collateral.remove"
10
+ - "rewards.claim"
11
+ - "position.read"
12
+ - "market.apy"
13
+ - "market.collateral_factor"
14
+ dependencies: []
@@ -2,13 +2,22 @@ from contextlib import asynccontextmanager
2
2
  from unittest.mock import AsyncMock, MagicMock, patch
3
3
 
4
4
  import pytest
5
+ from web3 import Web3
5
6
 
6
7
  from wayfinder_paths.adapters.moonwell_adapter.adapter import (
7
- BASE_CHAIN_ID,
8
+ CHAIN_NAME,
8
9
  MANTISSA,
9
- MOONWELL_DEFAULTS,
10
10
  MoonwellAdapter,
11
11
  )
12
+ from wayfinder_paths.core.constants.contracts import (
13
+ BASE_USDC,
14
+ BASE_WETH,
15
+ MOONWELL_M_USDC,
16
+ MOONWELL_M_WETH,
17
+ MOONWELL_M_WSTETH,
18
+ MOONWELL_REWARD_DISTRIBUTOR,
19
+ MOONWELL_WELL_TOKEN,
20
+ )
12
21
 
13
22
 
14
23
  class TestMoonwellAdapter:
@@ -22,38 +31,73 @@ class TestMoonwellAdapter:
22
31
  def test_adapter_type(self, adapter):
23
32
  assert adapter.adapter_type == "MOONWELL"
24
33
 
25
- def test_default_addresses(self, adapter):
26
- assert (
27
- adapter.comptroller_address.lower()
28
- == MOONWELL_DEFAULTS["comptroller"].lower()
29
- )
30
- assert (
31
- adapter.reward_distributor_address.lower()
32
- == MOONWELL_DEFAULTS["reward_distributor"].lower()
33
- )
34
- assert adapter.m_usdc.lower() == MOONWELL_DEFAULTS["m_usdc"].lower()
35
- assert adapter.m_weth.lower() == MOONWELL_DEFAULTS["m_weth"].lower()
36
- assert adapter.m_wsteth.lower() == MOONWELL_DEFAULTS["m_wsteth"].lower()
37
- assert adapter.well_token.lower() == MOONWELL_DEFAULTS["well_token"].lower()
34
+ def test_chain_name(self):
35
+ assert CHAIN_NAME == "base"
38
36
 
39
- def test_chain_id(self, adapter):
40
- assert adapter.chain_id == BASE_CHAIN_ID
37
+ @pytest.mark.asyncio
38
+ async def test_get_full_user_state_basic(self, adapter):
39
+ w3 = Web3()
41
40
 
42
- def test_chain_name(self, adapter):
43
- assert adapter.chain_name == "base"
41
+ @asynccontextmanager
42
+ async def mock_web3_ctx(_chain_id):
43
+ yield w3
44
+
45
+ m1 = MOONWELL_M_USDC
46
+ m2 = MOONWELL_M_WETH
47
+
48
+ rewards = w3.codec.encode(
49
+ ["(address,(address,uint256,uint256,uint256)[])[]"],
50
+ [[(m1, [(MOONWELL_WELL_TOKEN, 123, 0, 0)])]],
51
+ )
52
+
53
+ stage1 = [
54
+ w3.codec.encode(["address[]"], [[m1, m2]]),
55
+ w3.codec.encode(["address[]"], [[m1]]),
56
+ w3.codec.encode(["uint256", "uint256", "uint256"], [0, 123, 0]),
57
+ rewards,
58
+ ]
59
+
60
+ stage2 = [
61
+ # m1
62
+ w3.codec.encode(["uint256"], [100]),
63
+ w3.codec.encode(["uint256"], [2 * MANTISSA]),
64
+ w3.codec.encode(["uint256"], [50]),
65
+ w3.codec.encode(["address"], [BASE_USDC]),
66
+ w3.codec.encode(["uint8"], [8]),
67
+ w3.codec.encode(["bool", "uint256"], [True, int(0.5 * MANTISSA)]),
68
+ # m2 (all zeros, should be filtered out)
69
+ w3.codec.encode(["uint256"], [0]),
70
+ w3.codec.encode(["uint256"], [0]),
71
+ w3.codec.encode(["uint256"], [0]),
72
+ w3.codec.encode(["address"], [BASE_WETH]),
73
+ w3.codec.encode(["uint8"], [8]),
74
+ w3.codec.encode(["bool", "uint256"], [True, int(0.5 * MANTISSA)]),
75
+ ]
44
76
 
45
- @pytest.mark.asyncio
46
- async def test_health_check(self, adapter):
47
- health = await adapter.health_check()
48
- assert isinstance(health, dict)
49
- assert health.get("status") in {"healthy", "unhealthy", "error"}
50
- assert health.get("adapter") == "MOONWELL"
77
+ with (
78
+ patch(
79
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
80
+ mock_web3_ctx,
81
+ ),
82
+ patch.object(
83
+ adapter, "_multicall_chunked", new_callable=AsyncMock
84
+ ) as mock_multicall,
85
+ ):
86
+ mock_multicall.side_effect = [stage1, stage2]
87
+ ok, state = await adapter.get_full_user_state(
88
+ include_rewards=True,
89
+ include_usd=False,
90
+ include_apy=False,
91
+ )
51
92
 
52
- @pytest.mark.asyncio
53
- async def test_connect(self, adapter):
54
- ok = await adapter.connect()
55
- assert isinstance(ok, bool)
56
93
  assert ok is True
94
+ assert state["protocol"] == "moonwell"
95
+ assert state["chainId"] == adapter.chain_id
96
+ assert state["accountLiquidity"]["liquidity"] == 123
97
+ assert len(state["positions"]) == 1
98
+ assert state["positions"][0]["enteredAsCollateral"] is True
99
+ assert state["positions"][0]["suppliedUnderlying"] == 200
100
+ assert state["rewards"][f"base_{MOONWELL_WELL_TOKEN.lower()}"] == 123
57
101
 
58
102
  @pytest.mark.asyncio
59
103
  async def test_lend(self, adapter):
@@ -63,8 +107,9 @@ class TestMoonwellAdapter:
63
107
  "wayfinder_paths.adapters.moonwell_adapter.adapter.ensure_allowance",
64
108
  new_callable=AsyncMock,
65
109
  ) as mock_allowance,
66
- patch.object(
67
- adapter, "_encode_call", new_callable=AsyncMock
110
+ patch(
111
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
112
+ new_callable=AsyncMock,
68
113
  ) as mock_encode,
69
114
  patch(
70
115
  "wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
@@ -72,15 +117,12 @@ class TestMoonwellAdapter:
72
117
  ) as mock_send,
73
118
  ):
74
119
  mock_allowance.return_value = (True, {})
75
- mock_encode.return_value = {
76
- "data": "0x1234",
77
- "to": MOONWELL_DEFAULTS["m_usdc"],
78
- }
120
+ mock_encode.return_value = {"data": "0x1234", "to": MOONWELL_M_USDC}
79
121
  mock_send.return_value = mock_tx_hash
80
122
 
81
123
  success, result = await adapter.lend(
82
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
83
- underlying_token=MOONWELL_DEFAULTS["usdc"],
124
+ mtoken=MOONWELL_M_USDC,
125
+ underlying_token=BASE_USDC,
84
126
  amount=10**6,
85
127
  )
86
128
 
@@ -90,8 +132,8 @@ class TestMoonwellAdapter:
90
132
  @pytest.mark.asyncio
91
133
  async def test_lend_invalid_amount(self, adapter):
92
134
  success, result = await adapter.lend(
93
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
94
- underlying_token=MOONWELL_DEFAULTS["usdc"],
135
+ mtoken=MOONWELL_M_USDC,
136
+ underlying_token=BASE_USDC,
95
137
  amount=0,
96
138
  )
97
139
 
@@ -100,35 +142,22 @@ class TestMoonwellAdapter:
100
142
 
101
143
  @pytest.mark.asyncio
102
144
  async def test_unlend(self, adapter):
103
- # Mock contract encoding
104
- mock_contract = MagicMock()
105
- mock_contract.functions.redeem = MagicMock(
106
- return_value=MagicMock(
107
- build_transaction=AsyncMock(return_value={"data": "0x1234"})
108
- )
109
- )
110
- mock_web3 = MagicMock()
111
- mock_web3.eth.contract = MagicMock(return_value=mock_contract)
112
-
113
- @asynccontextmanager
114
- async def mock_web3_ctx(_chain_id):
115
- yield mock_web3
116
-
117
145
  mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
118
146
 
119
147
  with (
120
148
  patch(
121
- "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
122
- mock_web3_ctx,
123
- ),
149
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
150
+ new_callable=AsyncMock,
151
+ ) as mock_encode,
124
152
  patch(
125
153
  "wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
126
154
  new_callable=AsyncMock,
127
155
  ) as mock_send,
128
156
  ):
157
+ mock_encode.return_value = {"data": "0x1234", "to": MOONWELL_M_USDC}
129
158
  mock_send.return_value = mock_tx_hash
130
159
  success, result = await adapter.unlend(
131
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
160
+ mtoken=MOONWELL_M_USDC,
132
161
  amount=10**8,
133
162
  )
134
163
 
@@ -138,7 +167,7 @@ class TestMoonwellAdapter:
138
167
  @pytest.mark.asyncio
139
168
  async def test_unlend_invalid_amount(self, adapter):
140
169
  success, result = await adapter.unlend(
141
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
170
+ mtoken=MOONWELL_M_USDC,
142
171
  amount=-1,
143
172
  )
144
173
 
@@ -147,50 +176,22 @@ class TestMoonwellAdapter:
147
176
 
148
177
  @pytest.mark.asyncio
149
178
  async def test_borrow(self, adapter):
150
- # Track calls to return different values (0 before, 10**6 after)
151
- borrow_balance_calls = [0]
152
-
153
- async def mock_borrow_balance_call(**kwargs):
154
- result = borrow_balance_calls[0]
155
- # Next call returns increased balance
156
- borrow_balance_calls[0] = 10**6
157
- return result
158
-
159
- # Mock mtoken contract for pre-check and verification
160
- mock_mtoken = MagicMock()
161
- mock_mtoken.functions.borrowBalanceStored = MagicMock(
162
- return_value=MagicMock(call=mock_borrow_balance_call)
163
- )
164
- mock_mtoken.functions.borrow = MagicMock(
165
- return_value=MagicMock(
166
- call=AsyncMock(return_value=0),
167
- _encode_transaction_data=MagicMock(return_value="0x1234"),
168
- build_transaction=AsyncMock(return_value={"data": "0x1234"}),
169
- )
170
- )
171
-
172
- mock_web3 = MagicMock()
173
- mock_web3.eth.contract = MagicMock(return_value=mock_mtoken)
174
-
175
- @asynccontextmanager
176
- async def mock_web3_ctx(_chain_id):
177
- yield mock_web3
178
-
179
179
  mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
180
180
 
181
181
  with (
182
182
  patch(
183
- "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
184
- mock_web3_ctx,
185
- ),
183
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
184
+ new_callable=AsyncMock,
185
+ ) as mock_encode,
186
186
  patch(
187
187
  "wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
188
188
  new_callable=AsyncMock,
189
189
  ) as mock_send,
190
190
  ):
191
+ mock_encode.return_value = {"data": "0x1234", "to": MOONWELL_M_USDC}
191
192
  mock_send.return_value = mock_tx_hash
192
193
  success, result = await adapter.borrow(
193
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
194
+ mtoken=MOONWELL_M_USDC,
194
195
  amount=10**6,
195
196
  )
196
197
 
@@ -205,8 +206,9 @@ class TestMoonwellAdapter:
205
206
  "wayfinder_paths.adapters.moonwell_adapter.adapter.ensure_allowance",
206
207
  new_callable=AsyncMock,
207
208
  ) as mock_allowance,
208
- patch.object(
209
- adapter, "_encode_call", new_callable=AsyncMock
209
+ patch(
210
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
211
+ new_callable=AsyncMock,
210
212
  ) as mock_encode,
211
213
  patch(
212
214
  "wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
@@ -214,15 +216,12 @@ class TestMoonwellAdapter:
214
216
  ) as mock_send,
215
217
  ):
216
218
  mock_allowance.return_value = (True, {})
217
- mock_encode.return_value = {
218
- "data": "0x1234",
219
- "to": MOONWELL_DEFAULTS["m_usdc"],
220
- }
219
+ mock_encode.return_value = {"data": "0x1234", "to": MOONWELL_M_USDC}
221
220
  mock_send.return_value = mock_tx_hash
222
221
 
223
222
  success, result = await adapter.repay(
224
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
225
- underlying_token=MOONWELL_DEFAULTS["usdc"],
223
+ mtoken=MOONWELL_M_USDC,
224
+ underlying_token=BASE_USDC,
226
225
  amount=10**6,
227
226
  )
228
227
 
@@ -231,17 +230,10 @@ class TestMoonwellAdapter:
231
230
 
232
231
  @pytest.mark.asyncio
233
232
  async def test_set_collateral(self, adapter):
234
- # Mock comptroller contract for verification
235
233
  mock_comptroller = MagicMock()
236
234
  mock_comptroller.functions.checkMembership = MagicMock(
237
235
  return_value=MagicMock(call=AsyncMock(return_value=True))
238
236
  )
239
- mock_comptroller.functions.enterMarkets = MagicMock(
240
- return_value=MagicMock(
241
- _encode_transaction_data=MagicMock(return_value="0x1234"),
242
- build_transaction=AsyncMock(return_value={"data": "0x1234"}),
243
- )
244
- )
245
237
 
246
238
  mock_web3 = MagicMock()
247
239
  mock_web3.eth.contract = MagicMock(return_value=mock_comptroller)
@@ -254,17 +246,22 @@ class TestMoonwellAdapter:
254
246
 
255
247
  with (
256
248
  patch(
257
- "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
258
- mock_web3_ctx,
259
- ),
249
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
250
+ new_callable=AsyncMock,
251
+ ) as mock_encode,
260
252
  patch(
261
253
  "wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
262
254
  new_callable=AsyncMock,
263
255
  ) as mock_send,
256
+ patch(
257
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
258
+ mock_web3_ctx,
259
+ ),
264
260
  ):
261
+ mock_encode.return_value = {"data": "0x1234", "to": MOONWELL_M_WSTETH}
265
262
  mock_send.return_value = mock_tx_hash
266
263
  success, result = await adapter.set_collateral(
267
- mtoken=MOONWELL_DEFAULTS["m_wsteth"],
264
+ mtoken=MOONWELL_M_WSTETH,
268
265
  )
269
266
 
270
267
  assert success is True
@@ -287,7 +284,7 @@ class TestMoonwellAdapter:
287
284
  )
288
285
 
289
286
  def mock_contract(address, abi):
290
- if address.lower() == adapter.reward_distributor_address.lower():
287
+ if address.lower() == MOONWELL_REWARD_DISTRIBUTOR.lower():
291
288
  return mock_reward_contract
292
289
  return mock_comptroller
293
290
 
@@ -325,7 +322,7 @@ class TestMoonwellAdapter:
325
322
  )
326
323
 
327
324
  def mock_contract(address, abi):
328
- if address.lower() == adapter.reward_distributor_address.lower():
325
+ if address.lower() == MOONWELL_REWARD_DISTRIBUTOR.lower():
329
326
  return mock_reward
330
327
  return mock_mtoken
331
328
 
@@ -340,7 +337,7 @@ class TestMoonwellAdapter:
340
337
  "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
341
338
  mock_web3_ctx,
342
339
  ):
343
- success, result = await adapter.get_pos(mtoken=MOONWELL_DEFAULTS["m_usdc"])
340
+ success, result = await adapter.get_pos(mtoken=MOONWELL_M_USDC)
344
341
 
345
342
  assert success
346
343
  assert "mtoken_balance" in result
@@ -353,7 +350,7 @@ class TestMoonwellAdapter:
353
350
  @pytest.mark.asyncio
354
351
  async def test_get_collateral_factor_success(self, adapter):
355
352
  # Clear cache to ensure fresh test
356
- adapter._cf_cache.clear()
353
+ await adapter._cache.clear()
357
354
 
358
355
  # Mock contract calls - returns (isListed, collateralFactorMantissa)
359
356
  mock_contract = MagicMock()
@@ -374,7 +371,7 @@ class TestMoonwellAdapter:
374
371
  mock_web3_ctx,
375
372
  ):
376
373
  success, result = await adapter.get_collateral_factor(
377
- mtoken=MOONWELL_DEFAULTS["m_wsteth"]
374
+ mtoken=MOONWELL_M_WSTETH
378
375
  )
379
376
 
380
377
  assert success
@@ -407,7 +404,7 @@ class TestMoonwellAdapter:
407
404
  @pytest.mark.asyncio
408
405
  async def test_get_collateral_factor_caching(self, adapter):
409
406
  # Clear cache to ensure fresh test
410
- adapter._cf_cache.clear()
407
+ await adapter._cache.clear()
411
408
 
412
409
  call_count = 0
413
410
 
@@ -427,7 +424,7 @@ class TestMoonwellAdapter:
427
424
  async def mock_web3_ctx(_chain_id):
428
425
  yield mock_web3
429
426
 
430
- mtoken = MOONWELL_DEFAULTS["m_wsteth"]
427
+ mtoken = MOONWELL_M_WSTETH
431
428
 
432
429
  with patch(
433
430
  "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
@@ -452,67 +449,11 @@ class TestMoonwellAdapter:
452
449
  assert call_count == 1
453
450
 
454
451
  success4, result4 = await adapter.get_collateral_factor(
455
- mtoken=MOONWELL_DEFAULTS["m_usdc"]
452
+ mtoken=MOONWELL_M_USDC
456
453
  )
457
454
  assert success4 is True
458
455
  assert call_count == 2
459
456
 
460
- @pytest.mark.asyncio
461
- async def test_get_collateral_factor_cache_expiry(self, adapter):
462
- import time
463
-
464
- from wayfinder_paths.adapters.moonwell_adapter import adapter as adapter_module
465
-
466
- # Clear cache to ensure fresh test
467
- adapter._cf_cache.clear()
468
-
469
- original_ttl = adapter_module.CF_CACHE_TTL
470
-
471
- try:
472
- adapter_module.CF_CACHE_TTL = 0.1
473
-
474
- call_count = 0
475
-
476
- async def mock_markets_call(**kwargs):
477
- nonlocal call_count
478
- call_count += 1
479
- return (True, int(0.75 * MANTISSA))
480
-
481
- mock_contract = MagicMock()
482
- mock_contract.functions.markets = MagicMock(
483
- return_value=MagicMock(call=mock_markets_call)
484
- )
485
- mock_web3 = MagicMock()
486
- mock_web3.eth.contract = MagicMock(return_value=mock_contract)
487
-
488
- @asynccontextmanager
489
- async def mock_web3_ctx(_chain_id):
490
- yield mock_web3
491
-
492
- mtoken = MOONWELL_DEFAULTS["m_wsteth"]
493
-
494
- with patch(
495
- "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
496
- mock_web3_ctx,
497
- ):
498
- # First call
499
- await adapter.get_collateral_factor(mtoken=mtoken)
500
- assert call_count == 1
501
-
502
- # Immediate second call should use cache
503
- await adapter.get_collateral_factor(mtoken=mtoken)
504
- assert call_count == 1
505
-
506
- # Wait for cache to expire
507
- time.sleep(0.15)
508
-
509
- await adapter.get_collateral_factor(mtoken=mtoken)
510
- assert call_count == 2
511
-
512
- finally:
513
- # Restore original TTL
514
- adapter_module.CF_CACHE_TTL = original_ttl
515
-
516
457
  @pytest.mark.asyncio
517
458
  async def test_get_apy_supply(self, adapter):
518
459
  rate_per_second = int(1.5e9)
@@ -533,7 +474,7 @@ class TestMoonwellAdapter:
533
474
  )
534
475
 
535
476
  def mock_contract(address, abi):
536
- if address.lower() == adapter.reward_distributor_address.lower():
477
+ if address.lower() == MOONWELL_REWARD_DISTRIBUTOR.lower():
537
478
  return mock_reward
538
479
  return mock_mtoken
539
480
 
@@ -549,7 +490,7 @@ class TestMoonwellAdapter:
549
490
  mock_web3_ctx,
550
491
  ):
551
492
  success, result = await adapter.get_apy(
552
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
493
+ mtoken=MOONWELL_M_USDC,
553
494
  apy_type="supply",
554
495
  include_rewards=False,
555
496
  )
@@ -578,7 +519,7 @@ class TestMoonwellAdapter:
578
519
  )
579
520
 
580
521
  def mock_contract(address, abi):
581
- if address.lower() == adapter.reward_distributor_address.lower():
522
+ if address.lower() == MOONWELL_REWARD_DISTRIBUTOR.lower():
582
523
  return mock_reward
583
524
  return mock_mtoken
584
525
 
@@ -594,7 +535,7 @@ class TestMoonwellAdapter:
594
535
  mock_web3_ctx,
595
536
  ):
596
537
  success, result = await adapter.get_apy(
597
- mtoken=MOONWELL_DEFAULTS["m_usdc"],
538
+ mtoken=MOONWELL_M_USDC,
598
539
  apy_type="borrow",
599
540
  include_rewards=False,
600
541
  )
@@ -649,31 +590,19 @@ class TestMoonwellAdapter:
649
590
 
650
591
  @pytest.mark.asyncio
651
592
  async def test_wrap_eth(self, adapter):
652
- mock_contract = MagicMock()
653
- mock_contract.functions.deposit = MagicMock(
654
- return_value=MagicMock(
655
- build_transaction=AsyncMock(return_value={"data": "0x1234"})
656
- )
657
- )
658
- mock_web3 = MagicMock()
659
- mock_web3.eth.contract = MagicMock(return_value=mock_contract)
660
-
661
- @asynccontextmanager
662
- async def mock_web3_ctx(_chain_id):
663
- yield mock_web3
664
-
665
593
  mock_tx_hash = {"tx_hash": "0xabc123", "status": "success"}
666
594
 
667
595
  with (
668
596
  patch(
669
- "wayfinder_paths.adapters.moonwell_adapter.adapter.web3_from_chain_id",
670
- mock_web3_ctx,
671
- ),
597
+ "wayfinder_paths.adapters.moonwell_adapter.adapter.encode_call",
598
+ new_callable=AsyncMock,
599
+ ) as mock_encode,
672
600
  patch(
673
601
  "wayfinder_paths.adapters.moonwell_adapter.adapter.send_transaction",
674
602
  new_callable=AsyncMock,
675
603
  ) as mock_send,
676
604
  ):
605
+ mock_encode.return_value = {"data": "0x1234", "to": BASE_WETH}
677
606
  mock_send.return_value = mock_tx_hash
678
607
  success, result = await adapter.wrap_eth(amount=10**18)
679
608
 
@@ -681,29 +610,9 @@ class TestMoonwellAdapter:
681
610
  assert result == mock_tx_hash
682
611
 
683
612
  def test_strategy_address_missing(self):
684
- with pytest.raises(ValueError, match="strategy_wallet"):
613
+ with pytest.raises(KeyError):
685
614
  MoonwellAdapter(config={})
686
615
 
687
- def test_config_override(self):
688
- custom_comptroller = "0x1111111111111111111111111111111111111111"
689
- custom_well = "0x2222222222222222222222222222222222222222"
690
- config = {
691
- "strategy_wallet": {
692
- "address": "0x1234567890123456789012345678901234567890"
693
- },
694
- "moonwell_adapter": {
695
- "comptroller": custom_comptroller,
696
- "well_token": custom_well,
697
- "chain_id": 1,
698
- },
699
- }
700
-
701
- adapter = MoonwellAdapter(config=config)
702
-
703
- assert adapter.comptroller_address.lower() == custom_comptroller.lower()
704
- assert adapter.well_token.lower() == custom_well.lower()
705
- assert adapter.chain_id == 1
706
-
707
616
  @pytest.mark.asyncio
708
617
  async def test_max_withdrawable_mtoken_zero_balance(self, adapter):
709
618
  # Mock contracts
@@ -721,9 +630,7 @@ class TestMoonwellAdapter:
721
630
  return_value=MagicMock(call=AsyncMock(return_value=8))
722
631
  )
723
632
  mock_mtoken.functions.underlying = MagicMock(
724
- return_value=MagicMock(
725
- call=AsyncMock(return_value=MOONWELL_DEFAULTS["usdc"])
726
- )
633
+ return_value=MagicMock(call=AsyncMock(return_value=BASE_USDC))
727
634
  )
728
635
 
729
636
  mock_web3 = MagicMock()
@@ -738,7 +645,7 @@ class TestMoonwellAdapter:
738
645
  mock_web3_ctx,
739
646
  ):
740
647
  success, result = await adapter.max_withdrawable_mtoken(
741
- mtoken=MOONWELL_DEFAULTS["m_usdc"]
648
+ mtoken=MOONWELL_M_USDC
742
649
  )
743
650
 
744
651
  assert success
@@ -0,0 +1,7 @@
1
+ """
2
+ Multicall Adapter
3
+ """
4
+
5
+ from wayfinder_paths.adapters.multicall_adapter.adapter import MulticallAdapter
6
+
7
+ __all__ = ["MulticallAdapter"]