wayfinder-paths 0.1.11__py3-none-any.whl → 0.1.13__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.
- wayfinder_paths/adapters/balance_adapter/adapter.py +3 -7
- wayfinder_paths/adapters/brap_adapter/adapter.py +10 -13
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +6 -9
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1 -1
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +44 -5
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +104 -0
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +0 -3
- wayfinder_paths/adapters/pool_adapter/README.md +4 -19
- wayfinder_paths/adapters/pool_adapter/adapter.py +4 -29
- wayfinder_paths/adapters/pool_adapter/examples.json +6 -7
- wayfinder_paths/adapters/pool_adapter/test_adapter.py +8 -8
- wayfinder_paths/core/clients/AuthClient.py +2 -2
- wayfinder_paths/core/clients/BRAPClient.py +2 -2
- wayfinder_paths/core/clients/HyperlendClient.py +2 -2
- wayfinder_paths/core/clients/PoolClient.py +18 -54
- wayfinder_paths/core/clients/TokenClient.py +3 -3
- wayfinder_paths/core/clients/WalletClient.py +2 -2
- wayfinder_paths/core/clients/WayfinderClient.py +9 -10
- wayfinder_paths/core/clients/protocols.py +1 -7
- wayfinder_paths/core/config.py +60 -224
- wayfinder_paths/core/services/local_evm_txn.py +22 -4
- wayfinder_paths/core/strategies/Strategy.py +3 -3
- wayfinder_paths/core/strategies/descriptors.py +7 -0
- wayfinder_paths/core/utils/evm_helpers.py +5 -1
- wayfinder_paths/core/utils/wallets.py +12 -19
- wayfinder_paths/core/wallets/README.md +1 -1
- wayfinder_paths/run_strategy.py +10 -8
- wayfinder_paths/scripts/create_strategy.py +5 -5
- wayfinder_paths/scripts/make_wallets.py +5 -5
- wayfinder_paths/scripts/run_strategy.py +3 -3
- wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -1
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +196 -515
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +228 -11
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -2
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +1 -0
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +1 -1
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +8 -7
- wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +2 -2
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +25 -25
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +28 -9
- wayfinder_paths/templates/adapter/README.md +1 -1
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.13.dist-info}/METADATA +9 -12
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.13.dist-info}/RECORD +45 -45
- wayfinder_paths/core/settings.py +0 -61
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.13.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.11.dist-info → wayfinder_paths-0.1.13.dist-info}/WHEEL +0 -0
|
@@ -7,7 +7,6 @@ from wayfinder_paths.core.clients.TokenClient import TokenClient
|
|
|
7
7
|
from wayfinder_paths.core.clients.WalletClient import WalletClient
|
|
8
8
|
from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
|
|
9
9
|
from wayfinder_paths.core.services.base import Web3Service
|
|
10
|
-
from wayfinder_paths.core.settings import settings
|
|
11
10
|
from wayfinder_paths.core.utils.evm_helpers import resolve_chain_id
|
|
12
11
|
|
|
13
12
|
|
|
@@ -129,12 +128,9 @@ class BalanceAdapter(BaseAdapter):
|
|
|
129
128
|
return False, tx_data
|
|
130
129
|
|
|
131
130
|
tx = tx_data
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
broadcast_result = await self.wallet_provider.broadcast_transaction(
|
|
136
|
-
tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
|
|
137
|
-
)
|
|
131
|
+
broadcast_result = await self.wallet_provider.broadcast_transaction(
|
|
132
|
+
tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
|
|
133
|
+
)
|
|
138
134
|
|
|
139
135
|
if broadcast_result[0] and not skip_ledger and ledger_method is not None:
|
|
140
136
|
wallet_for_ledger = from_address if ledger_wallet == "from" else to_address
|
|
@@ -14,7 +14,6 @@ from wayfinder_paths.core.clients.TokenClient import TokenClient
|
|
|
14
14
|
from wayfinder_paths.core.constants import DEFAULT_SLIPPAGE, ZERO_ADDRESS
|
|
15
15
|
from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
|
|
16
16
|
from wayfinder_paths.core.services.base import Web3Service
|
|
17
|
-
from wayfinder_paths.core.settings import settings
|
|
18
17
|
|
|
19
18
|
_NEEDS_CLEAR_APPROVAL = {
|
|
20
19
|
(1, "0xdac17f958d2ee523a2206206994597c13d831ec7"),
|
|
@@ -339,16 +338,16 @@ class BRAPAdapter(BaseAdapter):
|
|
|
339
338
|
"all_routes": sorted_quotes,
|
|
340
339
|
"route_analysis": {
|
|
341
340
|
"highest_output": sorted_quotes[0] if sorted_quotes else None,
|
|
342
|
-
"lowest_fees":
|
|
343
|
-
all_quotes, key=lambda x: int(x.get("total_fee", 0))
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
"fastest":
|
|
348
|
-
all_quotes, key=lambda x: int(x.get("estimated_time", 0))
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
341
|
+
"lowest_fees": (
|
|
342
|
+
min(all_quotes, key=lambda x: int(x.get("total_fee", 0)))
|
|
343
|
+
if all_quotes
|
|
344
|
+
else None
|
|
345
|
+
),
|
|
346
|
+
"fastest": (
|
|
347
|
+
min(all_quotes, key=lambda x: int(x.get("estimated_time", 0)))
|
|
348
|
+
if all_quotes
|
|
349
|
+
else None
|
|
350
|
+
),
|
|
352
351
|
},
|
|
353
352
|
}
|
|
354
353
|
|
|
@@ -698,8 +697,6 @@ class BRAPAdapter(BaseAdapter):
|
|
|
698
697
|
async def _broadcast_transaction(
|
|
699
698
|
self, transaction: dict[str, Any], confirmations: int = 0
|
|
700
699
|
) -> tuple[bool, Any]:
|
|
701
|
-
if getattr(settings, "DRY_RUN", False):
|
|
702
|
-
return True, {"dry_run": True, "transaction": transaction}
|
|
703
700
|
return await self.wallet_provider.broadcast_transaction(
|
|
704
701
|
transaction,
|
|
705
702
|
wait_for_receipt=True,
|
|
@@ -18,7 +18,6 @@ from wayfinder_paths.core.constants.hyperlend_abi import (
|
|
|
18
18
|
WRAPPED_TOKEN_GATEWAY_ABI,
|
|
19
19
|
)
|
|
20
20
|
from wayfinder_paths.core.services.base import Web3Service
|
|
21
|
-
from wayfinder_paths.core.settings import settings
|
|
22
21
|
|
|
23
22
|
HYPERLEND_DEFAULTS = {
|
|
24
23
|
"pool": "0x00A89d7a5A02160f20150EbEA7a2b5E4879A1A8b",
|
|
@@ -136,7 +135,7 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
136
135
|
chain_id = int(chain_id)
|
|
137
136
|
|
|
138
137
|
if native:
|
|
139
|
-
tx = self._encode_call(
|
|
138
|
+
tx = await self._encode_call(
|
|
140
139
|
target=self.gateway_address,
|
|
141
140
|
abi=WRAPPED_TOKEN_GATEWAY_ABI,
|
|
142
141
|
fn_name="depositETH",
|
|
@@ -156,7 +155,7 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
156
155
|
)
|
|
157
156
|
if not approved[0]:
|
|
158
157
|
return approved
|
|
159
|
-
tx = self._encode_call(
|
|
158
|
+
tx = await self._encode_call(
|
|
160
159
|
target=self.pool_address,
|
|
161
160
|
abi=POOL_ABI,
|
|
162
161
|
fn_name="supply",
|
|
@@ -181,7 +180,7 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
181
180
|
chain_id = int(chain_id)
|
|
182
181
|
|
|
183
182
|
if native:
|
|
184
|
-
tx = self._encode_call(
|
|
183
|
+
tx = await self._encode_call(
|
|
185
184
|
target=self.gateway_address,
|
|
186
185
|
abi=WRAPPED_TOKEN_GATEWAY_ABI,
|
|
187
186
|
fn_name="withdrawETH",
|
|
@@ -191,7 +190,7 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
191
190
|
)
|
|
192
191
|
else:
|
|
193
192
|
token_addr = self._checksum(underlying_token)
|
|
194
|
-
tx = self._encode_call(
|
|
193
|
+
tx = await self._encode_call(
|
|
195
194
|
target=self.pool_address,
|
|
196
195
|
abi=POOL_ABI,
|
|
197
196
|
fn_name="withdraw",
|
|
@@ -237,13 +236,11 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
237
236
|
)
|
|
238
237
|
|
|
239
238
|
async def _broadcast_transaction(self, tx: dict[str, Any]) -> tuple[bool, Any]:
|
|
240
|
-
if getattr(settings, "DRY_RUN", False):
|
|
241
|
-
return True, {"dry_run": True, "transaction": tx}
|
|
242
239
|
return await self.web3.evm_transactions.broadcast_transaction(
|
|
243
240
|
tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
|
|
244
241
|
)
|
|
245
242
|
|
|
246
|
-
def _encode_call(
|
|
243
|
+
async def _encode_call(
|
|
247
244
|
self,
|
|
248
245
|
*,
|
|
249
246
|
target: str,
|
|
@@ -258,7 +255,7 @@ class HyperlendAdapter(BaseAdapter):
|
|
|
258
255
|
web3 = self.web3.get_web3(chain_id)
|
|
259
256
|
contract = web3.eth.contract(address=target, abi=abi)
|
|
260
257
|
try:
|
|
261
|
-
data = getattr(contract.functions, fn_name)(*args).build_transaction(
|
|
258
|
+
data = await getattr(contract.functions, fn_name)(*args).build_transaction(
|
|
262
259
|
{"from": from_address}
|
|
263
260
|
)["data"]
|
|
264
261
|
except ValueError as exc:
|
|
@@ -10,6 +10,7 @@ the protocol (for example, by delegating signing to a hosted signer).
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
import uuid
|
|
13
|
+
from collections.abc import Mapping
|
|
13
14
|
from typing import Any
|
|
14
15
|
|
|
15
16
|
from loguru import logger
|
|
@@ -86,11 +87,49 @@ class LocalHyperliquidExecutor:
|
|
|
86
87
|
)
|
|
87
88
|
self.info = Info(base_url, skip_ws=True)
|
|
88
89
|
self.exchange = Exchange(self._wallet, base_url)
|
|
90
|
+
self._asset_id_to_coin: dict[int, str] | None = None
|
|
89
91
|
|
|
90
92
|
logger.info(
|
|
91
93
|
f"LocalHyperliquidExecutor initialized for address: {self._wallet.address}"
|
|
92
94
|
)
|
|
93
95
|
|
|
96
|
+
def _get_perp_coin(self, asset_id: int) -> str | None:
|
|
97
|
+
"""
|
|
98
|
+
Resolve a perp coin name from a perp asset_id.
|
|
99
|
+
|
|
100
|
+
Note: newer versions of the hyperliquid SDK expose `coin_to_asset` but not
|
|
101
|
+
`asset_to_coin`, so we build the reverse mapping when needed.
|
|
102
|
+
"""
|
|
103
|
+
if self._asset_id_to_coin is None:
|
|
104
|
+
mapping: dict[int, str] = {}
|
|
105
|
+
|
|
106
|
+
asset_to_coin = getattr(self.info, "asset_to_coin", None)
|
|
107
|
+
if isinstance(asset_to_coin, Mapping):
|
|
108
|
+
for k, v in asset_to_coin.items():
|
|
109
|
+
try:
|
|
110
|
+
asset_int = int(k)
|
|
111
|
+
except (TypeError, ValueError):
|
|
112
|
+
continue
|
|
113
|
+
if v:
|
|
114
|
+
mapping[asset_int] = str(v)
|
|
115
|
+
|
|
116
|
+
coin_to_asset = getattr(self.info, "coin_to_asset", None)
|
|
117
|
+
try:
|
|
118
|
+
coin_to_asset_dict = dict(coin_to_asset) if coin_to_asset else {}
|
|
119
|
+
except Exception: # noqa: BLE001
|
|
120
|
+
coin_to_asset_dict = {}
|
|
121
|
+
for coin, aid in coin_to_asset_dict.items():
|
|
122
|
+
try:
|
|
123
|
+
asset_int = int(aid)
|
|
124
|
+
except (TypeError, ValueError):
|
|
125
|
+
continue
|
|
126
|
+
if coin and asset_int not in mapping:
|
|
127
|
+
mapping[asset_int] = str(coin)
|
|
128
|
+
|
|
129
|
+
self._asset_id_to_coin = mapping
|
|
130
|
+
|
|
131
|
+
return self._asset_id_to_coin.get(asset_id) if self._asset_id_to_coin else None
|
|
132
|
+
|
|
94
133
|
def _resolve_private_key(self, config: dict[str, Any]) -> str | None:
|
|
95
134
|
"""Extract private key from config."""
|
|
96
135
|
# Try strategy_wallet first
|
|
@@ -172,7 +211,7 @@ class LocalHyperliquidExecutor:
|
|
|
172
211
|
)
|
|
173
212
|
else:
|
|
174
213
|
# Perp market order
|
|
175
|
-
coin = self.
|
|
214
|
+
coin = self._get_perp_coin(asset_id)
|
|
176
215
|
if not coin:
|
|
177
216
|
return {
|
|
178
217
|
"status": "err",
|
|
@@ -231,7 +270,7 @@ class LocalHyperliquidExecutor:
|
|
|
231
270
|
spot_index = asset_id - 10000
|
|
232
271
|
coin = f"@{spot_index}"
|
|
233
272
|
else:
|
|
234
|
-
coin = self.
|
|
273
|
+
coin = self._get_perp_coin(asset_id)
|
|
235
274
|
if not coin:
|
|
236
275
|
return {
|
|
237
276
|
"status": "err",
|
|
@@ -268,7 +307,7 @@ class LocalHyperliquidExecutor:
|
|
|
268
307
|
}
|
|
269
308
|
|
|
270
309
|
try:
|
|
271
|
-
coin = self.
|
|
310
|
+
coin = self._get_perp_coin(asset_id)
|
|
272
311
|
if not coin:
|
|
273
312
|
return {
|
|
274
313
|
"status": "err",
|
|
@@ -366,7 +405,7 @@ class LocalHyperliquidExecutor:
|
|
|
366
405
|
}
|
|
367
406
|
|
|
368
407
|
try:
|
|
369
|
-
coin = self.
|
|
408
|
+
coin = self._get_perp_coin(asset_id)
|
|
370
409
|
if not coin:
|
|
371
410
|
return {
|
|
372
411
|
"status": "err",
|
|
@@ -439,7 +478,7 @@ class LocalHyperliquidExecutor:
|
|
|
439
478
|
spot_index = asset_id - 10000
|
|
440
479
|
coin = f"@{spot_index}"
|
|
441
480
|
else:
|
|
442
|
-
coin = self.
|
|
481
|
+
coin = self._get_perp_coin(asset_id)
|
|
443
482
|
if not coin:
|
|
444
483
|
return {
|
|
445
484
|
"status": "err",
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"""Tests for LocalHyperliquidExecutor."""
|
|
2
|
+
|
|
3
|
+
from types import SimpleNamespace
|
|
4
|
+
from unittest.mock import MagicMock, patch
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestLocalHyperliquidExecutor:
|
|
10
|
+
"""Tests for LocalHyperliquidExecutor."""
|
|
11
|
+
|
|
12
|
+
@pytest.fixture
|
|
13
|
+
def mock_info_without_asset_to_coin(self):
|
|
14
|
+
"""Mock Info that only exposes coin_to_asset (no asset_to_coin)."""
|
|
15
|
+
return SimpleNamespace(coin_to_asset={"HYPE": 7})
|
|
16
|
+
|
|
17
|
+
@pytest.fixture
|
|
18
|
+
def mock_exchange(self):
|
|
19
|
+
"""Mock Exchange client."""
|
|
20
|
+
mock = MagicMock()
|
|
21
|
+
mock.update_leverage.return_value = {"status": "ok"}
|
|
22
|
+
mock.market_open.return_value = {"status": "ok"}
|
|
23
|
+
return mock
|
|
24
|
+
|
|
25
|
+
@pytest.mark.asyncio
|
|
26
|
+
async def test_update_leverage_works_without_asset_to_coin(
|
|
27
|
+
self, mock_info_without_asset_to_coin, mock_exchange
|
|
28
|
+
):
|
|
29
|
+
"""update_leverage should map asset_id->coin via coin_to_asset inversion."""
|
|
30
|
+
dummy_wallet = SimpleNamespace(address="0xabc")
|
|
31
|
+
|
|
32
|
+
with patch(
|
|
33
|
+
"wayfinder_paths.adapters.hyperliquid_adapter.executor.Account"
|
|
34
|
+
) as mock_account:
|
|
35
|
+
mock_account.from_key.return_value = dummy_wallet
|
|
36
|
+
with patch(
|
|
37
|
+
"wayfinder_paths.adapters.hyperliquid_adapter.executor.Info",
|
|
38
|
+
return_value=mock_info_without_asset_to_coin,
|
|
39
|
+
):
|
|
40
|
+
with patch(
|
|
41
|
+
"wayfinder_paths.adapters.hyperliquid_adapter.executor.Exchange",
|
|
42
|
+
return_value=mock_exchange,
|
|
43
|
+
):
|
|
44
|
+
from wayfinder_paths.adapters.hyperliquid_adapter.executor import (
|
|
45
|
+
LocalHyperliquidExecutor,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
executor = LocalHyperliquidExecutor(
|
|
49
|
+
config={"strategy_wallet": {"private_key": "0x" + "11" * 32}}
|
|
50
|
+
)
|
|
51
|
+
resp = await executor.update_leverage(
|
|
52
|
+
asset_id=7,
|
|
53
|
+
leverage=1,
|
|
54
|
+
is_cross=True,
|
|
55
|
+
address="0xabc",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
assert resp.get("status") == "ok"
|
|
59
|
+
mock_exchange.update_leverage.assert_called_once_with(
|
|
60
|
+
leverage=1,
|
|
61
|
+
name="HYPE",
|
|
62
|
+
is_cross=True,
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
@pytest.mark.asyncio
|
|
66
|
+
async def test_place_market_order_perp_works_without_asset_to_coin(
|
|
67
|
+
self, mock_info_without_asset_to_coin, mock_exchange
|
|
68
|
+
):
|
|
69
|
+
"""place_market_order should map asset_id->coin via coin_to_asset inversion."""
|
|
70
|
+
dummy_wallet = SimpleNamespace(address="0xabc")
|
|
71
|
+
|
|
72
|
+
with patch(
|
|
73
|
+
"wayfinder_paths.adapters.hyperliquid_adapter.executor.Account"
|
|
74
|
+
) as mock_account:
|
|
75
|
+
mock_account.from_key.return_value = dummy_wallet
|
|
76
|
+
with patch(
|
|
77
|
+
"wayfinder_paths.adapters.hyperliquid_adapter.executor.Info",
|
|
78
|
+
return_value=mock_info_without_asset_to_coin,
|
|
79
|
+
):
|
|
80
|
+
with patch(
|
|
81
|
+
"wayfinder_paths.adapters.hyperliquid_adapter.executor.Exchange",
|
|
82
|
+
return_value=mock_exchange,
|
|
83
|
+
):
|
|
84
|
+
from wayfinder_paths.adapters.hyperliquid_adapter.executor import (
|
|
85
|
+
LocalHyperliquidExecutor,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
executor = LocalHyperliquidExecutor(
|
|
89
|
+
config={"strategy_wallet": {"private_key": "0x" + "11" * 32}}
|
|
90
|
+
)
|
|
91
|
+
resp = await executor.place_market_order(
|
|
92
|
+
asset_id=7,
|
|
93
|
+
is_buy=True,
|
|
94
|
+
slippage=0.01,
|
|
95
|
+
size=1.0,
|
|
96
|
+
address="0xabc",
|
|
97
|
+
reduce_only=False,
|
|
98
|
+
cloid=MagicMock(), # Avoid Cloid conversion
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
assert resp.get("status") == "ok"
|
|
102
|
+
mock_exchange.market_open.assert_called_once()
|
|
103
|
+
_, kwargs = mock_exchange.market_open.call_args
|
|
104
|
+
assert kwargs["name"] == "HYPE"
|
|
@@ -23,7 +23,6 @@ from wayfinder_paths.core.constants.moonwell_abi import (
|
|
|
23
23
|
WETH_ABI,
|
|
24
24
|
)
|
|
25
25
|
from wayfinder_paths.core.services.base import Web3Service
|
|
26
|
-
from wayfinder_paths.core.settings import settings
|
|
27
26
|
|
|
28
27
|
# Moonwell Base chain addresses
|
|
29
28
|
MOONWELL_DEFAULTS = {
|
|
@@ -1149,8 +1148,6 @@ class MoonwellAdapter(BaseAdapter):
|
|
|
1149
1148
|
|
|
1150
1149
|
Includes retry logic with exponential backoff for rate-limited RPCs.
|
|
1151
1150
|
"""
|
|
1152
|
-
if getattr(settings, "DRY_RUN", False):
|
|
1153
|
-
return True, {"dry_run": True, "transaction": tx}
|
|
1154
1151
|
if not self.web3:
|
|
1155
1152
|
return False, "web3 service not configured"
|
|
1156
1153
|
|
|
@@ -35,8 +35,7 @@ adapter = PoolAdapter()
|
|
|
35
35
|
|
|
36
36
|
```python
|
|
37
37
|
success, data = await adapter.get_pools_by_ids(
|
|
38
|
-
pool_ids=["pool-123", "pool-456"]
|
|
39
|
-
merge_external=True
|
|
38
|
+
pool_ids=["pool-123", "pool-456"]
|
|
40
39
|
)
|
|
41
40
|
if success:
|
|
42
41
|
pools = data.get("pools", [])
|
|
@@ -48,27 +47,13 @@ else:
|
|
|
48
47
|
### Get Llama Matches
|
|
49
48
|
|
|
50
49
|
```python
|
|
51
|
-
success, data = await adapter.
|
|
50
|
+
success, data = await adapter.get_pools()
|
|
52
51
|
if success:
|
|
53
52
|
matches = data.get("matches", [])
|
|
54
53
|
print(f"Found {len(matches)} Llama matches")
|
|
55
54
|
for match in matches:
|
|
56
|
-
if match.get("
|
|
57
|
-
print(f"Stablecoin pool: {match.get('id')} - APY: {match.get('
|
|
58
|
-
else:
|
|
59
|
-
print(f"Error: {data}")
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
### Get Llama Reports
|
|
63
|
-
|
|
64
|
-
```python
|
|
65
|
-
success, data = await adapter.get_llama_reports(
|
|
66
|
-
identifiers=["pool-123", "usd-coin-base", "base_0x1234..."]
|
|
67
|
-
)
|
|
68
|
-
if success:
|
|
69
|
-
reports = data
|
|
70
|
-
for identifier, report in reports.items():
|
|
71
|
-
print(f"Report for {identifier}: APY {report.get('llama_apy_pct', 0)}%")
|
|
55
|
+
if match.get("stablecoin"):
|
|
56
|
+
print(f"Stablecoin pool: {match.get('id')} - APY: {match.get('apy')}%")
|
|
72
57
|
else:
|
|
73
58
|
print(f"Error: {data}")
|
|
74
59
|
```
|
|
@@ -3,7 +3,6 @@ from typing import Any
|
|
|
3
3
|
from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
|
|
4
4
|
from wayfinder_paths.core.clients.PoolClient import (
|
|
5
5
|
LlamaMatch,
|
|
6
|
-
LlamaReport,
|
|
7
6
|
PoolClient,
|
|
8
7
|
PoolList,
|
|
9
8
|
)
|
|
@@ -31,29 +30,25 @@ class PoolAdapter(BaseAdapter):
|
|
|
31
30
|
self.pool_client = pool_client or PoolClient()
|
|
32
31
|
|
|
33
32
|
async def get_pools_by_ids(
|
|
34
|
-
self, pool_ids: list[str]
|
|
33
|
+
self, pool_ids: list[str]
|
|
35
34
|
) -> tuple[bool, PoolList | str]:
|
|
36
35
|
"""
|
|
37
36
|
Get pool information by pool IDs.
|
|
38
37
|
|
|
39
38
|
Args:
|
|
40
39
|
pool_ids: List of pool identifiers
|
|
41
|
-
merge_external: Whether to merge external data
|
|
42
40
|
|
|
43
41
|
Returns:
|
|
44
42
|
Tuple of (success, data) where data is pool information or error message
|
|
45
43
|
"""
|
|
46
44
|
try:
|
|
47
|
-
|
|
48
|
-
data = await self.pool_client.get_pools_by_ids(
|
|
49
|
-
pool_ids=pool_ids_str, merge_external=merge_external
|
|
50
|
-
)
|
|
45
|
+
data = await self.pool_client.get_pools_by_ids(pool_ids=pool_ids)
|
|
51
46
|
return (True, data)
|
|
52
47
|
except Exception as e:
|
|
53
48
|
self.logger.error(f"Error fetching pools by IDs: {e}")
|
|
54
49
|
return (False, str(e))
|
|
55
50
|
|
|
56
|
-
async def
|
|
51
|
+
async def get_pools(self) -> tuple[bool, dict[str, LlamaMatch] | str]:
|
|
57
52
|
"""
|
|
58
53
|
Get Llama protocol matches for pools.
|
|
59
54
|
|
|
@@ -61,28 +56,8 @@ class PoolAdapter(BaseAdapter):
|
|
|
61
56
|
Tuple of (success, data) where data is Llama matches or error message
|
|
62
57
|
"""
|
|
63
58
|
try:
|
|
64
|
-
data = await self.pool_client.
|
|
59
|
+
data = await self.pool_client.get_pools()
|
|
65
60
|
return (True, data)
|
|
66
61
|
except Exception as e:
|
|
67
62
|
self.logger.error(f"Error fetching Llama matches: {e}")
|
|
68
63
|
return (False, str(e))
|
|
69
|
-
|
|
70
|
-
async def get_llama_reports(
|
|
71
|
-
self, identifiers: list[str]
|
|
72
|
-
) -> tuple[bool, dict[str, LlamaReport] | str]:
|
|
73
|
-
"""
|
|
74
|
-
Get Llama reports for specific identifiers.
|
|
75
|
-
|
|
76
|
-
Args:
|
|
77
|
-
identifiers: List of identifiers (token IDs, addresses, pool IDs)
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
Tuple of (success, data) where data is Llama reports or error message
|
|
81
|
-
"""
|
|
82
|
-
try:
|
|
83
|
-
identifiers_str = ",".join(identifiers)
|
|
84
|
-
data = await self.pool_client.get_llama_reports(identifiers=identifiers_str)
|
|
85
|
-
return (True, data)
|
|
86
|
-
except Exception as e:
|
|
87
|
-
self.logger.error(f"Error fetching Llama reports: {e}")
|
|
88
|
-
return (False, str(e))
|
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
"get_pools_by_ids": {
|
|
3
3
|
"description": "Get pool information by pool IDs",
|
|
4
4
|
"input": {
|
|
5
|
-
"pool_ids": ["pool-123", "pool-456"]
|
|
6
|
-
"merge_external": true
|
|
5
|
+
"pool_ids": ["pool-123", "pool-456"]
|
|
7
6
|
},
|
|
8
7
|
"output": {
|
|
9
8
|
"success": true,
|
|
@@ -21,7 +20,7 @@
|
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
},
|
|
24
|
-
"
|
|
23
|
+
"get_pools": {
|
|
25
24
|
"description": "Get Llama protocol matches for pools",
|
|
26
25
|
"input": {},
|
|
27
26
|
"output": {
|
|
@@ -30,10 +29,10 @@
|
|
|
30
29
|
"matches": [
|
|
31
30
|
{
|
|
32
31
|
"id": "pool-123",
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
32
|
+
"apy": 5.2,
|
|
33
|
+
"tvlUsd": 1000000,
|
|
34
|
+
"stablecoin": true,
|
|
35
|
+
"ilRisk": "no",
|
|
37
36
|
"network": "base"
|
|
38
37
|
}
|
|
39
38
|
]
|
|
@@ -38,33 +38,33 @@ class TestPoolAdapter:
|
|
|
38
38
|
mock_pool_client.get_pools_by_ids = AsyncMock(return_value=mock_response)
|
|
39
39
|
|
|
40
40
|
success, data = await adapter.get_pools_by_ids(
|
|
41
|
-
pool_ids=["pool-123", "pool-456"]
|
|
41
|
+
pool_ids=["pool-123", "pool-456"]
|
|
42
42
|
)
|
|
43
43
|
|
|
44
44
|
assert success is True
|
|
45
45
|
assert data == mock_response
|
|
46
46
|
mock_pool_client.get_pools_by_ids.assert_called_once_with(
|
|
47
|
-
pool_ids="pool-123,pool-456"
|
|
47
|
+
pool_ids=["pool-123", "pool-456"]
|
|
48
48
|
)
|
|
49
49
|
|
|
50
50
|
@pytest.mark.asyncio
|
|
51
|
-
async def
|
|
51
|
+
async def test_get_pools_success(self, adapter, mock_pool_client):
|
|
52
52
|
"""Test successful Llama matches retrieval"""
|
|
53
53
|
# Mock response
|
|
54
54
|
mock_response = {
|
|
55
55
|
"matches": [
|
|
56
56
|
{
|
|
57
57
|
"id": "pool-123",
|
|
58
|
-
"
|
|
59
|
-
"
|
|
60
|
-
"
|
|
58
|
+
"apy": 5.2,
|
|
59
|
+
"tvlUsd": 1000000,
|
|
60
|
+
"stablecoin": True,
|
|
61
61
|
"network": "base",
|
|
62
62
|
}
|
|
63
63
|
]
|
|
64
64
|
}
|
|
65
|
-
mock_pool_client.
|
|
65
|
+
mock_pool_client.get_pools = AsyncMock(return_value=mock_response)
|
|
66
66
|
|
|
67
|
-
success, data = await adapter.
|
|
67
|
+
success, data = await adapter.get_pools()
|
|
68
68
|
|
|
69
69
|
assert success is True
|
|
70
70
|
assert data == mock_response
|
|
@@ -4,7 +4,7 @@ from typing import Any
|
|
|
4
4
|
from loguru import logger
|
|
5
5
|
|
|
6
6
|
from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
|
|
7
|
-
from wayfinder_paths.core.
|
|
7
|
+
from wayfinder_paths.core.config import get_api_base_url
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
class AuthClient(WayfinderClient):
|
|
@@ -18,7 +18,7 @@ class AuthClient(WayfinderClient):
|
|
|
18
18
|
"""
|
|
19
19
|
super().__init__(api_key=api_key)
|
|
20
20
|
|
|
21
|
-
self.api_base_url =
|
|
21
|
+
self.api_base_url = get_api_base_url()
|
|
22
22
|
self.logger = logger.bind(client="AuthClient")
|
|
23
23
|
|
|
24
24
|
def _is_using_api_key(self) -> bool:
|
|
@@ -12,7 +12,7 @@ from loguru import logger
|
|
|
12
12
|
|
|
13
13
|
from wayfinder_paths.core.clients.AuthClient import AuthClient
|
|
14
14
|
from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
|
|
15
|
-
from wayfinder_paths.core.
|
|
15
|
+
from wayfinder_paths.core.config import get_api_base_url
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class BRAPQuote(TypedDict):
|
|
@@ -38,7 +38,7 @@ class BRAPClient(WayfinderClient):
|
|
|
38
38
|
|
|
39
39
|
def __init__(self, api_key: str | None = None):
|
|
40
40
|
super().__init__(api_key=api_key)
|
|
41
|
-
self.api_base_url =
|
|
41
|
+
self.api_base_url = get_api_base_url()
|
|
42
42
|
self._auth_client: AuthClient | None = AuthClient(api_key=api_key)
|
|
43
43
|
|
|
44
44
|
async def get_quote(
|
|
@@ -8,7 +8,7 @@ from __future__ import annotations
|
|
|
8
8
|
from typing import Any, NotRequired, Required, TypedDict
|
|
9
9
|
|
|
10
10
|
from wayfinder_paths.core.clients.WayfinderClient import WayfinderClient
|
|
11
|
-
from wayfinder_paths.core.
|
|
11
|
+
from wayfinder_paths.core.config import get_api_base_url
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
class StableMarket(TypedDict):
|
|
@@ -55,7 +55,7 @@ class HyperlendClient(WayfinderClient):
|
|
|
55
55
|
|
|
56
56
|
def __init__(self, api_key: str | None = None):
|
|
57
57
|
super().__init__(api_key=api_key)
|
|
58
|
-
self.api_base_url =
|
|
58
|
+
self.api_base_url = get_api_base_url()
|
|
59
59
|
|
|
60
60
|
async def get_stable_markets(
|
|
61
61
|
self,
|