wayfinder-paths 0.1.23__py3-none-any.whl → 0.1.24__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 (122) hide show
  1. wayfinder_paths/adapters/balance_adapter/adapter.py +250 -0
  2. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -11
  4. wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
  5. wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
  6. wayfinder_paths/adapters/boros_adapter/client.py +476 -0
  7. wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
  8. wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
  9. wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
  10. wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
  11. wayfinder_paths/adapters/boros_adapter/types.py +70 -0
  12. wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
  13. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -1
  14. wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
  15. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +161 -26
  16. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
  17. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +77 -13
  18. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
  19. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +585 -61
  20. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
  21. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
  22. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
  23. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
  24. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
  27. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
  28. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
  29. wayfinder_paths/adapters/moonwell_adapter/adapter.py +592 -400
  30. wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
  31. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +126 -219
  32. wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
  33. wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
  34. wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
  35. wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
  36. wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
  37. wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
  38. wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
  39. wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
  40. wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
  41. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
  42. wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
  43. wayfinder_paths/adapters/token_adapter/examples.json +0 -4
  44. wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
  45. wayfinder_paths/conftest.py +24 -17
  46. wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
  47. wayfinder_paths/core/adapters/models.py +17 -7
  48. wayfinder_paths/core/clients/BRAPClient.py +1 -1
  49. wayfinder_paths/core/clients/TokenClient.py +47 -1
  50. wayfinder_paths/core/clients/WayfinderClient.py +1 -2
  51. wayfinder_paths/core/clients/protocols.py +21 -22
  52. wayfinder_paths/core/clients/test_ledger_client.py +448 -0
  53. wayfinder_paths/core/config.py +12 -0
  54. wayfinder_paths/core/constants/__init__.py +15 -0
  55. wayfinder_paths/core/constants/base.py +6 -1
  56. wayfinder_paths/core/constants/contracts.py +39 -26
  57. wayfinder_paths/core/constants/erc20_abi.py +0 -1
  58. wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
  59. wayfinder_paths/core/constants/hyperliquid.py +16 -0
  60. wayfinder_paths/core/constants/moonwell_abi.py +0 -15
  61. wayfinder_paths/core/engine/manifest.py +66 -0
  62. wayfinder_paths/core/strategies/Strategy.py +0 -61
  63. wayfinder_paths/core/strategies/__init__.py +10 -1
  64. wayfinder_paths/core/strategies/opa_loop.py +167 -0
  65. wayfinder_paths/core/utils/test_transaction.py +289 -0
  66. wayfinder_paths/core/utils/transaction.py +44 -1
  67. wayfinder_paths/core/utils/web3.py +3 -0
  68. wayfinder_paths/mcp/__init__.py +5 -0
  69. wayfinder_paths/mcp/preview.py +185 -0
  70. wayfinder_paths/mcp/scripting.py +84 -0
  71. wayfinder_paths/mcp/server.py +52 -0
  72. wayfinder_paths/mcp/state/profile_store.py +195 -0
  73. wayfinder_paths/mcp/state/store.py +89 -0
  74. wayfinder_paths/mcp/test_scripting.py +267 -0
  75. wayfinder_paths/mcp/tools/__init__.py +0 -0
  76. wayfinder_paths/mcp/tools/balances.py +290 -0
  77. wayfinder_paths/mcp/tools/discovery.py +158 -0
  78. wayfinder_paths/mcp/tools/execute.py +770 -0
  79. wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
  80. wayfinder_paths/mcp/tools/quotes.py +288 -0
  81. wayfinder_paths/mcp/tools/run_script.py +286 -0
  82. wayfinder_paths/mcp/tools/strategies.py +188 -0
  83. wayfinder_paths/mcp/tools/tokens.py +46 -0
  84. wayfinder_paths/mcp/tools/wallets.py +354 -0
  85. wayfinder_paths/mcp/utils.py +129 -0
  86. wayfinder_paths/policies/hyperliquid.py +1 -1
  87. wayfinder_paths/policies/lifi.py +18 -0
  88. wayfinder_paths/policies/util.py +8 -2
  89. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +28 -119
  90. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
  91. wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
  92. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
  93. wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
  94. wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
  95. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
  96. wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
  97. wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
  98. wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
  99. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
  100. wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
  101. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
  102. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
  103. wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +202 -0
  104. wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
  105. wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
  106. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +3 -12
  107. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +7 -29
  108. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +63 -40
  109. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
  110. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -34
  111. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +11 -34
  112. wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
  113. wayfinder_paths/tests/test_test_coverage.py +1 -4
  114. wayfinder_paths-0.1.24.dist-info/METADATA +378 -0
  115. wayfinder_paths-0.1.24.dist-info/RECORD +185 -0
  116. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.24.dist-info}/WHEEL +1 -1
  117. wayfinder_paths/scripts/create_strategy.py +0 -139
  118. wayfinder_paths/scripts/make_wallets.py +0 -142
  119. wayfinder_paths-0.1.23.dist-info/METADATA +0 -354
  120. wayfinder_paths-0.1.23.dist-info/RECORD +0 -120
  121. /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
  122. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.24.dist-info}/LICENSE +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
- # Ensure wayfinder-paths is on path for tests.test_utils import
6
- # This is a workaround until conftest loading order is resolved
7
- _wayfinder_path_dir = Path(__file__).parent.parent.parent.resolve()
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 = StablecoinYieldStrategy._sweep_wallet.__get__(
404
- strategy, StablecoinYieldStrategy
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