wayfinder-paths 0.1.7__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 (149) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +399 -0
  2. wayfinder_paths/__init__.py +22 -0
  3. wayfinder_paths/abis/generic/erc20.json +383 -0
  4. wayfinder_paths/adapters/__init__.py +0 -0
  5. wayfinder_paths/adapters/balance_adapter/README.md +94 -0
  6. wayfinder_paths/adapters/balance_adapter/adapter.py +238 -0
  7. wayfinder_paths/adapters/balance_adapter/examples.json +6 -0
  8. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  9. wayfinder_paths/adapters/balance_adapter/test_adapter.py +59 -0
  10. wayfinder_paths/adapters/brap_adapter/README.md +249 -0
  11. wayfinder_paths/adapters/brap_adapter/__init__.py +7 -0
  12. wayfinder_paths/adapters/brap_adapter/adapter.py +726 -0
  13. wayfinder_paths/adapters/brap_adapter/examples.json +175 -0
  14. wayfinder_paths/adapters/brap_adapter/manifest.yaml +11 -0
  15. wayfinder_paths/adapters/brap_adapter/test_adapter.py +286 -0
  16. wayfinder_paths/adapters/hyperlend_adapter/__init__.py +7 -0
  17. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +305 -0
  18. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +10 -0
  19. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +274 -0
  20. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +18 -0
  21. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +1093 -0
  22. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +549 -0
  23. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +8 -0
  24. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +1050 -0
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +126 -0
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +219 -0
  27. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +220 -0
  28. wayfinder_paths/adapters/hyperliquid_adapter/utils.py +134 -0
  29. wayfinder_paths/adapters/ledger_adapter/README.md +145 -0
  30. wayfinder_paths/adapters/ledger_adapter/__init__.py +7 -0
  31. wayfinder_paths/adapters/ledger_adapter/adapter.py +289 -0
  32. wayfinder_paths/adapters/ledger_adapter/examples.json +137 -0
  33. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +11 -0
  34. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +205 -0
  35. wayfinder_paths/adapters/pool_adapter/README.md +206 -0
  36. wayfinder_paths/adapters/pool_adapter/__init__.py +7 -0
  37. wayfinder_paths/adapters/pool_adapter/adapter.py +282 -0
  38. wayfinder_paths/adapters/pool_adapter/examples.json +143 -0
  39. wayfinder_paths/adapters/pool_adapter/manifest.yaml +10 -0
  40. wayfinder_paths/adapters/pool_adapter/test_adapter.py +220 -0
  41. wayfinder_paths/adapters/token_adapter/README.md +101 -0
  42. wayfinder_paths/adapters/token_adapter/__init__.py +3 -0
  43. wayfinder_paths/adapters/token_adapter/adapter.py +96 -0
  44. wayfinder_paths/adapters/token_adapter/examples.json +26 -0
  45. wayfinder_paths/adapters/token_adapter/manifest.yaml +6 -0
  46. wayfinder_paths/adapters/token_adapter/test_adapter.py +125 -0
  47. wayfinder_paths/config.example.json +22 -0
  48. wayfinder_paths/conftest.py +31 -0
  49. wayfinder_paths/core/__init__.py +18 -0
  50. wayfinder_paths/core/adapters/BaseAdapter.py +65 -0
  51. wayfinder_paths/core/adapters/__init__.py +5 -0
  52. wayfinder_paths/core/adapters/base.py +5 -0
  53. wayfinder_paths/core/adapters/models.py +46 -0
  54. wayfinder_paths/core/analytics/__init__.py +11 -0
  55. wayfinder_paths/core/analytics/bootstrap.py +57 -0
  56. wayfinder_paths/core/analytics/stats.py +48 -0
  57. wayfinder_paths/core/analytics/test_analytics.py +170 -0
  58. wayfinder_paths/core/clients/AuthClient.py +83 -0
  59. wayfinder_paths/core/clients/BRAPClient.py +109 -0
  60. wayfinder_paths/core/clients/ClientManager.py +210 -0
  61. wayfinder_paths/core/clients/HyperlendClient.py +192 -0
  62. wayfinder_paths/core/clients/LedgerClient.py +443 -0
  63. wayfinder_paths/core/clients/PoolClient.py +128 -0
  64. wayfinder_paths/core/clients/SimulationClient.py +192 -0
  65. wayfinder_paths/core/clients/TokenClient.py +89 -0
  66. wayfinder_paths/core/clients/TransactionClient.py +63 -0
  67. wayfinder_paths/core/clients/WalletClient.py +94 -0
  68. wayfinder_paths/core/clients/WayfinderClient.py +269 -0
  69. wayfinder_paths/core/clients/__init__.py +48 -0
  70. wayfinder_paths/core/clients/protocols.py +392 -0
  71. wayfinder_paths/core/clients/sdk_example.py +110 -0
  72. wayfinder_paths/core/config.py +458 -0
  73. wayfinder_paths/core/constants/__init__.py +26 -0
  74. wayfinder_paths/core/constants/base.py +42 -0
  75. wayfinder_paths/core/constants/erc20_abi.py +118 -0
  76. wayfinder_paths/core/constants/hyperlend_abi.py +152 -0
  77. wayfinder_paths/core/engine/StrategyJob.py +188 -0
  78. wayfinder_paths/core/engine/__init__.py +5 -0
  79. wayfinder_paths/core/engine/manifest.py +97 -0
  80. wayfinder_paths/core/services/__init__.py +0 -0
  81. wayfinder_paths/core/services/base.py +179 -0
  82. wayfinder_paths/core/services/local_evm_txn.py +430 -0
  83. wayfinder_paths/core/services/local_token_txn.py +231 -0
  84. wayfinder_paths/core/services/web3_service.py +45 -0
  85. wayfinder_paths/core/settings.py +61 -0
  86. wayfinder_paths/core/strategies/Strategy.py +280 -0
  87. wayfinder_paths/core/strategies/__init__.py +5 -0
  88. wayfinder_paths/core/strategies/base.py +7 -0
  89. wayfinder_paths/core/strategies/descriptors.py +81 -0
  90. wayfinder_paths/core/utils/__init__.py +1 -0
  91. wayfinder_paths/core/utils/evm_helpers.py +206 -0
  92. wayfinder_paths/core/utils/wallets.py +77 -0
  93. wayfinder_paths/core/wallets/README.md +91 -0
  94. wayfinder_paths/core/wallets/WalletManager.py +56 -0
  95. wayfinder_paths/core/wallets/__init__.py +7 -0
  96. wayfinder_paths/policies/enso.py +17 -0
  97. wayfinder_paths/policies/erc20.py +34 -0
  98. wayfinder_paths/policies/evm.py +21 -0
  99. wayfinder_paths/policies/hyper_evm.py +19 -0
  100. wayfinder_paths/policies/hyperlend.py +12 -0
  101. wayfinder_paths/policies/hyperliquid.py +30 -0
  102. wayfinder_paths/policies/moonwell.py +54 -0
  103. wayfinder_paths/policies/prjx.py +30 -0
  104. wayfinder_paths/policies/util.py +27 -0
  105. wayfinder_paths/run_strategy.py +411 -0
  106. wayfinder_paths/scripts/__init__.py +0 -0
  107. wayfinder_paths/scripts/create_strategy.py +181 -0
  108. wayfinder_paths/scripts/make_wallets.py +169 -0
  109. wayfinder_paths/scripts/run_strategy.py +124 -0
  110. wayfinder_paths/scripts/validate_manifests.py +213 -0
  111. wayfinder_paths/strategies/__init__.py +0 -0
  112. wayfinder_paths/strategies/basis_trading_strategy/README.md +213 -0
  113. wayfinder_paths/strategies/basis_trading_strategy/__init__.py +3 -0
  114. wayfinder_paths/strategies/basis_trading_strategy/constants.py +1 -0
  115. wayfinder_paths/strategies/basis_trading_strategy/examples.json +16 -0
  116. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +23 -0
  117. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1011 -0
  118. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +4522 -0
  119. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +727 -0
  120. wayfinder_paths/strategies/basis_trading_strategy/types.py +39 -0
  121. wayfinder_paths/strategies/config.py +85 -0
  122. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +100 -0
  123. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +8 -0
  124. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +7 -0
  125. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +2270 -0
  126. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +352 -0
  127. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +96 -0
  128. wayfinder_paths/strategies/stablecoin_yield_strategy/examples.json +17 -0
  129. wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +17 -0
  130. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +1810 -0
  131. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +520 -0
  132. wayfinder_paths/templates/adapter/README.md +105 -0
  133. wayfinder_paths/templates/adapter/adapter.py +26 -0
  134. wayfinder_paths/templates/adapter/examples.json +8 -0
  135. wayfinder_paths/templates/adapter/manifest.yaml +6 -0
  136. wayfinder_paths/templates/adapter/test_adapter.py +49 -0
  137. wayfinder_paths/templates/strategy/README.md +153 -0
  138. wayfinder_paths/templates/strategy/examples.json +11 -0
  139. wayfinder_paths/templates/strategy/manifest.yaml +8 -0
  140. wayfinder_paths/templates/strategy/strategy.py +57 -0
  141. wayfinder_paths/templates/strategy/test_strategy.py +197 -0
  142. wayfinder_paths/tests/__init__.py +0 -0
  143. wayfinder_paths/tests/test_smoke_manifest.py +48 -0
  144. wayfinder_paths/tests/test_test_coverage.py +212 -0
  145. wayfinder_paths/tests/test_utils.py +64 -0
  146. wayfinder_paths-0.1.7.dist-info/LICENSE +21 -0
  147. wayfinder_paths-0.1.7.dist-info/METADATA +777 -0
  148. wayfinder_paths-0.1.7.dist-info/RECORD +149 -0
  149. wayfinder_paths-0.1.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,238 @@
1
+ from typing import Any
2
+
3
+ from wayfinder_paths.adapters.ledger_adapter.adapter import LedgerAdapter
4
+ from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
5
+ from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
6
+ from wayfinder_paths.core.clients.TokenClient import TokenClient
7
+ from wayfinder_paths.core.clients.WalletClient import WalletClient
8
+ from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
9
+ from wayfinder_paths.core.services.base import Web3Service
10
+ from wayfinder_paths.core.settings import settings
11
+ from wayfinder_paths.core.utils.evm_helpers import resolve_chain_id
12
+
13
+
14
+ class BalanceAdapter(BaseAdapter):
15
+ adapter_type = "BALANCE"
16
+
17
+ def __init__(
18
+ self,
19
+ config: dict[str, Any],
20
+ web3_service: Web3Service,
21
+ ):
22
+ super().__init__("balance", config)
23
+ self.wallet_client = WalletClient()
24
+ self.token_client = TokenClient()
25
+ self.token_adapter = TokenAdapter()
26
+ self.ledger_adapter = LedgerAdapter()
27
+
28
+ self.wallet_provider = web3_service.evm_transactions
29
+ self.token_transactions = web3_service.token_transactions
30
+
31
+ def _parse_balance(self, raw: Any) -> int:
32
+ """Parse balance value to integer, handling various formats."""
33
+ if raw is None:
34
+ return 0
35
+ try:
36
+ return int(raw)
37
+ except (ValueError, TypeError):
38
+ try:
39
+ return int(float(raw))
40
+ except (ValueError, TypeError):
41
+ return 0
42
+
43
+ async def get_balance(
44
+ self,
45
+ *,
46
+ token_id: str,
47
+ wallet_address: str,
48
+ ) -> tuple[bool, str | int]:
49
+ """Get token balance for a wallet."""
50
+ try:
51
+ data = await self.wallet_client.get_token_balance_for_wallet(
52
+ token_id=token_id,
53
+ wallet_address=wallet_address,
54
+ )
55
+ return (True, data.get("balance"))
56
+ except Exception as e:
57
+ return (False, str(e))
58
+
59
+ async def move_from_main_wallet_to_strategy_wallet(
60
+ self,
61
+ token_id: str,
62
+ amount: float,
63
+ strategy_name: str = "unknown",
64
+ skip_ledger: bool = False,
65
+ ) -> tuple[bool, Any]:
66
+ """Move funds from the configured main wallet into the strategy wallet."""
67
+ return await self._move_between_wallets(
68
+ token_id=token_id,
69
+ amount=amount,
70
+ from_wallet=self.config.get("main_wallet"),
71
+ to_wallet=self.config.get("strategy_wallet"),
72
+ ledger_method=self.ledger_adapter.record_deposit,
73
+ ledger_wallet="to",
74
+ strategy_name=strategy_name,
75
+ skip_ledger=skip_ledger,
76
+ )
77
+
78
+ async def move_from_strategy_wallet_to_main_wallet(
79
+ self,
80
+ token_id: str,
81
+ amount: float,
82
+ strategy_name: str = "unknown",
83
+ skip_ledger: bool = False,
84
+ ) -> tuple[bool, Any]:
85
+ """Move funds from the strategy wallet back into the main wallet."""
86
+ return await self._move_between_wallets(
87
+ token_id=token_id,
88
+ amount=amount,
89
+ from_wallet=self.config.get("strategy_wallet"),
90
+ to_wallet=self.config.get("main_wallet"),
91
+ ledger_method=self.ledger_adapter.record_withdrawal,
92
+ ledger_wallet="from",
93
+ strategy_name=strategy_name,
94
+ skip_ledger=skip_ledger,
95
+ )
96
+
97
+ async def _move_between_wallets(
98
+ self,
99
+ *,
100
+ token_id: str,
101
+ amount: float,
102
+ from_wallet: dict[str, Any] | None,
103
+ to_wallet: dict[str, Any] | None,
104
+ ledger_method,
105
+ ledger_wallet: str,
106
+ strategy_name: str,
107
+ skip_ledger: bool,
108
+ ) -> tuple[bool, Any]:
109
+ if self.token_transactions is None:
110
+ return False, "Token transaction service not configured"
111
+
112
+ from_address = self._wallet_address(from_wallet)
113
+ to_address = self._wallet_address(to_wallet)
114
+ if not from_address or not to_address:
115
+ return False, "main_wallet or strategy_wallet missing"
116
+
117
+ token_info = await self.token_client.get_token_details(token_id)
118
+ if not token_info:
119
+ return False, f"Token not found: {token_id}"
120
+
121
+ build_success, tx_data = await self.token_transactions.build_send(
122
+ token_id=token_id,
123
+ amount=amount,
124
+ from_address=from_address,
125
+ to_address=to_address,
126
+ token_info=token_info,
127
+ )
128
+ if not build_success:
129
+ return False, tx_data
130
+
131
+ tx = tx_data
132
+ if getattr(settings, "DRY_RUN", False):
133
+ broadcast_result = (True, {"dry_run": True, "transaction": tx})
134
+ else:
135
+ broadcast_result = await self.wallet_provider.broadcast_transaction(
136
+ tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
137
+ )
138
+
139
+ if broadcast_result[0] and not skip_ledger and ledger_method is not None:
140
+ wallet_for_ledger = from_address if ledger_wallet == "from" else to_address
141
+ await self._record_ledger_entry(
142
+ ledger_method=ledger_method,
143
+ wallet_address=wallet_for_ledger,
144
+ token_info=token_info,
145
+ amount=amount,
146
+ strategy_name=strategy_name,
147
+ )
148
+
149
+ return broadcast_result
150
+
151
+ async def _record_ledger_entry(
152
+ self,
153
+ *,
154
+ ledger_method,
155
+ wallet_address: str,
156
+ token_info: dict[str, Any],
157
+ amount: float,
158
+ strategy_name: str,
159
+ ) -> None:
160
+ chain_id = resolve_chain_id(token_info, self.logger)
161
+ if chain_id is None:
162
+ return
163
+
164
+ usd_value = await self._token_amount_usd(token_info, amount)
165
+ try:
166
+ success, response = await ledger_method(
167
+ wallet_address=wallet_address,
168
+ chain_id=chain_id,
169
+ token_address=token_info.get("address"),
170
+ token_amount=str(amount),
171
+ usd_value=usd_value,
172
+ data={
173
+ "token_id": token_info.get("id"),
174
+ "amount": str(amount),
175
+ "usd_value": usd_value,
176
+ },
177
+ strategy_name=strategy_name,
178
+ )
179
+ if not success:
180
+ self.logger.warning(
181
+ "Ledger entry failed",
182
+ wallet=wallet_address,
183
+ token_id=token_info.get("id"),
184
+ amount=amount,
185
+ error=response,
186
+ )
187
+ except Exception as exc: # noqa: BLE001
188
+ self.logger.warning(
189
+ f"Ledger entry raised: {exc}",
190
+ wallet=wallet_address,
191
+ token_id=token_info.get("id"),
192
+ )
193
+
194
+ async def _token_amount_usd(
195
+ self, token_info: dict[str, Any], amount: float
196
+ ) -> float:
197
+ token_id = token_info.get("id")
198
+ if not token_id:
199
+ return 0.0
200
+ success, price_data = await self.token_adapter.get_token_price(token_id)
201
+ if not success or not price_data:
202
+ return 0.0
203
+ return float(price_data.get("current_price", 0.0)) * float(amount)
204
+
205
+ def _wallet_address(self, wallet: dict[str, Any] | None) -> str | None:
206
+ if not wallet:
207
+ return None
208
+ address = wallet.get("address")
209
+ if address:
210
+ return str(address)
211
+ evm_wallet = wallet.get("evm") if isinstance(wallet, dict) else None
212
+ if isinstance(evm_wallet, dict):
213
+ return evm_wallet.get("address")
214
+ return None
215
+
216
+ async def get_pool_balance(
217
+ self,
218
+ *,
219
+ pool_address: str,
220
+ chain_id: int,
221
+ user_address: str,
222
+ ) -> tuple[bool, Any]:
223
+ """Get pool balance for a wallet."""
224
+ try:
225
+ data = await self.wallet_client.get_pool_balance_for_wallet(
226
+ pool_address=pool_address,
227
+ chain_id=chain_id,
228
+ user_address=user_address,
229
+ human_readable=False,
230
+ )
231
+ raw = (
232
+ data.get("balance_raw") or data.get("balance")
233
+ if isinstance(data, dict)
234
+ else None
235
+ )
236
+ return (True, self._parse_balance(raw))
237
+ except Exception as e:
238
+ return (False, str(e))
@@ -0,0 +1,6 @@
1
+ {
2
+ "smoke": {
3
+ "get_balance": {"asset": "base_0x0000000000000000000000000000000000000000", "wallet_type": "strategy"}
4
+ }
5
+ }
6
+
@@ -0,0 +1,8 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "adapters.balance_adapter.adapter.BalanceAdapter"
3
+ capabilities:
4
+ - "wallet_read"
5
+ - "wallet_transfer"
6
+ dependencies:
7
+ - "TokenClient"
8
+ - "WalletClient"
@@ -0,0 +1,59 @@
1
+ from unittest.mock import AsyncMock, patch
2
+
3
+ import pytest
4
+
5
+ from wayfinder_paths.adapters.balance_adapter.adapter import BalanceAdapter
6
+
7
+
8
+ class TestBalanceAdapter:
9
+ """Test cases for BalanceAdapter"""
10
+
11
+ @pytest.fixture
12
+ def mock_wallet_client(self):
13
+ """Mock WalletClient for testing"""
14
+ mock_client = AsyncMock()
15
+ return mock_client
16
+
17
+ @pytest.fixture
18
+ def mock_token_client(self):
19
+ """Mock TokenClient for testing"""
20
+ mock_client = AsyncMock()
21
+ return mock_client
22
+
23
+ @pytest.fixture
24
+ def mock_web3_service(self):
25
+ """Mock TokenClient for testing"""
26
+ mock_client = AsyncMock()
27
+ return mock_client
28
+
29
+ @pytest.fixture
30
+ def adapter(self, mock_wallet_client, mock_token_client, mock_web3_service):
31
+ """Create a BalanceAdapter instance with mocked clients for testing"""
32
+ with (
33
+ patch(
34
+ "wayfinder_paths.adapters.balance_adapter.adapter.WalletClient",
35
+ return_value=mock_wallet_client,
36
+ ),
37
+ patch(
38
+ "wayfinder_paths.adapters.balance_adapter.adapter.TokenClient",
39
+ return_value=mock_token_client,
40
+ ),
41
+ ):
42
+ return BalanceAdapter(config={}, web3_service=mock_web3_service)
43
+
44
+ @pytest.mark.asyncio
45
+ async def test_health_check(self, adapter):
46
+ """Test adapter health check"""
47
+ health = await adapter.health_check()
48
+ assert isinstance(health, dict)
49
+ assert health.get("status") in {"healthy", "unhealthy", "error"}
50
+
51
+ @pytest.mark.asyncio
52
+ async def test_connect(self, adapter):
53
+ """Test adapter connection"""
54
+ ok = await adapter.connect()
55
+ assert isinstance(ok, bool)
56
+
57
+ def test_adapter_type(self, adapter):
58
+ """Test adapter has adapter_type"""
59
+ assert adapter.adapter_type == "BALANCE"
@@ -0,0 +1,249 @@
1
+ # BRAP Adapter
2
+
3
+ A Wayfinder adapter that provides high-level operations for cross-chain swaps and quotes via the BRAP (Bridge/Router/Adapter Protocol). This adapter wraps the `BRAPClient` to offer strategy-friendly methods for getting quotes, comparing routes, and executing cross-chain transactions.
4
+
5
+ ## Capabilities
6
+
7
+ - `swap.quote`: Get quotes for cross-chain swap operations
8
+ - `swap.execute`: Execute cross-chain swaps
9
+ - `bridge.quote`: Get quotes for bridge operations
10
+ - `bridge.execute`: Execute bridge operations
11
+ - `route.optimize`: Compare and optimize routes
12
+ - `fee.calculate`: Calculate fees and costs
13
+
14
+ ## Configuration
15
+
16
+ The adapter uses the BRAPClient which automatically handles authentication and API configuration through the Wayfinder settings. Pass a `Web3Service` instance so it can broadcast transactions and reuse the shared `LocalTokenTxnService` for approvals.
17
+
18
+ The BRAPClient will automatically:
19
+ - Use the WAYFINDER_API_URL from settings
20
+ - Handle authentication via environment variables or config.json
21
+ - Manage token refresh and retry logic
22
+
23
+ ## Usage
24
+
25
+ ### Initialize the Adapter
26
+
27
+ ```python
28
+ from wayfinder_paths.core.services.web3_service import DefaultWeb3Service
29
+ from wayfinder_paths.adapters.brap_adapter.adapter import BRAPAdapter
30
+
31
+ web3_service = DefaultWeb3Service(config)
32
+ adapter = BRAPAdapter(web3_service=web3_service)
33
+ ```
34
+
35
+ ### Get Swap Quote
36
+
37
+ ```python
38
+ success, data = await adapter.get_swap_quote(
39
+ from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
40
+ to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
41
+ from_chain_id=8453, # Base
42
+ to_chain_id=1, # Ethereum
43
+ from_address="0x1234567890123456789012345678901234567890",
44
+ to_address="0x1234567890123456789012345678901234567890",
45
+ amount="1000000000000000000", # 1 token (18 decimals)
46
+ slippage=0.01 # 1% slippage
47
+ )
48
+ if success:
49
+ quotes = data.get("quotes", {})
50
+ best_quote = quotes.get("best_quote", {})
51
+ print(f"Output amount: {best_quote.get('output_amount')}")
52
+ print(f"Total fee: {best_quote.get('total_fee')}")
53
+ else:
54
+ print(f"Error: {data}")
55
+ ```
56
+
57
+ ### Get Best Quote
58
+
59
+ ```python
60
+ success, data = await adapter.get_best_quote(
61
+ from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
62
+ to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
63
+ from_chain_id=8453,
64
+ to_chain_id=1,
65
+ from_address="0x1234567890123456789012345678901234567890",
66
+ to_address="0x1234567890123456789012345678901234567890",
67
+ amount="1000000000000000000"
68
+ )
69
+ if success:
70
+ print(f"Best output: {data.get('output_amount')}")
71
+ print(f"Gas fee: {data.get('gas_fee')}")
72
+ print(f"Bridge fee: {data.get('bridge_fee')}")
73
+ else:
74
+ print(f"Error: {data}")
75
+ ```
76
+
77
+ ### Calculate Swap Fees
78
+
79
+ ```python
80
+ success, data = await adapter.calculate_swap_fees(
81
+ from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
82
+ to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
83
+ from_chain_id=8453,
84
+ to_chain_id=1,
85
+ amount="1000000000000000000",
86
+ slippage=0.01
87
+ )
88
+ if success:
89
+ print(f"Input amount: {data.get('input_amount')}")
90
+ print(f"Output amount: {data.get('output_amount')}")
91
+ print(f"Gas fee: {data.get('gas_fee')}")
92
+ print(f"Bridge fee: {data.get('bridge_fee')}")
93
+ print(f"Protocol fee: {data.get('protocol_fee')}")
94
+ print(f"Total fee: {data.get('total_fee')}")
95
+ print(f"Price impact: {data.get('price_impact')}")
96
+ else:
97
+ print(f"Error: {data}")
98
+ ```
99
+
100
+ ### Compare Routes
101
+
102
+ ```python
103
+ success, data = await adapter.compare_routes(
104
+ from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
105
+ to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
106
+ from_chain_id=8453,
107
+ to_chain_id=1,
108
+ amount="1000000000000000000"
109
+ )
110
+ if success:
111
+ print(f"Total routes available: {data.get('total_routes')}")
112
+ print(f"Best route output: {data.get('best_route', {}).get('output_amount')}")
113
+
114
+ for i, route in enumerate(data.get('all_routes', [])):
115
+ print(f"Route {i+1}: Output {route.get('output_amount')}, Fee {route.get('total_fee')}")
116
+ else:
117
+ print(f"Error: {data}")
118
+ ```
119
+
120
+ ### Estimate Gas Costs
121
+
122
+ ```python
123
+ success, data = await adapter.estimate_gas_cost(
124
+ from_chain_id=8453, # Base
125
+ to_chain_id=1, # Ethereum
126
+ operation_type="swap"
127
+ )
128
+ if success:
129
+ print(f"From chain: {data.get('from_chain')}")
130
+ print(f"To chain: {data.get('to_chain')}")
131
+ print(f"From gas estimate: {data.get('from_gas_estimate')}")
132
+ print(f"To gas estimate: {data.get('to_gas_estimate')}")
133
+ print(f"Total operations: {data.get('total_operations')}")
134
+ else:
135
+ print(f"Error: {data}")
136
+ ```
137
+
138
+ ### Validate Swap Parameters
139
+
140
+ ```python
141
+ success, data = await adapter.validate_swap_parameters(
142
+ from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
143
+ to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
144
+ from_chain_id=8453,
145
+ to_chain_id=1,
146
+ amount="1000000000000000000"
147
+ )
148
+ if success:
149
+ if data.get("valid"):
150
+ print("Parameters are valid")
151
+ print(f"Estimated output: {data.get('estimated_output')}")
152
+ else:
153
+ print("Parameters are invalid:")
154
+ for error in data.get("errors", []):
155
+ print(f" - {error}")
156
+ else:
157
+ print(f"Error: {data}")
158
+ ```
159
+
160
+ ### Get Bridge Quote
161
+
162
+ ```python
163
+ # Bridge operations use the same interface as swaps
164
+ success, data = await adapter.get_bridge_quote(
165
+ from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
166
+ to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
167
+ from_chain_id=8453,
168
+ to_chain_id=1,
169
+ amount="1000000000000000000",
170
+ slippage=0.01
171
+ )
172
+ if success:
173
+ print(f"Bridge quote received: {data.get('quotes', {}).get('best_quote', {}).get('output_amount')}")
174
+ else:
175
+ print(f"Error: {data}")
176
+ ```
177
+
178
+ ## Advanced Usage
179
+
180
+ ### Route Optimization
181
+
182
+ ```python
183
+ # Compare multiple routes to find the best option
184
+ success, data = await adapter.compare_routes(
185
+ from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
186
+ to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
187
+ from_chain_id=8453,
188
+ to_chain_id=1,
189
+ amount="1000000000000000000"
190
+ )
191
+
192
+ if success:
193
+ analysis = data.get("route_analysis", {})
194
+ highest_output = analysis.get("highest_output")
195
+ lowest_fees = analysis.get("lowest_fees")
196
+ fastest = analysis.get("fastest")
197
+
198
+ print(f"Highest output route: {highest_output.get('output_amount')}")
199
+ print(f"Lowest fees route: {lowest_fees.get('total_fee')}")
200
+ print(f"Fastest route: {fastest.get('estimated_time')} seconds")
201
+ ```
202
+
203
+ ### Fee Analysis
204
+
205
+ ```python
206
+ # Analyze fees for a swap operation
207
+ success, data = await adapter.calculate_swap_fees(
208
+ from_token_address="0xA0b86a33E6441c8C06DdD4D4c4c4c4c4c4c4c4c4c",
209
+ to_token_address="0xB1c97a44F7552d9Dd5e5e5e5e5e5e5e5e5e5e5e5e5e",
210
+ from_chain_id=8453,
211
+ to_chain_id=1,
212
+ amount="1000000000000000000"
213
+ )
214
+
215
+ if success:
216
+ input_amount = int(data.get("input_amount", 0))
217
+ output_amount = int(data.get("output_amount", 0))
218
+ total_fee = int(data.get("total_fee", 0))
219
+
220
+ # Calculate effective rate
221
+ effective_rate = (input_amount - output_amount) / input_amount
222
+ print(f"Effective rate: {effective_rate:.4f} ({effective_rate * 100:.2f}%)")
223
+ print(f"Total fees: {total_fee / 1e18:.6f} tokens")
224
+ ```
225
+
226
+ ## API Endpoints
227
+
228
+ The adapter uses the following Wayfinder API endpoints:
229
+
230
+ - `POST /api/v1/public/quotes/` - Get swap/bridge quotes
231
+
232
+ ## Error Handling
233
+
234
+ All methods return a tuple of `(success: bool, data: Any)` where:
235
+ - `success` is `True` if the operation succeeded
236
+ - `data` contains the response data on success or error message on failure
237
+
238
+ ## Testing
239
+
240
+ Run the adapter tests:
241
+
242
+ ```bash
243
+ pytest wayfinder_paths/adapters/brap_adapter/test_adapter.py -v
244
+ ```
245
+
246
+ ## Dependencies
247
+
248
+ - `BRAPClient` - Low-level API client for BRAP operations
249
+ - `BaseAdapter` - Base adapter class with common functionality
@@ -0,0 +1,7 @@
1
+ """
2
+ BRAP Adapter Package
3
+ """
4
+
5
+ from .adapter import BRAPAdapter
6
+
7
+ __all__ = ["BRAPAdapter"]