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.
- wayfinder_paths/__init__.py +2 -0
- wayfinder_paths/adapters/balance_adapter/adapter.py +250 -0
- wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
- wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -11
- wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
- wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
- wayfinder_paths/adapters/boros_adapter/client.py +476 -0
- wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
- wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
- wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
- wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
- wayfinder_paths/adapters/boros_adapter/types.py +70 -0
- wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
- wayfinder_paths/adapters/brap_adapter/adapter.py +1 -1
- wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/hyperlend_adapter/adapter.py +161 -26
- wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
- wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +77 -13
- wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
- wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +585 -61
- wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
- wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
- wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
- wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
- wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
- wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
- wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
- wayfinder_paths/adapters/moonwell_adapter/adapter.py +592 -400
- wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
- wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +126 -219
- wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
- wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
- wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
- wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
- wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
- wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
- wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
- wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
- wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
- wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
- wayfinder_paths/adapters/token_adapter/examples.json +0 -4
- wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
- wayfinder_paths/conftest.py +24 -17
- wayfinder_paths/core/__init__.py +2 -0
- wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
- wayfinder_paths/core/adapters/models.py +17 -7
- wayfinder_paths/core/clients/BRAPClient.py +1 -1
- wayfinder_paths/core/clients/TokenClient.py +47 -1
- wayfinder_paths/core/clients/WayfinderClient.py +1 -2
- wayfinder_paths/core/clients/protocols.py +21 -22
- wayfinder_paths/core/clients/test_ledger_client.py +448 -0
- wayfinder_paths/core/config.py +12 -0
- wayfinder_paths/core/constants/__init__.py +15 -0
- wayfinder_paths/core/constants/base.py +6 -1
- wayfinder_paths/core/constants/contracts.py +39 -26
- wayfinder_paths/core/constants/erc20_abi.py +0 -1
- wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
- wayfinder_paths/core/constants/hyperliquid.py +16 -0
- wayfinder_paths/core/constants/moonwell_abi.py +0 -15
- wayfinder_paths/core/engine/manifest.py +66 -0
- wayfinder_paths/core/strategies/Strategy.py +0 -61
- wayfinder_paths/core/strategies/__init__.py +10 -1
- wayfinder_paths/core/strategies/opa_loop.py +167 -0
- wayfinder_paths/core/utils/test_transaction.py +289 -0
- wayfinder_paths/core/utils/transaction.py +44 -1
- wayfinder_paths/core/utils/web3.py +3 -0
- wayfinder_paths/mcp/__init__.py +5 -0
- wayfinder_paths/mcp/preview.py +185 -0
- wayfinder_paths/mcp/scripting.py +84 -0
- wayfinder_paths/mcp/server.py +52 -0
- wayfinder_paths/mcp/state/profile_store.py +195 -0
- wayfinder_paths/mcp/state/store.py +89 -0
- wayfinder_paths/mcp/test_scripting.py +267 -0
- wayfinder_paths/mcp/tools/__init__.py +0 -0
- wayfinder_paths/mcp/tools/balances.py +290 -0
- wayfinder_paths/mcp/tools/discovery.py +158 -0
- wayfinder_paths/mcp/tools/execute.py +770 -0
- wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
- wayfinder_paths/mcp/tools/quotes.py +288 -0
- wayfinder_paths/mcp/tools/run_script.py +286 -0
- wayfinder_paths/mcp/tools/strategies.py +188 -0
- wayfinder_paths/mcp/tools/tokens.py +46 -0
- wayfinder_paths/mcp/tools/wallets.py +354 -0
- wayfinder_paths/mcp/utils.py +129 -0
- wayfinder_paths/policies/hyperliquid.py +1 -1
- wayfinder_paths/policies/lifi.py +18 -0
- wayfinder_paths/policies/util.py +8 -2
- wayfinder_paths/strategies/basis_trading_strategy/strategy.py +28 -119
- wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
- wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
- wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
- wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
- wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
- wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
- wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
- wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
- wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
- wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
- wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
- wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
- wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
- wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +202 -0
- wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
- wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +3 -12
- wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +7 -29
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +63 -40
- wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
- wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -34
- wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +11 -34
- wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
- wayfinder_paths/tests/test_test_coverage.py +1 -4
- wayfinder_paths-0.1.25.dist-info/METADATA +377 -0
- wayfinder_paths-0.1.25.dist-info/RECORD +185 -0
- wayfinder_paths/scripts/create_strategy.py +0 -139
- wayfinder_paths/scripts/make_wallets.py +0 -142
- wayfinder_paths-0.1.23.dist-info/METADATA +0 -354
- wayfinder_paths-0.1.23.dist-info/RECORD +0 -120
- /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
- {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/LICENSE +0 -0
- {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/WHEEL +0 -0
|
@@ -206,15 +206,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
206
206
|
strategy_wallet_signing_callback=self.strategy_wallet_signing_callback,
|
|
207
207
|
)
|
|
208
208
|
|
|
209
|
-
self.register_adapters(
|
|
210
|
-
[
|
|
211
|
-
balance,
|
|
212
|
-
token_adapter,
|
|
213
|
-
ledger_adapter,
|
|
214
|
-
pool_adapter,
|
|
215
|
-
brap_adapter,
|
|
216
|
-
]
|
|
217
|
-
)
|
|
218
209
|
self.balance_adapter = balance
|
|
219
210
|
self.token_adapter = token_adapter
|
|
220
211
|
self.ledger_adapter = ledger_adapter
|
|
@@ -504,7 +495,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
504
495
|
== self.current_pool.get("chain").get("id")
|
|
505
496
|
else None
|
|
506
497
|
)
|
|
507
|
-
# Refresh all tracked balances from blockchain
|
|
508
498
|
await self._refresh_tracked_balances()
|
|
509
499
|
logger.info(
|
|
510
500
|
f"Refreshed balances for {len(self.tracked_balances)} tracked tokens"
|
|
@@ -573,7 +563,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
573
563
|
|
|
574
564
|
async def _infer_active_pool_from_tracked_tokens(self):
|
|
575
565
|
try:
|
|
576
|
-
# Refresh balances for tracked tokens
|
|
577
566
|
await self._refresh_tracked_balances()
|
|
578
567
|
|
|
579
568
|
usdc_token_id = self.usdc_token_info.get("token_id")
|
|
@@ -582,7 +571,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
582
571
|
best_token_id = None
|
|
583
572
|
best_balance_wei = 0
|
|
584
573
|
|
|
585
|
-
# Find the non-gas, non-USDC token with the largest balance
|
|
586
574
|
for token_id, balance_wei in self.tracked_balances.items():
|
|
587
575
|
if balance_wei <= 0:
|
|
588
576
|
continue
|
|
@@ -591,7 +579,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
591
579
|
if token_id == usdc_token_id:
|
|
592
580
|
continue
|
|
593
581
|
|
|
594
|
-
# Prefer tokens with larger balances
|
|
595
582
|
if balance_wei > best_balance_wei:
|
|
596
583
|
best_token_id = token_id
|
|
597
584
|
best_balance_wei = balance_wei
|
|
@@ -792,7 +779,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
792
779
|
f"Need at least {required_gas} {gas_symbol} on Base for gas. You have: {total_gas}",
|
|
793
780
|
)
|
|
794
781
|
|
|
795
|
-
# Transfer main token if provided
|
|
796
782
|
if main_token_amount > 0:
|
|
797
783
|
self.current_pool_balance = int(
|
|
798
784
|
main_token_amount * (10 ** self.current_pool.get("decimals"))
|
|
@@ -800,7 +786,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
800
786
|
self.DEPOSIT_USDC = main_token_amount
|
|
801
787
|
logger.info(f"Set deposit amount to {main_token_amount} USDC")
|
|
802
788
|
|
|
803
|
-
# Transfer USDC from main to strategy wallet
|
|
804
789
|
logger.info("Initiating USDC transfer from main to strategy wallet")
|
|
805
790
|
(
|
|
806
791
|
success,
|
|
@@ -978,7 +963,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
978
963
|
|
|
979
964
|
transferred_items = []
|
|
980
965
|
|
|
981
|
-
# Transfer USDC to main wallet
|
|
982
966
|
usdc_ok, usdc_raw = await self.balance_adapter.get_balance(
|
|
983
967
|
token_id="usd-coin-base",
|
|
984
968
|
wallet_address=strategy_address,
|
|
@@ -1001,7 +985,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1001
985
|
else:
|
|
1002
986
|
logger.warning(f"USDC transfer failed: {msg}")
|
|
1003
987
|
|
|
1004
|
-
# Transfer ETH (minus reserve for tx fees) to main wallet
|
|
1005
988
|
eth_ok, eth_raw = await self.balance_adapter.get_balance(
|
|
1006
989
|
token_id="ethereum-base",
|
|
1007
990
|
wallet_address=strategy_address,
|
|
@@ -1223,7 +1206,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1223
1206
|
pass
|
|
1224
1207
|
|
|
1225
1208
|
async def _sweep_wallet(self, target_token):
|
|
1226
|
-
# Refresh tracked balances
|
|
1227
1209
|
await self._refresh_tracked_balances()
|
|
1228
1210
|
|
|
1229
1211
|
target_token_id = target_token.get("token_id")
|
|
@@ -1231,17 +1213,13 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1231
1213
|
target_address = target_token.get("address", "").lower()
|
|
1232
1214
|
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
1233
1215
|
|
|
1234
|
-
# Swap all non-target, non-gas tokens to the target
|
|
1235
1216
|
for token_id, balance_wei in list(self.tracked_balances.items()):
|
|
1236
|
-
# Skip if no balance
|
|
1237
1217
|
if balance_wei <= 0:
|
|
1238
1218
|
continue
|
|
1239
1219
|
|
|
1240
|
-
# Skip gas token
|
|
1241
1220
|
if token_id == gas_token_id:
|
|
1242
1221
|
continue
|
|
1243
1222
|
|
|
1244
|
-
# Skip if it's already the target token
|
|
1245
1223
|
if token_id == target_token_id:
|
|
1246
1224
|
continue
|
|
1247
1225
|
|
|
@@ -1281,9 +1259,7 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1281
1259
|
logger.error(f"Error sweeping {token_id}: {e}")
|
|
1282
1260
|
continue
|
|
1283
1261
|
|
|
1284
|
-
# Track the target token
|
|
1285
1262
|
self._track_token(target_token_id)
|
|
1286
|
-
# Refresh target token balance
|
|
1287
1263
|
try:
|
|
1288
1264
|
success, target_balance = await self.balance_adapter.get_balance(
|
|
1289
1265
|
token_id=target_token_id,
|
|
@@ -1368,18 +1344,15 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1368
1344
|
)
|
|
1369
1345
|
|
|
1370
1346
|
async def _get_non_gas_balances(self) -> list[dict[str, Any]]:
|
|
1371
|
-
# Refresh tracked balances
|
|
1372
1347
|
await self._refresh_tracked_balances()
|
|
1373
1348
|
|
|
1374
1349
|
gas_token_id = self.gas_token.get("token_id") if self.gas_token else None
|
|
1375
1350
|
results = []
|
|
1376
1351
|
|
|
1377
1352
|
for token_id, balance_wei in self.tracked_balances.items():
|
|
1378
|
-
# Skip gas token
|
|
1379
1353
|
if token_id == gas_token_id:
|
|
1380
1354
|
continue
|
|
1381
1355
|
|
|
1382
|
-
# Skip zero balances
|
|
1383
1356
|
if balance_wei <= 0:
|
|
1384
1357
|
continue
|
|
1385
1358
|
|
|
@@ -1551,7 +1524,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1551
1524
|
)
|
|
1552
1525
|
|
|
1553
1526
|
if not self.DEPOSIT_USDC:
|
|
1554
|
-
# No deposits recorded - report minimal status
|
|
1555
1527
|
status_payload = {
|
|
1556
1528
|
"info": "No recorded strategy deposits.",
|
|
1557
1529
|
"idle_usd": 0.0,
|
|
@@ -1631,7 +1603,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1631
1603
|
return [f"({wallet_id}) && (({approve_enso}) || ({swap_enso})) "]
|
|
1632
1604
|
|
|
1633
1605
|
async def partial_liquidate(self, usd_value: float) -> StatusTuple:
|
|
1634
|
-
# Refresh tracked balances
|
|
1635
1606
|
await self._refresh_tracked_balances()
|
|
1636
1607
|
|
|
1637
1608
|
usdc_token_id = self.usdc_token_info.get("token_id")
|
|
@@ -1641,12 +1612,10 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1641
1612
|
available_usdc_wei = self.tracked_balances.get(usdc_token_id, 0)
|
|
1642
1613
|
available_usdc_usd = float(available_usdc_wei) / (10**usdc_decimals)
|
|
1643
1614
|
|
|
1644
|
-
# Liquidate non-USDC, non-gas, non-current-pool tokens first
|
|
1645
1615
|
for token_id, balance_wei in list(self.tracked_balances.items()):
|
|
1646
1616
|
if available_usdc_usd >= usd_value:
|
|
1647
1617
|
break
|
|
1648
1618
|
|
|
1649
|
-
# Skip USDC, gas, and current pool
|
|
1650
1619
|
if token_id == usdc_token_id:
|
|
1651
1620
|
continue
|
|
1652
1621
|
if token_id == gas_token_id:
|
|
@@ -1654,7 +1623,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1654
1623
|
if self.current_pool and token_id == self.current_pool.get("token_id"):
|
|
1655
1624
|
continue
|
|
1656
1625
|
|
|
1657
|
-
# Skip zero balances
|
|
1658
1626
|
if balance_wei <= 0:
|
|
1659
1627
|
continue
|
|
1660
1628
|
|
|
@@ -1706,7 +1674,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1706
1674
|
available_usdc_usd = float(available_usdc_wei) / (10**usdc_decimals)
|
|
1707
1675
|
self._update_balance(usdc_token_id, available_usdc_wei)
|
|
1708
1676
|
|
|
1709
|
-
# If still not enough, liquidate from current pool
|
|
1710
1677
|
if (
|
|
1711
1678
|
available_usdc_usd < usd_value
|
|
1712
1679
|
and self.current_pool
|
|
@@ -1741,7 +1708,6 @@ class StablecoinYieldStrategy(Strategy):
|
|
|
1741
1708
|
except Exception as e:
|
|
1742
1709
|
logger.error(f"Error swapping pool to USDC: {e}")
|
|
1743
1710
|
|
|
1744
|
-
# Refresh USDC balance again
|
|
1745
1711
|
success, usdc_wei = await self.balance_adapter.get_balance(
|
|
1746
1712
|
token_id=usdc_token_id,
|
|
1747
1713
|
wallet_address=self._get_strategy_wallet_address(),
|
|
@@ -1,36 +1,17 @@
|
|
|
1
|
-
import sys
|
|
2
1
|
from pathlib import Path
|
|
3
2
|
from unittest.mock import AsyncMock
|
|
4
3
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
_wayfinder_path_str = str(_wayfinder_path_dir)
|
|
9
|
-
if _wayfinder_path_str not in sys.path:
|
|
10
|
-
sys.path.insert(0, _wayfinder_path_str)
|
|
11
|
-
elif sys.path.index(_wayfinder_path_str) > 0:
|
|
12
|
-
# Move to front to take precedence
|
|
13
|
-
sys.path.remove(_wayfinder_path_str)
|
|
14
|
-
sys.path.insert(0, _wayfinder_path_str)
|
|
15
|
-
|
|
16
|
-
import pytest # noqa: E402
|
|
17
|
-
|
|
18
|
-
try:
|
|
19
|
-
from tests.test_utils import get_canonical_examples, load_strategy_examples
|
|
20
|
-
except ImportError:
|
|
21
|
-
# Fallback if path setup didn't work
|
|
22
|
-
import importlib.util
|
|
23
|
-
|
|
24
|
-
test_utils_path = Path(_wayfinder_path_dir) / "tests" / "test_utils.py"
|
|
25
|
-
spec = importlib.util.spec_from_file_location("tests.test_utils", test_utils_path)
|
|
26
|
-
test_utils = importlib.util.module_from_spec(spec)
|
|
27
|
-
spec.loader.exec_module(test_utils)
|
|
28
|
-
get_canonical_examples = test_utils.get_canonical_examples
|
|
29
|
-
load_strategy_examples = test_utils.load_strategy_examples
|
|
30
|
-
|
|
31
|
-
from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import ( # noqa: E402
|
|
4
|
+
import pytest
|
|
5
|
+
|
|
6
|
+
from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import (
|
|
32
7
|
StablecoinYieldStrategy,
|
|
33
8
|
)
|
|
9
|
+
from wayfinder_paths.tests.test_utils import (
|
|
10
|
+
get_canonical_examples,
|
|
11
|
+
load_strategy_examples,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
_StablecoinYieldStrategy = StablecoinYieldStrategy
|
|
34
15
|
|
|
35
16
|
|
|
36
17
|
@pytest.fixture
|
|
@@ -395,13 +376,9 @@ async def test_deposit_tracks_usdc(strategy):
|
|
|
395
376
|
|
|
396
377
|
@pytest.mark.asyncio
|
|
397
378
|
async def test_sweep_wallet_uses_tracked_tokens(strategy):
|
|
398
|
-
from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import (
|
|
399
|
-
StablecoinYieldStrategy,
|
|
400
|
-
)
|
|
401
|
-
|
|
402
379
|
# Restore the real _sweep_wallet method (fixture mocks it as a no-op)
|
|
403
|
-
strategy._sweep_wallet =
|
|
404
|
-
strategy,
|
|
380
|
+
strategy._sweep_wallet = _StablecoinYieldStrategy._sweep_wallet.__get__(
|
|
381
|
+
strategy, _StablecoinYieldStrategy
|
|
405
382
|
)
|
|
406
383
|
|
|
407
384
|
strategy._track_token("token-1", 1000000)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from unittest.mock import AsyncMock, patch
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from wayfinder_paths.mcp.tools.quotes import quote_swap
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.mark.asyncio
|
|
11
|
+
async def test_quote_swap_returns_compact_best_quote_by_default():
|
|
12
|
+
fake_wallet = {"address": "0x000000000000000000000000000000000000dEaD"}
|
|
13
|
+
|
|
14
|
+
class FakeTokenClient:
|
|
15
|
+
async def get_gas_token(self, chain_code: str):
|
|
16
|
+
assert chain_code == "arbitrum"
|
|
17
|
+
return {
|
|
18
|
+
"token_id": "ethereum-arbitrum",
|
|
19
|
+
"asset_id": "ethereum",
|
|
20
|
+
"symbol": "ETH",
|
|
21
|
+
"decimals": 18,
|
|
22
|
+
"chain_id": 42161,
|
|
23
|
+
"address": "0x0000000000000000000000000000000000000000",
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async def get_token_details(self, query: str):
|
|
27
|
+
assert query == "usd-coin-arbitrum"
|
|
28
|
+
return {
|
|
29
|
+
"token_id": "usd-coin-arbitrum",
|
|
30
|
+
"asset_id": "usd-coin",
|
|
31
|
+
"symbol": "USDC",
|
|
32
|
+
"decimals": 6,
|
|
33
|
+
"chain_id": 42161,
|
|
34
|
+
"address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class FakeBRAPClient:
|
|
38
|
+
async def get_quote(self, **kwargs): # noqa: ANN003
|
|
39
|
+
assert kwargs["from_chain_id"] == 42161
|
|
40
|
+
assert kwargs["to_chain_id"] == 42161
|
|
41
|
+
assert kwargs["slippage"] == pytest.approx(0.005)
|
|
42
|
+
|
|
43
|
+
calldata = "0x" + ("ab" * 4096)
|
|
44
|
+
return {
|
|
45
|
+
"quotes": {
|
|
46
|
+
"quote_count": 3,
|
|
47
|
+
"best_quote": {
|
|
48
|
+
"provider": "brap_best",
|
|
49
|
+
"input_amount": kwargs["amount1"],
|
|
50
|
+
"output_amount": "1234567",
|
|
51
|
+
"input_amount_usd": 5.0,
|
|
52
|
+
"output_amount_usd": 4.99,
|
|
53
|
+
"gas_estimate": 210000,
|
|
54
|
+
"fee_estimate": {"total_usd": 0.01},
|
|
55
|
+
"native_input": True,
|
|
56
|
+
"native_output": False,
|
|
57
|
+
"calldata": calldata,
|
|
58
|
+
"wrap_transaction": None,
|
|
59
|
+
"unwrap_transaction": None,
|
|
60
|
+
},
|
|
61
|
+
"all_quotes": [
|
|
62
|
+
{"provider": "brap_best"},
|
|
63
|
+
{"provider": "brap_alt"},
|
|
64
|
+
{"provider": "brap_alt"},
|
|
65
|
+
],
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
with (
|
|
70
|
+
patch(
|
|
71
|
+
"wayfinder_paths.mcp.tools.quotes.find_wallet_by_label",
|
|
72
|
+
return_value=fake_wallet,
|
|
73
|
+
),
|
|
74
|
+
patch(
|
|
75
|
+
"wayfinder_paths.mcp.tools.quotes.TokenClient",
|
|
76
|
+
return_value=FakeTokenClient(),
|
|
77
|
+
),
|
|
78
|
+
patch(
|
|
79
|
+
"wayfinder_paths.mcp.tools.quotes.BRAPClient", return_value=FakeBRAPClient()
|
|
80
|
+
),
|
|
81
|
+
):
|
|
82
|
+
out = await quote_swap(
|
|
83
|
+
wallet_label="main",
|
|
84
|
+
from_token="ethereum-arbitrum",
|
|
85
|
+
to_token="usd-coin-arbitrum",
|
|
86
|
+
amount="0.0017",
|
|
87
|
+
slippage_bps=50,
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
assert out["ok"] is True
|
|
91
|
+
res = out["result"]
|
|
92
|
+
assert "raw" not in res["quote"]
|
|
93
|
+
|
|
94
|
+
best = res["quote"]["best_quote"]
|
|
95
|
+
assert best["provider"] == "brap_best"
|
|
96
|
+
assert best["output_amount"] == "1234567"
|
|
97
|
+
assert best["calldata_len"] > 0
|
|
98
|
+
assert "calldata" not in best
|
|
99
|
+
assert res["quote"]["quote_count"] == 3
|
|
100
|
+
assert res["quote"]["providers"] == ["brap_best", "brap_alt"]
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@pytest.mark.asyncio
|
|
104
|
+
async def test_quote_swap_can_include_calldata_when_requested():
|
|
105
|
+
fake_wallet = {"address": "0x000000000000000000000000000000000000dEaD"}
|
|
106
|
+
token_client = AsyncMock()
|
|
107
|
+
token_client.get_gas_token = AsyncMock(
|
|
108
|
+
return_value={
|
|
109
|
+
"token_id": "ethereum-arbitrum",
|
|
110
|
+
"asset_id": "ethereum",
|
|
111
|
+
"symbol": "ETH",
|
|
112
|
+
"decimals": 18,
|
|
113
|
+
"chain_id": 42161,
|
|
114
|
+
"address": "0x0000000000000000000000000000000000000000",
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
token_client.get_token_details = AsyncMock(
|
|
118
|
+
return_value={
|
|
119
|
+
"token_id": "usd-coin-arbitrum",
|
|
120
|
+
"asset_id": "usd-coin",
|
|
121
|
+
"symbol": "USDC",
|
|
122
|
+
"decimals": 6,
|
|
123
|
+
"chain_id": 42161,
|
|
124
|
+
"address": "0xaf88d065e77c8cc2239327c5edb3a432268e5831",
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
calldata = "0x" + ("cd" * 1024)
|
|
129
|
+
brap_client = AsyncMock()
|
|
130
|
+
brap_client.get_quote = AsyncMock(
|
|
131
|
+
return_value={
|
|
132
|
+
"quotes": {
|
|
133
|
+
"quote_count": 1,
|
|
134
|
+
"best_quote": {
|
|
135
|
+
"provider": "brap_best",
|
|
136
|
+
"output_amount": "1",
|
|
137
|
+
"calldata": calldata,
|
|
138
|
+
},
|
|
139
|
+
"all_quotes": [{"provider": "brap_best"}],
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
with (
|
|
145
|
+
patch(
|
|
146
|
+
"wayfinder_paths.mcp.tools.quotes.find_wallet_by_label",
|
|
147
|
+
return_value=fake_wallet,
|
|
148
|
+
),
|
|
149
|
+
patch(
|
|
150
|
+
"wayfinder_paths.mcp.tools.quotes.TokenClient", return_value=token_client
|
|
151
|
+
),
|
|
152
|
+
patch("wayfinder_paths.mcp.tools.quotes.BRAPClient", return_value=brap_client),
|
|
153
|
+
):
|
|
154
|
+
out = await quote_swap(
|
|
155
|
+
wallet_label="main",
|
|
156
|
+
from_token="ethereum-arbitrum",
|
|
157
|
+
to_token="usd-coin-arbitrum",
|
|
158
|
+
amount="0.0017",
|
|
159
|
+
slippage_bps=50,
|
|
160
|
+
include_calldata=True,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
assert out["ok"] is True
|
|
164
|
+
best = out["result"]["quote"]["best_quote"]
|
|
165
|
+
assert best["calldata"] == calldata
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
from pathlib import Path
|
|
2
3
|
|
|
3
4
|
import pytest
|
|
@@ -105,7 +106,6 @@ def test_strategy_tests_use_examples_json():
|
|
|
105
106
|
if not test_file.exists():
|
|
106
107
|
continue
|
|
107
108
|
|
|
108
|
-
# Read the test file content
|
|
109
109
|
try:
|
|
110
110
|
content = test_file.read_text()
|
|
111
111
|
|
|
@@ -162,8 +162,6 @@ def test_strategy_examples_have_smoke():
|
|
|
162
162
|
if not strategies_dir.exists():
|
|
163
163
|
pytest.skip("Strategies directory not found")
|
|
164
164
|
|
|
165
|
-
import json
|
|
166
|
-
|
|
167
165
|
missing_smoke = []
|
|
168
166
|
|
|
169
167
|
# Find all strategy directories
|
|
@@ -174,7 +172,6 @@ def test_strategy_examples_have_smoke():
|
|
|
174
172
|
strategy_py = strategy_dir / "strategy.py"
|
|
175
173
|
examples_json = strategy_dir / "examples.json"
|
|
176
174
|
|
|
177
|
-
# Only check strategies that exist
|
|
178
175
|
if not strategy_py.exists():
|
|
179
176
|
continue
|
|
180
177
|
|