wayfinder-paths 0.1.8__py3-none-any.whl → 0.1.10__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 (80) hide show
  1. wayfinder_paths/CONFIG_GUIDE.md +6 -15
  2. wayfinder_paths/adapters/balance_adapter/README.md +1 -2
  3. wayfinder_paths/adapters/balance_adapter/adapter.py +4 -4
  4. wayfinder_paths/adapters/brap_adapter/README.md +1 -1
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +139 -74
  6. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +0 -7
  7. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +0 -54
  8. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +1 -1
  9. wayfinder_paths/adapters/ledger_adapter/README.md +1 -1
  10. wayfinder_paths/adapters/moonwell_adapter/README.md +174 -0
  11. wayfinder_paths/adapters/moonwell_adapter/__init__.py +7 -0
  12. wayfinder_paths/adapters/moonwell_adapter/adapter.py +1226 -0
  13. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +635 -0
  14. wayfinder_paths/adapters/pool_adapter/README.md +1 -77
  15. wayfinder_paths/adapters/pool_adapter/adapter.py +0 -122
  16. wayfinder_paths/adapters/pool_adapter/examples.json +0 -57
  17. wayfinder_paths/adapters/pool_adapter/test_adapter.py +0 -86
  18. wayfinder_paths/adapters/token_adapter/README.md +1 -1
  19. wayfinder_paths/core/clients/ClientManager.py +1 -22
  20. wayfinder_paths/core/clients/WalletClient.py +0 -8
  21. wayfinder_paths/core/clients/WayfinderClient.py +7 -12
  22. wayfinder_paths/core/clients/__init__.py +0 -8
  23. wayfinder_paths/core/clients/protocols.py +0 -60
  24. wayfinder_paths/core/config.py +5 -45
  25. wayfinder_paths/core/constants/__init__.py +0 -2
  26. wayfinder_paths/core/constants/base.py +6 -2
  27. wayfinder_paths/core/constants/moonwell_abi.py +411 -0
  28. wayfinder_paths/core/services/base.py +7 -1
  29. wayfinder_paths/core/services/local_evm_txn.py +223 -222
  30. wayfinder_paths/core/services/local_token_txn.py +103 -92
  31. wayfinder_paths/core/services/web3_service.py +0 -2
  32. wayfinder_paths/core/settings.py +8 -8
  33. wayfinder_paths/core/strategies/Strategy.py +1 -5
  34. wayfinder_paths/core/strategies/descriptors.py +1 -1
  35. wayfinder_paths/core/utils/evm_helpers.py +7 -12
  36. wayfinder_paths/core/wallets/README.md +3 -6
  37. wayfinder_paths/run_strategy.py +62 -105
  38. wayfinder_paths/scripts/create_strategy.py +2 -27
  39. wayfinder_paths/scripts/make_wallets.py +1 -25
  40. wayfinder_paths/scripts/run_strategy.py +37 -9
  41. wayfinder_paths/strategies/basis_trading_strategy/snapshot_mixin.py +1 -3
  42. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +87 -138
  43. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +96 -58
  44. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +2 -17
  45. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/examples.json +4 -1
  46. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +107 -29
  47. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +53 -14
  48. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +108 -0
  49. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/examples.json +11 -0
  50. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2975 -0
  51. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +886 -0
  52. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +0 -7
  53. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +2 -7
  54. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -4
  55. wayfinder_paths/templates/adapter/README.md +5 -21
  56. wayfinder_paths/templates/adapter/adapter.py +1 -2
  57. wayfinder_paths/templates/adapter/test_adapter.py +1 -1
  58. wayfinder_paths/templates/strategy/README.md +4 -21
  59. wayfinder_paths/templates/strategy/test_strategy.py +0 -4
  60. wayfinder_paths/tests/test_smoke_manifest.py +17 -2
  61. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/METADATA +64 -201
  62. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/RECORD +64 -71
  63. wayfinder_paths/adapters/balance_adapter/manifest.yaml +0 -8
  64. wayfinder_paths/adapters/brap_adapter/manifest.yaml +0 -11
  65. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +0 -10
  66. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +0 -8
  67. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +0 -11
  68. wayfinder_paths/adapters/pool_adapter/manifest.yaml +0 -10
  69. wayfinder_paths/adapters/token_adapter/manifest.yaml +0 -6
  70. wayfinder_paths/core/clients/SimulationClient.py +0 -192
  71. wayfinder_paths/core/clients/TransactionClient.py +0 -63
  72. wayfinder_paths/core/engine/manifest.py +0 -97
  73. wayfinder_paths/scripts/validate_manifests.py +0 -213
  74. wayfinder_paths/strategies/basis_trading_strategy/manifest.yaml +0 -23
  75. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/manifest.yaml +0 -7
  76. wayfinder_paths/strategies/stablecoin_yield_strategy/manifest.yaml +0 -17
  77. wayfinder_paths/templates/adapter/manifest.yaml +0 -6
  78. wayfinder_paths/templates/strategy/manifest.yaml +0 -8
  79. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/LICENSE +0 -0
  80. {wayfinder_paths-0.1.8.dist-info → wayfinder_paths-0.1.10.dist-info}/WHEEL +0 -0
@@ -52,7 +52,6 @@ cp wayfinder_paths/config.example.json config.json
52
52
  **Option 1: Service Account (API Key) - Recommended for Production**
53
53
  - `user.api_key` - Your Wayfinder API key for service account authentication (loaded directly by clients from config.json)
54
54
  - OR `system.api_key` - System-level API key (alternative location, loaded directly by clients)
55
- - OR set `WAYFINDER_API_KEY` environment variable
56
55
  - OR pass `api_key` parameter to strategy constructor
57
56
 
58
57
  **Note:** API keys in `config.json` are loaded directly by `WayfinderClient` via `_load_config_credentials()`, not through the `UserConfig` or `SystemConfig` dataclasses. This is intentional to allow flexible credential loading.
@@ -139,7 +138,7 @@ just create-strategy "My Strategy Name"
139
138
  # This automatically:
140
139
  # - Creates the strategy directory
141
140
  # - Generates a wallet with label matching the directory name
142
- # - Updates the manifest with the correct name and entrypoint
141
+ # - Updates the strategy files with the correct names
143
142
  ```
144
143
 
145
144
  ### Wallet Structure
@@ -229,7 +228,6 @@ The strategy system supports multiple wallet types through a wallet abstraction
229
228
 
230
229
  By default, adapters use `LocalWalletProvider` which resolves private keys from:
231
230
  - `wallets.json` (matched by address)
232
- - Environment variables (`PRIVATE_KEY`, `PRIVATE_KEY_STRATEGY`)
233
231
  - Wallet config in `config.json`
234
232
 
235
233
  No code changes are required - existing strategies continue to work.
@@ -270,7 +268,7 @@ Wayfinder Paths supports **dual authentication** for different use cases:
270
268
 
271
269
  **Best for:** Backend services, automated systems, and production deployments with higher rate limits.
272
270
 
273
- API keys provide service account authentication and are automatically discovered by all clients. You can provide an API key in three ways:
271
+ API keys provide service account authentication and are automatically discovered by all clients. You can provide an API key in two ways:
274
272
 
275
273
  #### Option A: Strategy Constructor (Programmatic)
276
274
  ```python
@@ -278,16 +276,11 @@ from wayfinder_paths.strategies.stablecoin_yield_strategy.strategy import Stable
278
276
 
279
277
  strategy = StablecoinYieldStrategy(
280
278
  config={...},
281
- api_key="sk_live_abc123..." # Sets WAYFINDER_API_KEY env var automatically
279
+ api_key="sk_live_abc123..." # API key is auto-discovered by all clients
282
280
  )
283
281
  ```
284
282
 
285
- #### Option B: Environment Variable
286
- ```bash
287
- export WAYFINDER_API_KEY="sk_live_abc123..."
288
- ```
289
-
290
- #### Option C: config.json
283
+ #### Option B: config.json
291
284
  ```json
292
285
  {
293
286
  "user": {
@@ -299,14 +292,12 @@ export WAYFINDER_API_KEY="sk_live_abc123..."
299
292
  }
300
293
  ```
301
294
 
302
- **Priority Order:** Constructor parameter > `config.json` (user.api_key or system.api_key) > `WAYFINDER_API_KEY` environment variable
295
+ **Priority Order:** Constructor parameter > `config.json` (user.api_key or system.api_key)
303
296
 
304
297
  **How It Works:**
305
- - When a strategy receives an `api_key`, it sets `os.environ["WAYFINDER_API_KEY"]`
306
298
  - All clients created by adapters automatically discover the API key from:
307
299
  - Constructor parameter (if passed)
308
300
  - `config.json` (via `WayfinderClient._load_config_credentials()` which reads `user.api_key` or `system.api_key`)
309
- - `WAYFINDER_API_KEY` environment variable
310
301
  - API keys are included in **all** API requests (including public endpoints) for rate limiting
311
302
  - No need to pass API keys explicitly to adapters or clients—they auto-discover it
312
303
  - **Note:** API keys in `config.json` are loaded directly by clients, not stored in the `UserConfig` or `SystemConfig` dataclasses
@@ -379,7 +370,7 @@ To use custom RPC endpoints, update the `strategy.rpc_urls` section in `config.j
379
370
  **Issue:** "Authentication failed"
380
371
  - **If using API key:**
381
372
  - Verify API key is correct and not expired
382
- - Check that API key is set in one of: constructor parameter, `config.json` (user.api_key or system.api_key), or `WAYFINDER_API_KEY` env var
373
+ - Check that API key is set in one of: constructor parameter or `config.json` (user.api_key or system.api_key)
383
374
  - Ensure API key has proper permissions for the operations you're performing
384
375
  - **If using OAuth:**
385
376
  - Check that `username` and `password` are correct in `config.json`
@@ -3,12 +3,11 @@
3
3
  Adapter that exposes wallet, token, and pool balances backed by `WalletClient`/`TokenClient` and now orchestrates transfers between the configured main/strategy wallets (with ledger bookkeeping).
4
4
 
5
5
  - Entrypoint: `adapters.balance_adapter.adapter.BalanceAdapter`
6
- - Manifest: `manifest.yaml`
7
6
  - Tests: `test_adapter.py`
8
7
 
9
8
  ## Capabilities
10
9
 
11
- The adapter declares both `wallet_read` and `wallet_transfer` capabilities in its manifest. Transfers are executed by leveraging the shared `DefaultWeb3Service.token_transactions` helper, but ledger recording + wallet selection now live inside the adapter.
10
+ The adapter provides both wallet read and wallet transfer capabilities. Transfers are executed by leveraging the shared `DefaultWeb3Service.token_transactions` helper, but ledger recording + wallet selection now live inside the adapter.
12
11
 
13
12
  ## Construction
14
13
 
@@ -170,7 +170,7 @@ class BalanceAdapter(BaseAdapter):
170
170
  token_amount=str(amount),
171
171
  usd_value=usd_value,
172
172
  data={
173
- "token_id": token_info.get("id"),
173
+ "token_id": token_info.get("token_id"),
174
174
  "amount": str(amount),
175
175
  "usd_value": usd_value,
176
176
  },
@@ -180,7 +180,7 @@ class BalanceAdapter(BaseAdapter):
180
180
  self.logger.warning(
181
181
  "Ledger entry failed",
182
182
  wallet=wallet_address,
183
- token_id=token_info.get("id"),
183
+ token_id=token_info.get("token_id"),
184
184
  amount=amount,
185
185
  error=response,
186
186
  )
@@ -188,13 +188,13 @@ class BalanceAdapter(BaseAdapter):
188
188
  self.logger.warning(
189
189
  f"Ledger entry raised: {exc}",
190
190
  wallet=wallet_address,
191
- token_id=token_info.get("id"),
191
+ token_id=token_info.get("token_id"),
192
192
  )
193
193
 
194
194
  async def _token_amount_usd(
195
195
  self, token_info: dict[str, Any], amount: float
196
196
  ) -> float:
197
- token_id = token_info.get("id")
197
+ token_id = token_info.get("token_id")
198
198
  if not token_id:
199
199
  return 0.0
200
200
  success, price_data = await self.token_adapter.get_token_price(token_id)
@@ -17,7 +17,7 @@ The adapter uses the BRAPClient which automatically handles authentication and A
17
17
 
18
18
  The BRAPClient will automatically:
19
19
  - Use the WAYFINDER_API_URL from settings
20
- - Handle authentication via environment variables or config.json
20
+ - Handle authentication via config.json
21
21
  - Manage token refresh and retry logic
22
22
 
23
23
  ## Usage
@@ -10,12 +10,8 @@ from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
10
10
  from wayfinder_paths.core.adapters.models import SWAP
11
11
  from wayfinder_paths.core.clients.BRAPClient import BRAPClient, BRAPQuote
12
12
  from wayfinder_paths.core.clients.LedgerClient import TransactionRecord
13
- from wayfinder_paths.core.clients.SimulationClient import (
14
- SimulationClient,
15
- SimulationResult,
16
- )
17
13
  from wayfinder_paths.core.clients.TokenClient import TokenClient
18
- from wayfinder_paths.core.constants import DEFAULT_SLIPPAGE
14
+ from wayfinder_paths.core.constants import DEFAULT_SLIPPAGE, ZERO_ADDRESS
19
15
  from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
20
16
  from wayfinder_paths.core.services.base import Web3Service
21
17
  from wayfinder_paths.core.settings import settings
@@ -45,7 +41,6 @@ class BRAPAdapter(BaseAdapter):
45
41
  config: dict[str, Any] | None = None,
46
42
  *,
47
43
  web3_service: Web3Service,
48
- simulation: bool = False,
49
44
  ):
50
45
  super().__init__("brap_adapter", config)
51
46
  self.brap_client = BRAPClient()
@@ -55,8 +50,6 @@ class BRAPAdapter(BaseAdapter):
55
50
  self.web3_service = web3_service
56
51
  self.wallet_provider = web3_service.evm_transactions
57
52
  self.token_transactions = web3_service.token_transactions
58
- self.simulation = simulation
59
- self.simulation_client = SimulationClient() if simulation else None
60
53
 
61
54
  async def get_swap_quote(
62
55
  self,
@@ -115,6 +108,7 @@ class BRAPAdapter(BaseAdapter):
115
108
  amount: str,
116
109
  slippage: float | None = None,
117
110
  wayfinder_fee: float | None = None,
111
+ preferred_providers: list[str] | None = None,
118
112
  ) -> tuple[bool, dict[str, Any] | str]:
119
113
  """
120
114
  Get the best available quote for a swap operation.
@@ -129,6 +123,8 @@ class BRAPAdapter(BaseAdapter):
129
123
  amount: Amount to swap (in smallest units)
130
124
  slippage: Maximum slippage tolerance (optional)
131
125
  wayfinder_fee: Wayfinder fee (optional)
126
+ preferred_providers: List of provider names in order of preference (optional).
127
+ If provided, selects first matching provider instead of "best".
132
128
 
133
129
  Returns:
134
130
  Tuple of (success, data) where data is best quote or error message
@@ -146,8 +142,27 @@ class BRAPAdapter(BaseAdapter):
146
142
  wayfinder_fee=wayfinder_fee,
147
143
  )
148
144
 
149
- # Extract best quote from response
150
- best_quote = data["best_route"]
145
+ quotes_container = data.get("quotes", {})
146
+ # BRAP returns quotes in "all_quotes" not "quotes"
147
+ all_quotes = quotes_container.get("all_quotes", []) or quotes_container.get(
148
+ "quotes", []
149
+ )
150
+
151
+ # If preferred providers specified, select by provider preference
152
+ if preferred_providers and all_quotes:
153
+ selected_quote = self._select_quote_by_provider(
154
+ all_quotes, preferred_providers
155
+ )
156
+ if selected_quote:
157
+ return (True, selected_quote)
158
+ # Fall through to best_quote if no preferred provider found
159
+
160
+ # Extract best quote from response - check nested quotes first, then top level
161
+ best_quote = (
162
+ quotes_container.get("best_quote")
163
+ or data.get("best_quote")
164
+ or data.get("best_route")
165
+ )
151
166
 
152
167
  if not best_quote:
153
168
  return (False, "No quotes available")
@@ -157,6 +172,58 @@ class BRAPAdapter(BaseAdapter):
157
172
  self.logger.error(f"Error getting best quote: {e}")
158
173
  return (False, str(e))
159
174
 
175
+ def _select_quote_by_provider(
176
+ self,
177
+ quotes: list[dict[str, Any]],
178
+ preferred_providers: list[str],
179
+ ) -> dict[str, Any] | None:
180
+ """
181
+ Select a quote from the list based on provider preference order.
182
+
183
+ Args:
184
+ quotes: List of quote objects from BRAP API
185
+ preferred_providers: List of provider names in order of preference (case-insensitive)
186
+
187
+ Returns:
188
+ The first matching quote, or None if no preferred provider found
189
+ """
190
+ # Normalize preferred providers to lowercase for case-insensitive matching
191
+ preferred_lower = [p.lower() for p in preferred_providers]
192
+
193
+ # Build a map of provider name -> quotes
194
+ provider_quotes: dict[str, list[dict[str, Any]]] = {}
195
+ for quote in quotes:
196
+ # Provider name might be in different fields depending on BRAP response structure
197
+ provider = (
198
+ quote.get("provider")
199
+ or quote.get("provider_name")
200
+ or quote.get("source")
201
+ or quote.get("protocol")
202
+ or ""
203
+ ).lower()
204
+ if provider:
205
+ if provider not in provider_quotes:
206
+ provider_quotes[provider] = []
207
+ provider_quotes[provider].append(quote)
208
+
209
+ # Select first matching provider in preference order
210
+ for pref in preferred_lower:
211
+ if pref in provider_quotes:
212
+ # Return the quote with highest output for this provider
213
+ provider_list = provider_quotes[pref]
214
+ best_for_provider = max(
215
+ provider_list, key=lambda q: int(q.get("output_amount", 0) or 0)
216
+ )
217
+ self.logger.info(f"Selected quote from preferred provider: {pref}")
218
+ return best_for_provider
219
+
220
+ # Log available providers for debugging
221
+ available = list(provider_quotes.keys())
222
+ self.logger.warning(
223
+ f"No preferred provider found. Wanted: {preferred_providers}, Available: {available}"
224
+ )
225
+ return None
226
+
160
227
  async def calculate_swap_fees(
161
228
  self,
162
229
  from_token_address: str,
@@ -255,7 +322,7 @@ class BRAPAdapter(BaseAdapter):
255
322
  )
256
323
 
257
324
  quotes = data.get("quotes", {})
258
- all_quotes = quotes.get("quotes", [])
325
+ all_quotes = quotes.get("all_quotes", []) or quotes.get("quotes", [])
259
326
  best_quote = quotes.get("best_quote")
260
327
 
261
328
  if not all_quotes:
@@ -298,9 +365,20 @@ class BRAPAdapter(BaseAdapter):
298
365
  amount: str,
299
366
  slippage: float = DEFAULT_SLIPPAGE,
300
367
  strategy_name: str | None = None,
368
+ preferred_providers: list[str] | None = None,
301
369
  ) -> tuple[bool, Any]:
302
370
  """
303
371
  Execute a swap by looking up token metadata via token IDs.
372
+
373
+ Args:
374
+ from_token_id: Source token ID
375
+ to_token_id: Destination token ID
376
+ from_address: Wallet address
377
+ amount: Amount to swap (in smallest units)
378
+ slippage: Maximum slippage tolerance
379
+ strategy_name: Strategy name for ledger recording
380
+ preferred_providers: List of provider names in order of preference (e.g., ["enso", "aerodrome"]).
381
+ If provided, selects first matching provider instead of "best".
304
382
  """
305
383
  from_token = await self.token_client.get_token_details(from_token_id)
306
384
  if not from_token:
@@ -318,6 +396,7 @@ class BRAPAdapter(BaseAdapter):
318
396
  to_address=from_address,
319
397
  amount=amount,
320
398
  slippage=slippage,
399
+ preferred_providers=preferred_providers,
321
400
  )
322
401
  if not success:
323
402
  return (False, best_quote)
@@ -356,7 +435,14 @@ class BRAPAdapter(BaseAdapter):
356
435
  or quote.get("inputAmount")
357
436
  or transaction.get("value")
358
437
  )
359
- if from_token.get("address") and spender and approve_amount:
438
+ token_address = from_token.get("address")
439
+ token_address_l = str(token_address or "").lower()
440
+ is_native = token_address_l in {
441
+ "",
442
+ ZERO_ADDRESS.lower(),
443
+ "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
444
+ }
445
+ if token_address and (not is_native) and spender and approve_amount:
360
446
  approve_success, approve_response = await self._handle_token_approval(
361
447
  chain=chain,
362
448
  token_address=from_token.get("address"),
@@ -367,26 +453,39 @@ class BRAPAdapter(BaseAdapter):
367
453
  if not approve_success:
368
454
  return (False, approve_response)
369
455
 
370
- if self.simulation:
371
- simulation = await self._simulate_swap(
372
- from_token, to_token, from_address, chain_id, quote
373
- )
374
- return (True, {"quote": quote, "simulation": simulation})
375
-
376
456
  broadcast_success, broadcast_response = await self._broadcast_transaction(
377
457
  transaction
378
458
  )
459
+ self.logger.info(
460
+ f"Swap broadcast result: success={broadcast_success}, "
461
+ f"response={broadcast_response}"
462
+ )
379
463
  if not broadcast_success:
380
464
  return (False, broadcast_response)
381
465
 
382
- ledger_record = await self._record_swap_operation(
383
- from_token=from_token,
384
- to_token=to_token,
385
- wallet_address=from_address,
386
- quote=quote,
387
- broadcast_response=broadcast_response,
388
- strategy_name=strategy_name,
389
- )
466
+ # Record the swap operation in ledger - but don't let ledger errors fail the swap
467
+ # since the on-chain transaction already succeeded
468
+ try:
469
+ ledger_record = await self._record_swap_operation(
470
+ from_token=from_token,
471
+ to_token=to_token,
472
+ wallet_address=from_address,
473
+ quote=quote,
474
+ broadcast_response=broadcast_response,
475
+ strategy_name=strategy_name,
476
+ )
477
+ except Exception as e:
478
+ self.logger.warning(
479
+ f"Ledger recording failed (swap succeeded on-chain): {e}"
480
+ )
481
+ # Return the quote with output amount so caller can proceed
482
+ ledger_record = {
483
+ "to_amount": quote.get("output_amount"),
484
+ "from_amount": quote.get("input_amount"),
485
+ "tx_hash": broadcast_response.get("tx_hash")
486
+ if isinstance(broadcast_response, dict)
487
+ else None,
488
+ }
390
489
  return (True, ledger_record)
391
490
 
392
491
  async def get_bridge_quote(
@@ -594,10 +693,10 @@ class BRAPAdapter(BaseAdapter):
594
693
  )
595
694
  if not build_success:
596
695
  return False, approve_tx
597
- return await self._broadcast_transaction(approve_tx)
696
+ return await self._broadcast_transaction(approve_tx, confirmations=2)
598
697
 
599
698
  async def _broadcast_transaction(
600
- self, transaction: dict[str, Any]
699
+ self, transaction: dict[str, Any], confirmations: int = 0
601
700
  ) -> tuple[bool, Any]:
602
701
  if getattr(settings, "DRY_RUN", False):
603
702
  return True, {"dry_run": True, "transaction": transaction}
@@ -605,6 +704,7 @@ class BRAPAdapter(BaseAdapter):
605
704
  transaction,
606
705
  wait_for_receipt=True,
607
706
  timeout=DEFAULT_TRANSACTION_TIMEOUT,
707
+ confirmations=confirmations,
608
708
  )
609
709
 
610
710
  async def _record_swap_operation(
@@ -631,16 +731,19 @@ class BRAPAdapter(BaseAdapter):
631
731
  response = broadcast_response if isinstance(broadcast_response, dict) else {}
632
732
  operation_data = SWAP(
633
733
  adapter=self.adapter_type,
634
- from_token_id=from_token.get("id"),
635
- to_token_id=to_token.get("id"),
636
- from_amount=quote.get("input_amount"),
637
- to_amount=quote.get("output_amount"),
734
+ from_token_id=str(from_token.get("id")),
735
+ to_token_id=str(to_token.get("id")),
736
+ from_amount=str(quote.get("input_amount")),
737
+ to_amount=str(quote.get("output_amount")),
638
738
  from_amount_usd=from_amount_usd or 0,
639
739
  to_amount_usd=to_amount_usd or 0,
640
- transaction_hash=response.get("transaction_hash"),
641
- transaction_chain_id=from_token.get("chain_id"),
740
+ transaction_hash=response.get("tx_hash")
741
+ or response.get("transaction_hash"),
742
+ transaction_chain_id=from_token.get("chain_id")
743
+ or (from_token.get("chain") or {}).get("id"),
642
744
  transaction_status=response.get("transaction_status"),
643
- transaction_receipt=response.get("transaction_receipt"),
745
+ # Don't pass raw receipt - it contains HexBytes that can't be JSON serialized
746
+ transaction_receipt=None,
644
747
  )
645
748
 
646
749
  try:
@@ -666,7 +769,7 @@ class BRAPAdapter(BaseAdapter):
666
769
  if raw_amount is None:
667
770
  return None
668
771
  success, price_data = await self.token_adapter.get_token_price(
669
- token_info.get("id")
772
+ token_info.get("token_id")
670
773
  )
671
774
  if not success or not price_data:
672
775
  return None
@@ -677,44 +780,6 @@ class BRAPAdapter(BaseAdapter):
677
780
  / 10 ** int(decimals)
678
781
  )
679
782
 
680
- async def _simulate_swap(
681
- self,
682
- from_token: dict[str, Any],
683
- to_token: dict[str, Any],
684
- from_address: str,
685
- chain_id: int,
686
- quote: dict[str, Any],
687
- ) -> SimulationResult:
688
- client = await self._get_simulation_client()
689
- initial_balances = {"native": "5000000000000000000"}
690
- if from_token.get("address"):
691
- initial_balances[from_token.get("address")] = "1000000000000000000000000"
692
-
693
- slippage = quote.get("slippage") or quote.get("slippage_percent")
694
- if isinstance(slippage, str):
695
- try:
696
- slippage = float(slippage)
697
- except ValueError:
698
- slippage = DEFAULT_SLIPPAGE
699
- slippage = slippage or DEFAULT_SLIPPAGE
700
-
701
- amount = quote.get("input_amount") or quote.get("inputAmount") or "0"
702
- return await client.simulate_swap(
703
- from_token_address=from_token.get("address"),
704
- to_token_address=to_token.get("address"),
705
- from_chain_id=chain_id,
706
- to_chain_id=chain_id,
707
- amount=str(amount),
708
- from_address=from_address,
709
- slippage=float(slippage),
710
- initial_balances=initial_balances,
711
- )
712
-
713
- async def _get_simulation_client(self) -> SimulationClient:
714
- if not self.simulation_client:
715
- self.simulation_client = SimulationClient()
716
- return self.simulation_client
717
-
718
783
  def _chain_id(self, chain: Any) -> int:
719
784
  if isinstance(chain, dict):
720
785
  chain_id = chain.get("id") or chain.get("chain_id")
@@ -12,7 +12,6 @@ from wayfinder_paths.core.clients.HyperlendClient import (
12
12
  MarketEntry,
13
13
  StableMarket,
14
14
  )
15
- from wayfinder_paths.core.clients.SimulationClient import SimulationClient
16
15
  from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
17
16
  from wayfinder_paths.core.constants.hyperlend_abi import (
18
17
  POOL_ABI,
@@ -37,16 +36,12 @@ class HyperlendAdapter(BaseAdapter):
37
36
  self,
38
37
  config: dict[str, Any],
39
38
  web3_service: Web3Service,
40
- simulation: bool = False,
41
39
  ) -> None:
42
40
  super().__init__("hyperlend_adapter", config)
43
41
  cfg = config or {}
44
42
  adapter_cfg = cfg.get("hyperlend_adapter") or {}
45
43
 
46
44
  self.hyperlend_client = HyperlendClient()
47
- self.simulation = simulation
48
- self.simulation_client = SimulationClient() if simulation else None
49
-
50
45
  self.web3 = web3_service
51
46
  self.token_txn_service = web3_service.token_transactions
52
47
 
@@ -237,8 +232,6 @@ class HyperlendAdapter(BaseAdapter):
237
232
  return await self._broadcast_transaction(approve_tx)
238
233
 
239
234
  async def _execute(self, tx: dict[str, Any]) -> tuple[bool, Any]:
240
- if self.simulation:
241
- return True, {"simulation": tx}
242
235
  return await self.web3.broadcast_transaction(
243
236
  tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
244
237
  )
@@ -74,7 +74,6 @@ class HyperliquidAdapter(BaseAdapter):
74
74
  self,
75
75
  config: dict[str, Any] | None = None,
76
76
  *,
77
- simulation: bool = False,
78
77
  executor: HyperliquidExecutor | None = None,
79
78
  ) -> None:
80
79
  super().__init__("hyperliquid_adapter", config)
@@ -85,7 +84,6 @@ class HyperliquidAdapter(BaseAdapter):
85
84
  "Install with: poetry add hyperliquid"
86
85
  )
87
86
 
88
- self.simulation = simulation
89
87
  self._cache = SimpleCache()
90
88
  self._executor = executor
91
89
 
@@ -481,13 +479,6 @@ class HyperliquidAdapter(BaseAdapter):
481
479
  Returns:
482
480
  (success, response_data or error_message)
483
481
  """
484
- if self.simulation:
485
- self.logger.info(
486
- f"[SIMULATION] place_market_order: asset={asset_id}, "
487
- f"is_buy={is_buy}, size={size}, address={address}"
488
- )
489
- return True, {"simulation": True, "status": "ok"}
490
-
491
482
  if not self._executor:
492
483
  raise NotImplementedError(
493
484
  "No Hyperliquid executor configured. "
@@ -525,12 +516,6 @@ class HyperliquidAdapter(BaseAdapter):
525
516
  Returns:
526
517
  (success, response_data or error_message)
527
518
  """
528
- if self.simulation:
529
- self.logger.info(
530
- f"[SIMULATION] cancel_order: asset={asset_id}, oid={order_id}"
531
- )
532
- return True, {"simulation": True, "status": "ok"}
533
-
534
519
  if not self._executor:
535
520
  raise NotImplementedError(
536
521
  "No Hyperliquid executor configured. "
@@ -565,12 +550,6 @@ class HyperliquidAdapter(BaseAdapter):
565
550
  Returns:
566
551
  (success, response_data or error_message)
567
552
  """
568
- if self.simulation:
569
- self.logger.info(
570
- f"[SIMULATION] update_leverage: asset={asset_id}, leverage={leverage}"
571
- )
572
- return True, {"simulation": True, "status": "ok"}
573
-
574
553
  if not self._executor:
575
554
  raise NotImplementedError("No Hyperliquid executor configured.")
576
555
 
@@ -590,10 +569,6 @@ class HyperliquidAdapter(BaseAdapter):
590
569
  address: str,
591
570
  ) -> tuple[bool, dict[str, Any]]:
592
571
  """Transfer USDC from spot to perp balance."""
593
- if self.simulation:
594
- self.logger.info(f"[SIMULATION] transfer_spot_to_perp: {amount} USDC")
595
- return True, {"simulation": True, "status": "ok"}
596
-
597
572
  if not self._executor:
598
573
  raise NotImplementedError("No Hyperliquid executor configured.")
599
574
 
@@ -611,10 +586,6 @@ class HyperliquidAdapter(BaseAdapter):
611
586
  address: str,
612
587
  ) -> tuple[bool, dict[str, Any]]:
613
588
  """Transfer USDC from perp to spot balance."""
614
- if self.simulation:
615
- self.logger.info(f"[SIMULATION] transfer_perp_to_spot: {amount} USDC")
616
- return True, {"simulation": True, "status": "ok"}
617
-
618
589
  if not self._executor:
619
590
  raise NotImplementedError("No Hyperliquid executor configured.")
620
591
 
@@ -647,13 +618,6 @@ class HyperliquidAdapter(BaseAdapter):
647
618
  Returns:
648
619
  (success, response_data or error_message)
649
620
  """
650
- if self.simulation:
651
- self.logger.info(
652
- f"[SIMULATION] place_stop_loss: asset={asset_id}, "
653
- f"trigger={trigger_price}, size={size}"
654
- )
655
- return True, {"simulation": True, "status": "ok"}
656
-
657
621
  if not self._executor:
658
622
  raise NotImplementedError("No Hyperliquid executor configured.")
659
623
 
@@ -758,10 +722,6 @@ class HyperliquidAdapter(BaseAdapter):
758
722
 
759
723
  Note: This is an L1 withdrawal handled by the Hyperliquid executor (signing required).
760
724
  """
761
- if self.simulation:
762
- self.logger.info(f"[SIMULATION] withdraw: {amount} USDC")
763
- return True, {"simulation": True, "status": "ok"}
764
-
765
725
  if not self._executor:
766
726
  raise NotImplementedError("No Hyperliquid executor configured.")
767
727
 
@@ -856,13 +816,6 @@ class HyperliquidAdapter(BaseAdapter):
856
816
  Returns:
857
817
  (success, response_data or error_message)
858
818
  """
859
- if self.simulation:
860
- self.logger.info(
861
- f"[SIMULATION] approve_builder_fee: builder={builder}, "
862
- f"rate={max_fee_rate}, address={address}"
863
- )
864
- return True, {"simulation": True, "status": "ok"}
865
-
866
819
  if not self._executor:
867
820
  raise NotImplementedError("No Hyperliquid executor configured.")
868
821
 
@@ -903,13 +856,6 @@ class HyperliquidAdapter(BaseAdapter):
903
856
  Returns:
904
857
  (success, response_data or error_message)
905
858
  """
906
- if self.simulation:
907
- self.logger.info(
908
- f"[SIMULATION] place_limit_order: asset={asset_id}, "
909
- f"is_buy={is_buy}, price={price}, size={size}"
910
- )
911
- return True, {"simulation": True, "status": "ok"}
912
-
913
859
  if not self._executor:
914
860
  raise NotImplementedError("No Hyperliquid executor configured.")
915
861
 
@@ -17,7 +17,7 @@ from wayfinder_paths.adapters.hyperliquid_adapter.adapter import HyperliquidAdap
17
17
  @pytest.fixture
18
18
  def live_adapter():
19
19
  """Create adapter connected to real Hyperliquid API."""
20
- return HyperliquidAdapter(config={}, simulation=True)
20
+ return HyperliquidAdapter(config={})
21
21
 
22
22
 
23
23
  class TestSpotAssetIDs:
@@ -17,7 +17,7 @@ The adapter uses the LedgerClient which automatically handles authentication and
17
17
 
18
18
  The LedgerClient will automatically:
19
19
  - Use the WAYFINDER_API_URL from settings
20
- - Handle authentication via environment variables or config.json
20
+ - Handle authentication via config.json
21
21
  - Manage token refresh and retry logic
22
22
 
23
23
  ## Usage