wayfinder-paths 0.1.22__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 (156) hide show
  1. wayfinder_paths/__init__.py +0 -4
  2. wayfinder_paths/adapters/balance_adapter/README.md +0 -1
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +313 -167
  4. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  5. wayfinder_paths/adapters/balance_adapter/test_adapter.py +41 -124
  6. wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
  7. wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
  8. wayfinder_paths/adapters/boros_adapter/client.py +476 -0
  9. wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
  10. wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
  11. wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
  12. wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
  13. wayfinder_paths/adapters/boros_adapter/types.py +70 -0
  14. wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
  15. wayfinder_paths/adapters/brap_adapter/README.md +22 -75
  16. wayfinder_paths/adapters/brap_adapter/adapter.py +187 -576
  17. wayfinder_paths/adapters/brap_adapter/examples.json +21 -140
  18. wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
  19. wayfinder_paths/adapters/brap_adapter/test_adapter.py +6 -234
  20. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +180 -92
  21. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
  22. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +82 -14
  23. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
  24. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +586 -61
  25. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
  26. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
  27. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
  28. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
  29. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
  30. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
  31. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
  32. wayfinder_paths/adapters/ledger_adapter/README.md +4 -1
  33. wayfinder_paths/adapters/ledger_adapter/adapter.py +3 -3
  34. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
  35. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
  36. wayfinder_paths/adapters/moonwell_adapter/adapter.py +649 -547
  37. wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
  38. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +160 -239
  39. wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
  40. wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
  41. wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
  42. wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
  43. wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
  44. wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
  45. wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
  46. wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
  47. wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
  48. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
  49. wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
  50. wayfinder_paths/adapters/token_adapter/adapter.py +14 -0
  51. wayfinder_paths/adapters/token_adapter/examples.json +0 -4
  52. wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
  53. wayfinder_paths/conftest.py +24 -17
  54. wayfinder_paths/core/__init__.py +0 -3
  55. wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
  56. wayfinder_paths/core/adapters/models.py +17 -7
  57. wayfinder_paths/core/clients/BRAPClient.py +4 -1
  58. wayfinder_paths/core/clients/ClientManager.py +0 -7
  59. wayfinder_paths/core/clients/LedgerClient.py +196 -172
  60. wayfinder_paths/core/clients/TokenClient.py +47 -1
  61. wayfinder_paths/core/clients/WayfinderClient.py +1 -3
  62. wayfinder_paths/core/clients/__init__.py +0 -5
  63. wayfinder_paths/core/clients/protocols.py +21 -35
  64. wayfinder_paths/core/clients/test_ledger_client.py +448 -0
  65. wayfinder_paths/core/config.py +10 -162
  66. wayfinder_paths/core/constants/__init__.py +73 -2
  67. wayfinder_paths/core/constants/base.py +8 -17
  68. wayfinder_paths/core/constants/chains.py +36 -0
  69. wayfinder_paths/core/constants/contracts.py +52 -0
  70. wayfinder_paths/core/constants/erc20_abi.py +0 -1
  71. wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
  72. wayfinder_paths/core/constants/hyperliquid.py +16 -0
  73. wayfinder_paths/core/constants/moonwell_abi.py +0 -15
  74. wayfinder_paths/core/constants/tokens.py +9 -0
  75. wayfinder_paths/core/engine/manifest.py +66 -0
  76. wayfinder_paths/core/strategies/Strategy.py +0 -71
  77. wayfinder_paths/core/strategies/__init__.py +10 -1
  78. wayfinder_paths/core/strategies/opa_loop.py +167 -0
  79. wayfinder_paths/core/utils/evm_helpers.py +5 -15
  80. wayfinder_paths/core/utils/test_transaction.py +289 -0
  81. wayfinder_paths/core/utils/tokens.py +28 -0
  82. wayfinder_paths/core/utils/transaction.py +57 -8
  83. wayfinder_paths/core/utils/web3.py +8 -3
  84. wayfinder_paths/mcp/__init__.py +5 -0
  85. wayfinder_paths/mcp/preview.py +185 -0
  86. wayfinder_paths/mcp/scripting.py +84 -0
  87. wayfinder_paths/mcp/server.py +52 -0
  88. wayfinder_paths/mcp/state/profile_store.py +195 -0
  89. wayfinder_paths/mcp/state/store.py +89 -0
  90. wayfinder_paths/mcp/test_scripting.py +267 -0
  91. wayfinder_paths/mcp/tools/__init__.py +0 -0
  92. wayfinder_paths/mcp/tools/balances.py +290 -0
  93. wayfinder_paths/mcp/tools/discovery.py +158 -0
  94. wayfinder_paths/mcp/tools/execute.py +770 -0
  95. wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
  96. wayfinder_paths/mcp/tools/quotes.py +288 -0
  97. wayfinder_paths/mcp/tools/run_script.py +286 -0
  98. wayfinder_paths/mcp/tools/strategies.py +188 -0
  99. wayfinder_paths/mcp/tools/tokens.py +46 -0
  100. wayfinder_paths/mcp/tools/wallets.py +354 -0
  101. wayfinder_paths/mcp/utils.py +129 -0
  102. wayfinder_paths/policies/enso.py +1 -2
  103. wayfinder_paths/policies/hyper_evm.py +6 -3
  104. wayfinder_paths/policies/hyperlend.py +1 -2
  105. wayfinder_paths/policies/hyperliquid.py +1 -1
  106. wayfinder_paths/policies/lifi.py +18 -0
  107. wayfinder_paths/policies/moonwell.py +12 -7
  108. wayfinder_paths/policies/prjx.py +1 -3
  109. wayfinder_paths/policies/util.py +8 -2
  110. wayfinder_paths/run_strategy.py +97 -300
  111. wayfinder_paths/strategies/basis_trading_strategy/constants.py +3 -1
  112. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +47 -133
  113. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
  114. wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
  115. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
  116. wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
  117. wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
  118. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
  119. wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
  120. wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
  121. wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
  122. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
  123. wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
  124. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
  125. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
  126. wayfinder_paths/{templates/strategy → strategies/boros_hype_strategy}/test_strategy.py +99 -63
  127. wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
  128. wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
  129. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +15 -23
  130. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +27 -62
  131. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +84 -58
  132. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
  133. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +69 -164
  134. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +43 -76
  135. wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
  136. wayfinder_paths/tests/test_test_coverage.py +1 -4
  137. wayfinder_paths-0.1.24.dist-info/METADATA +378 -0
  138. wayfinder_paths-0.1.24.dist-info/RECORD +185 -0
  139. {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.24.dist-info}/WHEEL +1 -1
  140. wayfinder_paths/core/clients/WalletClient.py +0 -41
  141. wayfinder_paths/core/engine/StrategyJob.py +0 -110
  142. wayfinder_paths/core/services/test_local_evm_txn.py +0 -145
  143. wayfinder_paths/scripts/create_strategy.py +0 -139
  144. wayfinder_paths/scripts/make_wallets.py +0 -142
  145. wayfinder_paths/templates/adapter/README.md +0 -150
  146. wayfinder_paths/templates/adapter/adapter.py +0 -16
  147. wayfinder_paths/templates/adapter/examples.json +0 -8
  148. wayfinder_paths/templates/adapter/test_adapter.py +0 -30
  149. wayfinder_paths/templates/strategy/README.md +0 -186
  150. wayfinder_paths/templates/strategy/examples.json +0 -11
  151. wayfinder_paths/templates/strategy/strategy.py +0 -35
  152. wayfinder_paths/tests/test_smoke_manifest.py +0 -63
  153. wayfinder_paths-0.1.22.dist-info/METADATA +0 -355
  154. wayfinder_paths-0.1.22.dist-info/RECORD +0 -129
  155. /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
  156. {wayfinder_paths-0.1.22.dist-info → wayfinder_paths-0.1.24.dist-info}/LICENSE +0 -0
@@ -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
@@ -47,17 +28,14 @@ def strategy():
47
28
  )
48
29
 
49
30
  if hasattr(s, "balance_adapter") and s.balance_adapter:
50
- usdc_balance_mock = AsyncMock(return_value=(True, 60000000))
51
- gas_balance_mock = AsyncMock(return_value=(True, 2000000000000000))
52
31
 
53
- def get_balance_side_effect(query, wallet_address, **kwargs):
54
- token_id = (
55
- query if isinstance(query, str) else (query or {}).get("token_id")
56
- )
32
+ def get_balance_side_effect(
33
+ *, wallet_address, token_id=None, token_address=None, chain_id=None
34
+ ):
57
35
  if token_id == "usd-coin-base" or token_id == "usd-coin":
58
- return usdc_balance_mock.return_value
36
+ return (True, 60000000)
59
37
  elif token_id == "ethereum-base" or token_id == "ethereum":
60
- return gas_balance_mock.return_value
38
+ return (True, 2000000000000000)
61
39
  return (True, 1000000000)
62
40
 
63
41
  s.balance_adapter.get_balance = AsyncMock(side_effect=get_balance_side_effect)
@@ -111,10 +89,10 @@ def strategy():
111
89
 
112
90
  if hasattr(s, "balance_adapter") and s.balance_adapter:
113
91
  s.balance_adapter.move_from_main_wallet_to_strategy_wallet = AsyncMock(
114
- return_value=(True, "Transfer successful (simulated)")
92
+ return_value=(True, "0xtxhash_transfer")
115
93
  )
116
94
  s.balance_adapter.move_from_strategy_wallet_to_main_wallet = AsyncMock(
117
- return_value=(True, "Transfer successful (simulated)")
95
+ return_value=(True, "0xtxhash_transfer")
118
96
  )
119
97
 
120
98
  if hasattr(s, "ledger_adapter") and s.ledger_adapter:
@@ -154,46 +132,36 @@ def strategy():
154
132
 
155
133
  if hasattr(s, "brap_adapter") and s.brap_adapter:
156
134
 
157
- def get_swap_quote_side_effect(*args, **kwargs):
135
+ def best_quote_side_effect(*args, **kwargs):
158
136
  to_token_address = kwargs.get("to_token_address", "")
159
137
  if to_token_address == "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913":
160
- return (
161
- True,
162
- {
163
- "quotes": {
164
- "best_quote": {
165
- "output_amount": "99900000",
166
- }
167
- }
168
- },
169
- )
138
+ return (True, {"output_amount": "99900000"})
170
139
  return (
171
140
  True,
172
141
  {
173
- "quotes": {
174
- "best_quote": {
175
- "output_amount": "105000000",
176
- "input_amount": "50000000000000",
177
- "toAmount": "105000000",
178
- "estimatedGas": "1000000000",
179
- "fromAmount": "100000000",
180
- "fromToken": {"symbol": "USDC"},
181
- "toToken": {"symbol": "POOL"},
182
- }
183
- }
142
+ "output_amount": "105000000",
143
+ "input_amount": "50000000000000",
144
+ "toAmount": "105000000",
145
+ "estimatedGas": "1000000000",
146
+ "fromAmount": "100000000",
147
+ "fromToken": {"symbol": "USDC"},
148
+ "toToken": {"symbol": "POOL"},
184
149
  },
185
150
  )
186
151
 
187
- s.brap_adapter.get_swap_quote = AsyncMock(
188
- side_effect=get_swap_quote_side_effect
189
- )
152
+ s.brap_adapter.best_quote = AsyncMock(side_effect=best_quote_side_effect)
190
153
 
191
154
  if (
192
155
  hasattr(s, "brap_adapter")
193
156
  and s.brap_adapter
194
157
  and hasattr(s.brap_adapter, "swap_from_quote")
195
158
  ):
196
- s.brap_adapter.swap_from_quote = AsyncMock(return_value=None)
159
+ s.brap_adapter.swap_from_quote = AsyncMock(
160
+ return_value=(
161
+ True,
162
+ {"tx_hash": "0xmockhash", "from_amount": "100", "to_amount": "99"},
163
+ )
164
+ )
197
165
 
198
166
  s.DEPOSIT_USDC = 0
199
167
  s.usdc_token_info = {
@@ -408,13 +376,9 @@ async def test_deposit_tracks_usdc(strategy):
408
376
 
409
377
  @pytest.mark.asyncio
410
378
  async def test_sweep_wallet_uses_tracked_tokens(strategy):
411
- from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import (
412
- StablecoinYieldStrategy,
413
- )
414
-
415
379
  # Restore the real _sweep_wallet method (fixture mocks it as a no-op)
416
- strategy._sweep_wallet = StablecoinYieldStrategy._sweep_wallet.__get__(
417
- strategy, StablecoinYieldStrategy
380
+ strategy._sweep_wallet = _StablecoinYieldStrategy._sweep_wallet.__get__(
381
+ strategy, _StablecoinYieldStrategy
418
382
  )
419
383
 
420
384
  strategy._track_token("token-1", 1000000)
@@ -426,8 +390,9 @@ async def test_sweep_wallet_uses_tracked_tokens(strategy):
426
390
  assert "token-2" in strategy.tracked_token_ids
427
391
 
428
392
  # Mock balance adapter to return fresh balances
429
- async def get_balance_mock(query, **kwargs):
430
- token_id = query if isinstance(query, str) else (query or {}).get("token_id")
393
+ def get_balance_mock(
394
+ *, wallet_address, token_id=None, token_address=None, chain_id=None
395
+ ):
431
396
  balance = strategy.tracked_balances.get(token_id, 0)
432
397
  return (True, int(balance) if balance else 0)
433
398
 
@@ -468,8 +433,9 @@ async def test_get_non_gas_balances_uses_tracked_state(strategy):
468
433
  strategy._track_token(pool_token_id, 50000000000000000000)
469
434
 
470
435
  # Mock refresh
471
- def _get_balance_effect(query, **kwargs):
472
- token_id = query if isinstance(query, str) else (query or {}).get("token_id")
436
+ def _get_balance_effect(
437
+ *, wallet_address, token_id=None, token_address=None, chain_id=None
438
+ ):
473
439
  return (True, strategy.tracked_balances.get(token_id, 0))
474
440
 
475
441
  strategy.balance_adapter.get_balance = AsyncMock(side_effect=_get_balance_effect)
@@ -488,8 +454,9 @@ async def test_partial_liquidate_uses_tracked_tokens(strategy):
488
454
  strategy._track_token("test-pool-base", 100000000000000000000)
489
455
 
490
456
  # Mock balance and token adapters
491
- def _get_balance_effect_partial(query, **kwargs):
492
- token_id = query if isinstance(query, str) else (query or {}).get("token_id")
457
+ def _get_balance_effect_partial(
458
+ *, wallet_address, token_id=None, token_address=None, chain_id=None
459
+ ):
493
460
  return (True, strategy.tracked_balances.get(token_id, 0))
494
461
 
495
462
  strategy.balance_adapter.get_balance = AsyncMock(
@@ -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