wayfinder-paths 0.1.14__py3-none-any.whl → 0.1.16__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.
- wayfinder_paths/adapters/balance_adapter/README.md +19 -20
- wayfinder_paths/adapters/balance_adapter/adapter.py +91 -22
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +5 -11
- wayfinder_paths/adapters/brap_adapter/README.md +22 -19
- wayfinder_paths/adapters/brap_adapter/adapter.py +95 -45
- wayfinder_paths/adapters/brap_adapter/test_adapter.py +8 -24
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +40 -42
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +8 -15
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +12 -12
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +6 -6
- wayfinder_paths/adapters/moonwell_adapter/README.md +29 -31
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +326 -364
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +285 -189
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +2 -2
- wayfinder_paths/adapters/token_adapter/test_adapter.py +4 -4
- wayfinder_paths/core/config.py +8 -47
- wayfinder_paths/core/constants/base.py +0 -1
- wayfinder_paths/core/constants/erc20_abi.py +13 -24
- wayfinder_paths/core/engine/StrategyJob.py +3 -1
- wayfinder_paths/core/services/test_local_evm_txn.py +145 -0
- wayfinder_paths/core/strategies/Strategy.py +22 -4
- wayfinder_paths/core/utils/erc20_service.py +100 -0
- wayfinder_paths/core/utils/evm_helpers.py +1 -8
- wayfinder_paths/core/utils/transaction.py +191 -0
- wayfinder_paths/core/utils/web3.py +66 -0
- wayfinder_paths/policies/erc20.py +1 -1
- wayfinder_paths/run_strategy.py +42 -6
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +263 -220
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +132 -155
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +0 -1
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +123 -80
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -12
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +6 -6
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2270 -1328
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +282 -121
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +0 -1
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +107 -85
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -8
- wayfinder_paths/templates/adapter/README.md +1 -1
- wayfinder_paths/templates/strategy/README.md +1 -5
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/METADATA +3 -41
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/RECORD +45 -54
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/WHEEL +1 -1
- wayfinder_paths/abis/generic/erc20.json +0 -383
- wayfinder_paths/core/clients/sdk_example.py +0 -125
- wayfinder_paths/core/engine/__init__.py +0 -5
- wayfinder_paths/core/services/__init__.py +0 -0
- wayfinder_paths/core/services/base.py +0 -130
- wayfinder_paths/core/services/local_evm_txn.py +0 -334
- wayfinder_paths/core/services/local_token_txn.py +0 -242
- wayfinder_paths/core/services/web3_service.py +0 -43
- wayfinder_paths/core/wallets/README.md +0 -88
- wayfinder_paths/core/wallets/WalletManager.py +0 -56
- wayfinder_paths/core/wallets/__init__.py +0 -7
- wayfinder_paths/scripts/run_strategy.py +0 -152
- wayfinder_paths/strategies/config.py +0 -85
- {wayfinder_paths-0.1.14.dist-info → wayfinder_paths-0.1.16.dist-info}/LICENSE +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
from types import SimpleNamespace
|
|
2
1
|
from unittest.mock import AsyncMock
|
|
3
2
|
|
|
4
3
|
import pytest
|
|
@@ -16,24 +15,9 @@ class TestBRAPAdapter:
|
|
|
16
15
|
return mock_client
|
|
17
16
|
|
|
18
17
|
@pytest.fixture
|
|
19
|
-
def
|
|
20
|
-
"""Minimal Web3Service stub for adapter construction."""
|
|
21
|
-
wallet_provider = SimpleNamespace(
|
|
22
|
-
broadcast_transaction=AsyncMock(return_value=(True, {}))
|
|
23
|
-
)
|
|
24
|
-
token_txn = SimpleNamespace(
|
|
25
|
-
build_send=AsyncMock(return_value=(True, {})),
|
|
26
|
-
build_erc20_approve=AsyncMock(return_value=(True, {})),
|
|
27
|
-
read_erc20_allowance=AsyncMock(return_value={"allowance": 0}),
|
|
28
|
-
)
|
|
29
|
-
return SimpleNamespace(
|
|
30
|
-
evm_transactions=wallet_provider, token_transactions=token_txn
|
|
31
|
-
)
|
|
32
|
-
|
|
33
|
-
@pytest.fixture
|
|
34
|
-
def adapter(self, mock_brap_client, mock_web3_service):
|
|
18
|
+
def adapter(self, mock_brap_client):
|
|
35
19
|
"""Create a BRAPAdapter instance with mocked client for testing"""
|
|
36
|
-
adapter = BRAPAdapter(
|
|
20
|
+
adapter = BRAPAdapter()
|
|
37
21
|
adapter.brap_client = mock_brap_client
|
|
38
22
|
return adapter
|
|
39
23
|
|
|
@@ -83,7 +67,7 @@ class TestBRAPAdapter:
|
|
|
83
67
|
slippage=0.01,
|
|
84
68
|
)
|
|
85
69
|
|
|
86
|
-
assert success
|
|
70
|
+
assert success
|
|
87
71
|
assert data == mock_response
|
|
88
72
|
mock_brap_client.get_quote.assert_called_once_with(
|
|
89
73
|
from_token="0x" + "a" * 40,
|
|
@@ -125,7 +109,7 @@ class TestBRAPAdapter:
|
|
|
125
109
|
amount="1000000000000000000",
|
|
126
110
|
)
|
|
127
111
|
|
|
128
|
-
assert success
|
|
112
|
+
assert success
|
|
129
113
|
assert data["input_amount"] == 1000000000000000000
|
|
130
114
|
assert data["output_amount"] == 995000000000000000
|
|
131
115
|
|
|
@@ -183,7 +167,7 @@ class TestBRAPAdapter:
|
|
|
183
167
|
slippage=0.01,
|
|
184
168
|
)
|
|
185
169
|
|
|
186
|
-
assert success
|
|
170
|
+
assert success
|
|
187
171
|
assert data["input_amount"] == 1000000000000000000
|
|
188
172
|
assert data["output_amount"] == 995000000000000000
|
|
189
173
|
assert data["gas_fee"] == 5000000000000000
|
|
@@ -242,7 +226,7 @@ class TestBRAPAdapter:
|
|
|
242
226
|
amount="1000000000000000000",
|
|
243
227
|
)
|
|
244
228
|
|
|
245
|
-
assert success
|
|
229
|
+
assert success
|
|
246
230
|
assert data["total_routes"] == 2
|
|
247
231
|
assert len(data["all_routes"]) == 2
|
|
248
232
|
assert data["best_route"]["output_amount"] == 995000000000000000
|
|
@@ -254,7 +238,7 @@ class TestBRAPAdapter:
|
|
|
254
238
|
from_chain_id=8453, to_chain_id=1, operation_type="swap"
|
|
255
239
|
)
|
|
256
240
|
|
|
257
|
-
assert success
|
|
241
|
+
assert success
|
|
258
242
|
assert data["from_chain"] == "base"
|
|
259
243
|
assert data["to_chain"] == "ethereum"
|
|
260
244
|
assert data["from_gas_estimate"] == 100000
|
|
@@ -288,7 +272,7 @@ class TestBRAPAdapter:
|
|
|
288
272
|
amount="1000000000000000000",
|
|
289
273
|
)
|
|
290
274
|
|
|
291
|
-
assert success
|
|
275
|
+
assert success
|
|
292
276
|
assert data["valid"] is True
|
|
293
277
|
assert data["quote_available"] is True
|
|
294
278
|
assert data["estimated_output"] == "995000000000000000"
|
|
@@ -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.
|
|
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
|
-
|
|
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(
|
|
@@ -165,7 +170,7 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
165
170
|
from_address=strategy,
|
|
166
171
|
chain_id=chain_id,
|
|
167
172
|
)
|
|
168
|
-
return await self.
|
|
173
|
+
return await self._send_tx(tx)
|
|
169
174
|
|
|
170
175
|
async def unlend(
|
|
171
176
|
self,
|
|
@@ -200,12 +205,19 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
200
205
|
from_address=strategy,
|
|
201
206
|
chain_id=chain_id,
|
|
202
207
|
)
|
|
203
|
-
return await self.
|
|
208
|
+
return await self._send_tx(tx)
|
|
204
209
|
|
|
205
210
|
# ------------------------------------------------------------------ #
|
|
206
211
|
# Helpers #
|
|
207
212
|
# ------------------------------------------------------------------ #
|
|
208
213
|
|
|
214
|
+
async def _send_tx(self, tx: dict[str, Any]) -> tuple[bool, Any]:
|
|
215
|
+
"""Send transaction with simulation check."""
|
|
216
|
+
if self.simulation:
|
|
217
|
+
return True, {"simulation": tx}
|
|
218
|
+
txn_hash = await send_transaction(tx, self.strategy_wallet_signing_callback)
|
|
219
|
+
return True, txn_hash
|
|
220
|
+
|
|
209
221
|
async def _ensure_allowance(
|
|
210
222
|
self,
|
|
211
223
|
*,
|
|
@@ -215,32 +227,17 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
215
227
|
amount: int,
|
|
216
228
|
chain_id: int,
|
|
217
229
|
) -> tuple[bool, Any]:
|
|
218
|
-
|
|
219
|
-
allowance = await self.token_txn_service.read_erc20_allowance(
|
|
220
|
-
chain, token_address, owner, spender
|
|
221
|
-
)
|
|
230
|
+
allowance = await get_token_allowance(token_address, chain_id, owner, spender)
|
|
222
231
|
if allowance.get("allowance", 0) >= amount:
|
|
223
232
|
return True, {}
|
|
224
|
-
|
|
233
|
+
approve_tx = await build_approve_transaction(
|
|
234
|
+
from_address=owner,
|
|
225
235
|
chain_id=chain_id,
|
|
226
236
|
token_address=token_address,
|
|
227
|
-
from_address=owner,
|
|
228
237
|
spender=spender,
|
|
229
238
|
amount=amount,
|
|
230
239
|
)
|
|
231
|
-
|
|
232
|
-
return False, approve_tx
|
|
233
|
-
return await self._broadcast_transaction(approve_tx)
|
|
234
|
-
|
|
235
|
-
async def _execute(self, tx: dict[str, Any]) -> tuple[bool, Any]:
|
|
236
|
-
return await self.web3.broadcast_transaction(
|
|
237
|
-
tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
|
|
238
|
-
)
|
|
239
|
-
|
|
240
|
-
async def _broadcast_transaction(self, tx: dict[str, Any]) -> tuple[bool, Any]:
|
|
241
|
-
return await self.web3.evm_transactions.broadcast_transaction(
|
|
242
|
-
tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
|
|
243
|
-
)
|
|
240
|
+
return await self._send_tx(approve_tx)
|
|
244
241
|
|
|
245
242
|
async def _encode_call(
|
|
246
243
|
self,
|
|
@@ -254,23 +251,24 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
254
251
|
value: int = 0,
|
|
255
252
|
) -> dict[str, Any]:
|
|
256
253
|
"""Encode calldata without touching network."""
|
|
257
|
-
web3 = self.web3.get_web3(chain_id)
|
|
258
|
-
contract = web3.eth.contract(address=target, abi=abi)
|
|
259
|
-
try:
|
|
260
|
-
data = await getattr(contract.functions, fn_name)(*args).build_transaction(
|
|
261
|
-
{"from": from_address}
|
|
262
|
-
)["data"]
|
|
263
|
-
except ValueError as exc:
|
|
264
|
-
raise ValueError(f"Failed to encode {fn_name}: {exc}") from exc
|
|
265
254
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
255
|
+
async with web3_from_chain_id(chain_id) as web3:
|
|
256
|
+
contract = web3.eth.contract(address=target, abi=abi)
|
|
257
|
+
try:
|
|
258
|
+
data = await getattr(contract.functions, fn_name)(
|
|
259
|
+
*args
|
|
260
|
+
).build_transaction({"from": from_address})["data"]
|
|
261
|
+
except ValueError as exc:
|
|
262
|
+
raise ValueError(f"Failed to encode {fn_name}: {exc}") from exc
|
|
263
|
+
|
|
264
|
+
tx: dict[str, Any] = {
|
|
265
|
+
"chainId": int(chain_id),
|
|
266
|
+
"from": to_checksum_address(from_address),
|
|
267
|
+
"to": to_checksum_address(target),
|
|
268
|
+
"data": data,
|
|
269
|
+
"value": int(value),
|
|
270
|
+
}
|
|
271
|
+
return tx
|
|
274
272
|
|
|
275
273
|
def _strategy_address(self) -> str:
|
|
276
274
|
addr = None
|
|
@@ -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
|
|
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
|
|
@@ -64,7 +57,7 @@ class TestHyperlendAdapter:
|
|
|
64
57
|
min_buffer_tokens=100.0,
|
|
65
58
|
)
|
|
66
59
|
|
|
67
|
-
assert success
|
|
60
|
+
assert success
|
|
68
61
|
assert data == mock_response
|
|
69
62
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
70
63
|
required_underlying_tokens=1000.0,
|
|
@@ -95,7 +88,7 @@ class TestHyperlendAdapter:
|
|
|
95
88
|
|
|
96
89
|
success, data = await adapter.get_stable_markets()
|
|
97
90
|
|
|
98
|
-
assert success
|
|
91
|
+
assert success
|
|
99
92
|
assert data == mock_response
|
|
100
93
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
101
94
|
required_underlying_tokens=None,
|
|
@@ -115,7 +108,7 @@ class TestHyperlendAdapter:
|
|
|
115
108
|
required_underlying_tokens=500.0
|
|
116
109
|
)
|
|
117
110
|
|
|
118
|
-
assert success
|
|
111
|
+
assert success
|
|
119
112
|
assert data == mock_response
|
|
120
113
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
121
114
|
required_underlying_tokens=500.0,
|
|
@@ -157,7 +150,7 @@ class TestHyperlendAdapter:
|
|
|
157
150
|
|
|
158
151
|
success, data = await adapter.get_stable_markets()
|
|
159
152
|
|
|
160
|
-
assert success
|
|
153
|
+
assert success
|
|
161
154
|
assert data == mock_response
|
|
162
155
|
assert len(data.get("markets", {})) == 0
|
|
163
156
|
|
|
@@ -203,7 +196,7 @@ class TestHyperlendAdapter:
|
|
|
203
196
|
|
|
204
197
|
success, data = await adapter.get_stable_markets()
|
|
205
198
|
|
|
206
|
-
assert success
|
|
199
|
+
assert success
|
|
207
200
|
assert data == mock_response
|
|
208
201
|
mock_hyperlend_client.get_stable_markets.assert_called_once_with(
|
|
209
202
|
required_underlying_tokens=None,
|
|
@@ -270,7 +263,7 @@ class TestHyperlendAdapter:
|
|
|
270
263
|
user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
271
264
|
)
|
|
272
265
|
|
|
273
|
-
assert success
|
|
266
|
+
assert success
|
|
274
267
|
assert data == mock_response
|
|
275
268
|
mock_hyperlend_client.get_assets_view.assert_called_once_with(
|
|
276
269
|
user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
@@ -321,7 +314,7 @@ class TestHyperlendAdapter:
|
|
|
321
314
|
user_address="0x0c737cB5934afCb5B01965141F865F795B324080",
|
|
322
315
|
)
|
|
323
316
|
|
|
324
|
-
assert success
|
|
317
|
+
assert success
|
|
325
318
|
assert data == mock_response
|
|
326
319
|
assert len(data.get("assets", [])) == 0
|
|
327
320
|
# New API uses account_data; total_value may not be present
|
|
@@ -72,41 +72,41 @@ class TestHyperliquidAdapter:
|
|
|
72
72
|
async def test_get_meta_and_asset_ctxs(self, adapter):
|
|
73
73
|
"""Test fetching market metadata."""
|
|
74
74
|
success, data = await adapter.get_meta_and_asset_ctxs()
|
|
75
|
-
assert success
|
|
75
|
+
assert success
|
|
76
76
|
assert "universe" in data[0]
|
|
77
77
|
|
|
78
78
|
@pytest.mark.asyncio
|
|
79
79
|
async def test_get_spot_meta(self, adapter):
|
|
80
80
|
"""Test fetching spot metadata."""
|
|
81
81
|
success, data = await adapter.get_spot_meta()
|
|
82
|
-
assert success
|
|
82
|
+
assert success
|
|
83
83
|
|
|
84
84
|
@pytest.mark.asyncio
|
|
85
85
|
async def test_get_funding_history(self, adapter):
|
|
86
86
|
"""Test fetching funding history."""
|
|
87
87
|
success, data = await adapter.get_funding_history("ETH", 1700000000000)
|
|
88
|
-
assert success
|
|
88
|
+
assert success
|
|
89
89
|
assert isinstance(data, list)
|
|
90
90
|
|
|
91
91
|
@pytest.mark.asyncio
|
|
92
92
|
async def test_get_candles(self, adapter):
|
|
93
93
|
"""Test fetching candle data."""
|
|
94
94
|
success, data = await adapter.get_candles("ETH", "1h", 1700000000000)
|
|
95
|
-
assert success
|
|
95
|
+
assert success
|
|
96
96
|
assert isinstance(data, list)
|
|
97
97
|
|
|
98
98
|
@pytest.mark.asyncio
|
|
99
99
|
async def test_get_l2_book(self, adapter):
|
|
100
100
|
"""Test fetching order book."""
|
|
101
101
|
success, data = await adapter.get_l2_book("ETH")
|
|
102
|
-
assert success
|
|
102
|
+
assert success
|
|
103
103
|
assert "levels" in data
|
|
104
104
|
|
|
105
105
|
@pytest.mark.asyncio
|
|
106
106
|
async def test_get_user_state(self, adapter):
|
|
107
107
|
"""Test fetching user state."""
|
|
108
108
|
success, data = await adapter.get_user_state("0x1234")
|
|
109
|
-
assert success
|
|
109
|
+
assert success
|
|
110
110
|
assert "assetPositions" in data
|
|
111
111
|
|
|
112
112
|
@pytest.mark.asyncio
|
|
@@ -28,7 +28,7 @@ class TestSpotAssetIDs:
|
|
|
28
28
|
"""Verify get_spot_assets returns a populated dict."""
|
|
29
29
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
30
30
|
|
|
31
|
-
assert success
|
|
31
|
+
assert success
|
|
32
32
|
assert isinstance(spot_assets, dict)
|
|
33
33
|
assert len(spot_assets) > 0
|
|
34
34
|
|
|
@@ -37,7 +37,7 @@ class TestSpotAssetIDs:
|
|
|
37
37
|
"""PURR/USDC should be the first spot pair (index 0 + 10000 = 10000)."""
|
|
38
38
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
39
39
|
|
|
40
|
-
assert success
|
|
40
|
+
assert success
|
|
41
41
|
assert "PURR/USDC" in spot_assets
|
|
42
42
|
assert spot_assets["PURR/USDC"] == 10000
|
|
43
43
|
|
|
@@ -46,7 +46,7 @@ class TestSpotAssetIDs:
|
|
|
46
46
|
"""HYPE/USDC should have asset ID 10107."""
|
|
47
47
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
48
48
|
|
|
49
|
-
assert success
|
|
49
|
+
assert success
|
|
50
50
|
assert "HYPE/USDC" in spot_assets
|
|
51
51
|
# HYPE is index 107, so asset_id = 10107
|
|
52
52
|
assert spot_assets["HYPE/USDC"] == 10107
|
|
@@ -56,7 +56,7 @@ class TestSpotAssetIDs:
|
|
|
56
56
|
"""ETH/USDC spot pair should exist."""
|
|
57
57
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
58
58
|
|
|
59
|
-
assert success
|
|
59
|
+
assert success
|
|
60
60
|
# ETH spot may have different naming, check common variants
|
|
61
61
|
eth_pairs = [k for k in spot_assets if "ETH" in k and "USDC" in k]
|
|
62
62
|
assert len(eth_pairs) > 0, (
|
|
@@ -68,7 +68,7 @@ class TestSpotAssetIDs:
|
|
|
68
68
|
"""BTC/USDC spot pair should exist."""
|
|
69
69
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
70
70
|
|
|
71
|
-
assert success
|
|
71
|
+
assert success
|
|
72
72
|
# BTC spot may have different naming
|
|
73
73
|
btc_pairs = [k for k in spot_assets if "BTC" in k and "USDC" in k]
|
|
74
74
|
assert len(btc_pairs) > 0, (
|
|
@@ -80,7 +80,7 @@ class TestSpotAssetIDs:
|
|
|
80
80
|
"""All spot asset IDs should be >= 10000."""
|
|
81
81
|
success, spot_assets = await live_adapter.get_spot_assets()
|
|
82
82
|
|
|
83
|
-
assert success
|
|
83
|
+
assert success
|
|
84
84
|
for name, asset_id in spot_assets.items():
|
|
85
85
|
assert asset_id >= 10000, f"{name} has invalid asset_id {asset_id}"
|
|
86
86
|
|
|
@@ -89,7 +89,7 @@ class TestSpotAssetIDs:
|
|
|
89
89
|
"""Test synchronous helper after cache is populated."""
|
|
90
90
|
# First populate cache
|
|
91
91
|
success, _ = await live_adapter.get_spot_assets()
|
|
92
|
-
assert success
|
|
92
|
+
assert success
|
|
93
93
|
|
|
94
94
|
# Now use sync helper
|
|
95
95
|
purr_id = live_adapter.get_spot_asset_id("PURR", "USDC")
|
|
@@ -147,7 +147,7 @@ class TestSpotMetaStructure:
|
|
|
147
147
|
"""Spot meta should have tokens array."""
|
|
148
148
|
success, spot_meta = await live_adapter.get_spot_meta()
|
|
149
149
|
|
|
150
|
-
assert success
|
|
150
|
+
assert success
|
|
151
151
|
assert "tokens" in spot_meta
|
|
152
152
|
assert isinstance(spot_meta["tokens"], list)
|
|
153
153
|
assert len(spot_meta["tokens"]) > 0
|
|
@@ -157,7 +157,7 @@ class TestSpotMetaStructure:
|
|
|
157
157
|
"""Spot meta should have universe array with pairs."""
|
|
158
158
|
success, spot_meta = await live_adapter.get_spot_meta()
|
|
159
159
|
|
|
160
|
-
assert success
|
|
160
|
+
assert success
|
|
161
161
|
assert "universe" in spot_meta
|
|
162
162
|
assert isinstance(spot_meta["universe"], list)
|
|
163
163
|
assert len(spot_meta["universe"]) > 0
|
|
@@ -167,7 +167,7 @@ class TestSpotMetaStructure:
|
|
|
167
167
|
"""Each spot universe entry should have tokens and index."""
|
|
168
168
|
success, spot_meta = await live_adapter.get_spot_meta()
|
|
169
169
|
|
|
170
|
-
assert success
|
|
170
|
+
assert success
|
|
171
171
|
for pair in spot_meta["universe"][:5]: # Check first 5
|
|
172
172
|
assert "tokens" in pair, f"Missing tokens in {pair}"
|
|
173
173
|
assert "index" in pair, f"Missing index in {pair}"
|
|
@@ -182,7 +182,7 @@ class TestL2BookResolution:
|
|
|
182
182
|
"""PURR/USDC (10000) should return valid L2 book."""
|
|
183
183
|
success, book = await live_adapter.get_spot_l2_book(10000)
|
|
184
184
|
|
|
185
|
-
assert success
|
|
185
|
+
assert success
|
|
186
186
|
assert "levels" in book
|
|
187
187
|
|
|
188
188
|
@pytest.mark.asyncio
|
|
@@ -190,7 +190,7 @@ class TestL2BookResolution:
|
|
|
190
190
|
"""HYPE/USDC (10107) should return valid L2 book."""
|
|
191
191
|
success, book = await live_adapter.get_spot_l2_book(10107)
|
|
192
192
|
|
|
193
|
-
assert success
|
|
193
|
+
assert success
|
|
194
194
|
assert "levels" in book
|
|
195
195
|
|
|
196
196
|
|
|
@@ -45,7 +45,7 @@ class TestLedgerAdapter:
|
|
|
45
45
|
offset=0,
|
|
46
46
|
)
|
|
47
47
|
|
|
48
|
-
assert success
|
|
48
|
+
assert success
|
|
49
49
|
assert data == mock_response
|
|
50
50
|
mock_ledger_client.get_strategy_transactions.assert_called_once_with(
|
|
51
51
|
wallet_address="0x1234567890123456789012345678901234567890",
|
|
@@ -84,7 +84,7 @@ class TestLedgerAdapter:
|
|
|
84
84
|
wallet_address="0x1234567890123456789012345678901234567890"
|
|
85
85
|
)
|
|
86
86
|
|
|
87
|
-
assert success
|
|
87
|
+
assert success
|
|
88
88
|
assert data == mock_response
|
|
89
89
|
mock_ledger_client.get_strategy_net_deposit.assert_called_once_with(
|
|
90
90
|
wallet_address="0x1234567890123456789012345678901234567890"
|
|
@@ -110,7 +110,7 @@ class TestLedgerAdapter:
|
|
|
110
110
|
strategy_name="TestStrategy",
|
|
111
111
|
)
|
|
112
112
|
|
|
113
|
-
assert success
|
|
113
|
+
assert success
|
|
114
114
|
assert data == mock_response
|
|
115
115
|
mock_ledger_client.add_strategy_deposit.assert_called_once_with(
|
|
116
116
|
wallet_address="0x1234567890123456789012345678901234567890",
|
|
@@ -142,7 +142,7 @@ class TestLedgerAdapter:
|
|
|
142
142
|
strategy_name="TestStrategy",
|
|
143
143
|
)
|
|
144
144
|
|
|
145
|
-
assert success
|
|
145
|
+
assert success
|
|
146
146
|
assert data == mock_response
|
|
147
147
|
|
|
148
148
|
@pytest.mark.asyncio
|
|
@@ -177,7 +177,7 @@ class TestLedgerAdapter:
|
|
|
177
177
|
strategy_name="TestStrategy",
|
|
178
178
|
)
|
|
179
179
|
|
|
180
|
-
assert success
|
|
180
|
+
assert success
|
|
181
181
|
assert data == mock_response
|
|
182
182
|
|
|
183
183
|
@pytest.mark.asyncio
|
|
@@ -197,7 +197,7 @@ class TestLedgerAdapter:
|
|
|
197
197
|
wallet_address="0x1234567890123456789012345678901234567890", limit=10
|
|
198
198
|
)
|
|
199
199
|
|
|
200
|
-
assert success
|
|
200
|
+
assert success
|
|
201
201
|
assert data["total_transactions"] == 3
|
|
202
202
|
assert data["operations"]["deposits"] == 1
|
|
203
203
|
assert data["operations"]["withdrawals"] == 1
|
|
@@ -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
|
|
29
|
-
|
|
30
|
-
| USDC
|
|
31
|
-
| WETH
|
|
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
|
-
|
|
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
|
|
106
|
-
|
|
107
|
-
| `lend(mtoken, underlying_token, amount)` | Supply tokens to earn yield
|
|
108
|
-
| `unlend(mtoken, amount)`
|
|
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
|
|
113
|
-
|
|
114
|
-
| `borrow(mtoken, amount)`
|
|
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
|
|
120
|
-
|
|
121
|
-
| `set_collateral(mtoken)`
|
|
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
|
|
127
|
-
|
|
128
|
-
| `get_pos(mtoken, account)`
|
|
129
|
-
| `get_collateral_factor(mtoken)`
|
|
130
|
-
| `get_apy(mtoken, apy_type)`
|
|
131
|
-
| `get_borrowable_amount(account)`
|
|
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
|
|
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
|
|
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.
|