wayfinder-paths 0.1.15__py3-none-any.whl → 0.1.17__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 (47) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +19 -20
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +66 -37
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +2 -8
  4. wayfinder_paths/adapters/brap_adapter/README.md +22 -19
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +33 -34
  6. wayfinder_paths/adapters/brap_adapter/test_adapter.py +2 -18
  7. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +45 -59
  8. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +1 -8
  9. wayfinder_paths/adapters/moonwell_adapter/README.md +29 -31
  10. wayfinder_paths/adapters/moonwell_adapter/adapter.py +301 -662
  11. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +275 -179
  12. wayfinder_paths/core/config.py +8 -47
  13. wayfinder_paths/core/constants/base.py +0 -1
  14. wayfinder_paths/core/constants/erc20_abi.py +13 -13
  15. wayfinder_paths/core/strategies/Strategy.py +6 -2
  16. wayfinder_paths/core/utils/erc20_service.py +100 -0
  17. wayfinder_paths/core/utils/evm_helpers.py +1 -1
  18. wayfinder_paths/core/utils/transaction.py +191 -0
  19. wayfinder_paths/core/utils/web3.py +66 -0
  20. wayfinder_paths/run_strategy.py +37 -6
  21. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +200 -224
  22. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +128 -151
  23. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/README.md +0 -1
  24. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +52 -78
  25. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +0 -12
  26. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +0 -1
  27. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +39 -64
  28. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +0 -1
  29. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +42 -85
  30. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +0 -8
  31. wayfinder_paths/templates/strategy/README.md +1 -5
  32. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.17.dist-info}/METADATA +3 -41
  33. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.17.dist-info}/RECORD +35 -44
  34. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.17.dist-info}/WHEEL +1 -1
  35. wayfinder_paths/core/clients/sdk_example.py +0 -125
  36. wayfinder_paths/core/engine/__init__.py +0 -5
  37. wayfinder_paths/core/services/__init__.py +0 -0
  38. wayfinder_paths/core/services/base.py +0 -131
  39. wayfinder_paths/core/services/local_evm_txn.py +0 -350
  40. wayfinder_paths/core/services/local_token_txn.py +0 -238
  41. wayfinder_paths/core/services/web3_service.py +0 -43
  42. wayfinder_paths/core/wallets/README.md +0 -88
  43. wayfinder_paths/core/wallets/WalletManager.py +0 -56
  44. wayfinder_paths/core/wallets/__init__.py +0 -7
  45. wayfinder_paths/scripts/run_strategy.py +0 -152
  46. wayfinder_paths/strategies/config.py +0 -85
  47. {wayfinder_paths-0.1.15.dist-info → wayfinder_paths-0.1.17.dist-info}/LICENSE +0 -0
@@ -7,43 +7,42 @@ Adapter that exposes wallet, token, and pool balances backed by `WalletClient`/`
7
7
 
8
8
  ## Capabilities
9
9
 
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.
10
+ The adapter provides both wallet read and wallet transfer capabilities. Ledger recording + wallet selection now live inside the adapter.
11
11
 
12
12
  ## Construction
13
13
 
14
14
  ```python
15
- from wayfinder_paths.core.services.web3_service import DefaultWeb3Service
16
15
  from wayfinder_paths.adapters.balance_adapter.adapter import BalanceAdapter
17
16
 
18
- web3_service = DefaultWeb3Service(config)
19
- balance = BalanceAdapter(config, web3_service=web3_service)
20
17
  ```
21
18
 
22
- `web3_service` is required so the adapter can share the same wallet provider (and `TokenTxn` helper) as the rest of the strategy.
23
-
24
19
  ## API surface
25
20
 
26
- ### `get_balance(*, query: str | dict, wallet_address: str, chain_id: int | None = None)`
27
- Returns the raw balance (as an integer) for a specific token or pool on a wallet.
21
+ ### `get_balance(token_id: str, wallet_address: str)`
28
22
 
29
- `query`: token_id/address string or a dict with a `"token_id"` key. When `query` is a token identifier (e.g. `"usd-coin-base"`), `chain_id` is auto-resolved from token info; when it is a pool address, `chain_id` must be provided.
23
+ Returns the raw balance (as an integer) for a specific token on a wallet.
30
24
 
31
25
  ```python
32
- # Token balance (chain_id auto-resolved)
33
26
  success, balance = await balance.get_balance(
34
- query="usd-coin-base",
27
+ token_id="usd-coin-base",
35
28
  wallet_address=config["main_wallet"]["address"],
36
29
  )
30
+ ```
31
+
32
+ ### `get_pool_balance(pool_address: str, chain_id: int, user_address: str)`
37
33
 
38
- # Pool balance (chain_id required)
39
- success, pool_balance = await balance.get_balance(
40
- query="0xPool...",
41
- wallet_address=config["strategy_wallet"]["address"],
34
+ Fetches the amount supplied to a specific pool, using the `/wallets/pool-balance` endpoint.
35
+
36
+ ```python
37
+ success, amount = await balance.get_pool_balance(
38
+ pool_address="0xPool",
42
39
  chain_id=8453,
40
+ user_address=config["strategy_wallet"]["address"],
43
41
  )
44
42
  ```
45
43
 
46
44
  ### `move_from_main_wallet_to_strategy_wallet(token_id: str, amount: float, strategy_name="unknown", skip_ledger=False)`
45
+
47
46
  Sends the specified token from the configured `main_wallet` to the strategy wallet, records the ledger deposit (unless `skip_ledger=True`), and returns the `(success, tx_result)` tuple from the underlying send helper.
48
47
 
49
48
  ```python
@@ -55,6 +54,7 @@ success, tx = await balance.move_from_main_wallet_to_strategy_wallet(
55
54
  ```
56
55
 
57
56
  ### `move_from_strategy_wallet_to_main_wallet(token_id: str, amount: float, strategy_name="unknown", skip_ledger=False)`
57
+
58
58
  Mirrors the previous method but withdraws from the strategy wallet back to the main wallet while recording a ledger withdrawal entry.
59
59
 
60
60
  ```python
@@ -73,16 +73,15 @@ All methods return `(success: bool, payload: Any)` tuples. On failure the payloa
73
73
  class MyStrategy(Strategy):
74
74
  def __init__(self, config):
75
75
  super().__init__()
76
- web3_service = DefaultWeb3Service(config)
77
- balance_adapter = BalanceAdapter(config, web3_service=web3_service)
76
+ balance_adapter = BalanceAdapter(config)
78
77
  self.register_adapters([balance_adapter])
79
78
  self.balance_adapter = balance_adapter
80
79
 
81
80
  async def _status(self):
82
- success, pool_balance = await self.balance_adapter.get_balance(
83
- query=self.current_pool["address"],
84
- wallet_address=self.config["strategy_wallet"]["address"],
81
+ success, pool_balance = await self.balance_adapter.get_pool_balance(
82
+ pool_address=self.current_pool["address"],
85
83
  chain_id=self.current_pool["chain"]["id"],
84
+ user_address=self.config["strategy_wallet"]["address"],
86
85
  )
87
86
  return {"portfolio_value": float(pool_balance or 0), ...}
88
87
  ```
@@ -5,9 +5,9 @@ from wayfinder_paths.adapters.token_adapter.adapter import TokenAdapter
5
5
  from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
6
6
  from wayfinder_paths.core.clients.TokenClient import TokenClient
7
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
8
+ from wayfinder_paths.core.utils.erc20_service import build_send_transaction
10
9
  from wayfinder_paths.core.utils.evm_helpers import resolve_chain_id
10
+ from wayfinder_paths.core.utils.transaction import send_transaction
11
11
 
12
12
 
13
13
  class BalanceAdapter(BaseAdapter):
@@ -16,17 +16,19 @@ class BalanceAdapter(BaseAdapter):
16
16
  def __init__(
17
17
  self,
18
18
  config: dict[str, Any],
19
- web3_service: Web3Service,
19
+ simulation: bool = False,
20
+ main_wallet_signing_callback=None,
21
+ strategy_wallet_signing_callback=None,
20
22
  ):
21
23
  super().__init__("balance", config)
24
+ self.simulation = simulation
25
+ self.main_wallet_signing_callback = main_wallet_signing_callback
26
+ self.strategy_wallet_signing_callback = strategy_wallet_signing_callback
22
27
  self.wallet_client = WalletClient()
23
28
  self.token_client = TokenClient()
24
29
  self.token_adapter = TokenAdapter()
25
30
  self.ledger_adapter = LedgerAdapter()
26
31
 
27
- self.wallet_provider = web3_service.evm_transactions
28
- self.token_transactions = web3_service.token_transactions
29
-
30
32
  def _parse_balance(self, raw: Any) -> int:
31
33
  """Parse balance value to integer, handling various formats."""
32
34
  if raw is None:
@@ -42,15 +44,23 @@ class BalanceAdapter(BaseAdapter):
42
44
  async def get_balance(
43
45
  self,
44
46
  *,
45
- query: str | dict[str, Any],
47
+ query: str | dict[str, Any] | None = None,
48
+ token_id: str | None = None,
46
49
  wallet_address: str,
47
50
  chain_id: int | None = None,
48
51
  ) -> tuple[bool, str | int]:
49
52
  """Get token or pool balance for a wallet.
50
53
 
51
54
  query: token_id/address string or a dict with a "token_id" key.
55
+ token_id: alternative to query for convenience.
52
56
  """
53
- resolved = query if isinstance(query, str) else (query or {}).get("token_id")
57
+ # Support both query= and token_id= for caller convenience
58
+ effective_query = query if query is not None else token_id
59
+ resolved = (
60
+ effective_query
61
+ if isinstance(effective_query, str)
62
+ else (effective_query or {}).get("token_id")
63
+ )
54
64
  if not resolved:
55
65
  return (False, "missing query")
56
66
  try:
@@ -119,15 +129,13 @@ class BalanceAdapter(BaseAdapter):
119
129
  async def send_to_address(
120
130
  self,
121
131
  token_id: str,
122
- amount: float,
132
+ amount: int,
123
133
  from_wallet: dict[str, Any] | None,
124
134
  to_address: str,
135
+ signing_callback=None,
125
136
  skip_ledger: bool = True,
126
137
  ) -> tuple[bool, Any]:
127
138
  """Send tokens from a wallet to an arbitrary address (e.g., bridge contract)."""
128
- if self.token_transactions is None:
129
- return False, "Token transaction service not configured"
130
-
131
139
  from_address = self._wallet_address(from_wallet)
132
140
  if not from_address:
133
141
  return False, "from_wallet missing or invalid"
@@ -139,22 +147,28 @@ class BalanceAdapter(BaseAdapter):
139
147
  if not token_info:
140
148
  return False, f"Token not found: {token_id}"
141
149
 
142
- build_success, tx_data = await self.token_transactions.build_send(
143
- token_id=token_id,
144
- amount=amount,
150
+ chain_id = resolve_chain_id(token_info, self.logger)
151
+ if chain_id is None:
152
+ return False, f"Token {token_id} is missing chain_id"
153
+
154
+ token_address = token_info.get("address")
155
+
156
+ tx = await build_send_transaction(
145
157
  from_address=from_address,
146
158
  to_address=to_address,
147
- token_info=token_info,
159
+ token_address=token_address,
160
+ chain_id=chain_id,
161
+ amount=int(amount),
148
162
  )
149
- if not build_success:
150
- return False, tx_data
151
163
 
152
- tx = tx_data
153
- broadcast_result = await self.wallet_provider.broadcast_transaction(
154
- tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
155
- )
164
+ if self.simulation:
165
+ return True, {"simulation": tx}
156
166
 
157
- return broadcast_result
167
+ if not signing_callback:
168
+ return False, "signing_callback is required"
169
+
170
+ tx_hash = await send_transaction(tx, signing_callback)
171
+ return True, tx_hash
158
172
 
159
173
  async def _move_between_wallets(
160
174
  self,
@@ -168,9 +182,6 @@ class BalanceAdapter(BaseAdapter):
168
182
  strategy_name: str,
169
183
  skip_ledger: bool,
170
184
  ) -> tuple[bool, Any]:
171
- if self.token_transactions is None:
172
- return False, "Token transaction service not configured"
173
-
174
185
  from_address = self._wallet_address(from_wallet)
175
186
  to_address = self._wallet_address(to_wallet)
176
187
  if not from_address or not to_address:
@@ -180,20 +191,21 @@ class BalanceAdapter(BaseAdapter):
180
191
  if not token_info:
181
192
  return False, f"Token not found: {token_id}"
182
193
 
183
- build_success, tx_data = await self.token_transactions.build_send(
184
- token_id=token_id,
185
- amount=amount,
194
+ chain_id = resolve_chain_id(token_info, self.logger)
195
+ if chain_id is None:
196
+ return False, f"Token {token_id} is missing chain_id"
197
+
198
+ decimals = token_info.get("decimals", 18)
199
+ raw_amount = int(amount * (10**decimals))
200
+
201
+ tx = await build_send_transaction(
186
202
  from_address=from_address,
187
203
  to_address=to_address,
188
- token_info=token_info,
189
- )
190
- if not build_success:
191
- return False, tx_data
192
-
193
- tx = tx_data
194
- broadcast_result = await self.wallet_provider.broadcast_transaction(
195
- tx, wait_for_receipt=True, timeout=DEFAULT_TRANSACTION_TIMEOUT
204
+ token_address=token_info.get("address"),
205
+ chain_id=chain_id,
206
+ amount=raw_amount,
196
207
  )
208
+ broadcast_result = await self._send_tx(tx, from_address)
197
209
 
198
210
  if broadcast_result[0] and not skip_ledger and ledger_method is not None:
199
211
  wallet_for_ledger = from_address if ledger_wallet == "from" else to_address
@@ -207,6 +219,23 @@ class BalanceAdapter(BaseAdapter):
207
219
 
208
220
  return broadcast_result
209
221
 
222
+ async def _send_tx(self, tx: dict[str, Any], from_address: str) -> tuple[bool, Any]:
223
+ """Send transaction with simulation check, using appropriate signing callback."""
224
+ if self.simulation:
225
+ return True, {"simulation": tx}
226
+
227
+ # Choose callback based on which wallet is sending
228
+ main_wallet = self.config.get("main_wallet") or {}
229
+ main_addr = main_wallet.get("address", "").lower()
230
+
231
+ if from_address.lower() == main_addr:
232
+ callback = self.main_wallet_signing_callback
233
+ else:
234
+ callback = self.strategy_wallet_signing_callback
235
+
236
+ txn_hash = await send_transaction(tx, callback)
237
+ return True, txn_hash
238
+
210
239
  async def _record_ledger_entry(
211
240
  self,
212
241
  *,
@@ -21,13 +21,7 @@ class TestBalanceAdapter:
21
21
  return mock_client
22
22
 
23
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):
24
+ def adapter(self, mock_wallet_client, mock_token_client):
31
25
  """Create a BalanceAdapter instance with mocked clients for testing"""
32
26
  with (
33
27
  patch(
@@ -39,7 +33,7 @@ class TestBalanceAdapter:
39
33
  return_value=mock_token_client,
40
34
  ),
41
35
  ):
42
- return BalanceAdapter(config={}, web3_service=mock_web3_service)
36
+ return BalanceAdapter(config={})
43
37
 
44
38
  @pytest.mark.asyncio
45
39
  async def test_health_check(self, adapter):
@@ -13,9 +13,8 @@ A Wayfinder adapter that provides high-level operations for cross-chain swaps an
13
13
 
14
14
  ## Configuration
15
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
16
  The BRAPClient will automatically:
17
+
19
18
  - Use the WAYFINDER_API_URL from settings
20
19
  - Handle authentication via config.json
21
20
  - Manage token refresh and retry logic
@@ -25,11 +24,9 @@ The BRAPClient will automatically:
25
24
  ### Initialize the Adapter
26
25
 
27
26
  ```python
28
- from wayfinder_paths.core.services.web3_service import DefaultWeb3Service
29
27
  from wayfinder_paths.adapters.brap_adapter.adapter import BRAPAdapter
30
28
 
31
- web3_service = DefaultWeb3Service(config)
32
- adapter = BRAPAdapter(web3_service=web3_service)
29
+ adapter = BRAPAdapter()
33
30
  ```
34
31
 
35
32
  ### Get Swap Quote
@@ -46,9 +43,10 @@ success, data = await adapter.get_swap_quote(
46
43
  slippage=0.01 # 1% slippage
47
44
  )
48
45
  if success:
49
- best_quote = data.get("best_quote", {})
46
+ quotes = data.get("quotes", {})
47
+ best_quote = quotes.get("best_quote", {})
50
48
  print(f"Output amount: {best_quote.get('output_amount')}")
51
- print(f"Fee estimate: {best_quote.get('fee_estimate', {}).get('fee_total_usd')}")
49
+ print(f"Total fee: {best_quote.get('total_fee')}")
52
50
  else:
53
51
  print(f"Error: {data}")
54
52
  ```
@@ -67,7 +65,8 @@ success, data = await adapter.get_best_quote(
67
65
  )
68
66
  if success:
69
67
  print(f"Best output: {data.get('output_amount')}")
70
- print(f"Fee estimate (USD): {data.get('fee_estimate', {}).get('fee_total_usd')}")
68
+ print(f"Gas fee: {data.get('gas_fee')}")
69
+ print(f"Bridge fee: {data.get('bridge_fee')}")
71
70
  else:
72
71
  print(f"Error: {data}")
73
72
  ```
@@ -86,8 +85,10 @@ success, data = await adapter.calculate_swap_fees(
86
85
  if success:
87
86
  print(f"Input amount: {data.get('input_amount')}")
88
87
  print(f"Output amount: {data.get('output_amount')}")
89
- print(f"Gas estimate: {data.get('gas_fee')}")
90
- print(f"Total fee (USD): {data.get('total_fee')}")
88
+ print(f"Gas fee: {data.get('gas_fee')}")
89
+ print(f"Bridge fee: {data.get('bridge_fee')}")
90
+ print(f"Protocol fee: {data.get('protocol_fee')}")
91
+ print(f"Total fee: {data.get('total_fee')}")
91
92
  print(f"Price impact: {data.get('price_impact')}")
92
93
  else:
93
94
  print(f"Error: {data}")
@@ -106,9 +107,9 @@ success, data = await adapter.compare_routes(
106
107
  if success:
107
108
  print(f"Total routes available: {data.get('total_routes')}")
108
109
  print(f"Best route output: {data.get('best_route', {}).get('output_amount')}")
109
-
110
+
110
111
  for i, route in enumerate(data.get('all_routes', [])):
111
- print(f\"Route {i+1}: Output {route.get('output_amount')}, Fee USD {route.get('fee_estimate', {}).get('fee_total_usd')}\")
112
+ print(f"Route {i+1}: Output {route.get('output_amount')}, Fee {route.get('total_fee')}")
112
113
  else:
113
114
  print(f"Error: {data}")
114
115
  ```
@@ -166,7 +167,7 @@ success, data = await adapter.get_bridge_quote(
166
167
  slippage=0.01
167
168
  )
168
169
  if success:
169
- print(f"Bridge quote received: {data.get('best_quote', {}).get('output_amount')}")
170
+ print(f"Bridge quote received: {data.get('quotes', {}).get('best_quote', {}).get('output_amount')}")
170
171
  else:
171
172
  print(f"Error: {data}")
172
173
  ```
@@ -190,9 +191,10 @@ if success:
190
191
  highest_output = analysis.get("highest_output")
191
192
  lowest_fees = analysis.get("lowest_fees")
192
193
  fastest = analysis.get("fastest")
193
-
194
+
194
195
  print(f"Highest output route: {highest_output.get('output_amount')}")
195
- print(f\"Lowest fees route (USD): {lowest_fees.get('fee_estimate', {}).get('fee_total_usd')}\")
196
+ print(f"Lowest fees route: {lowest_fees.get('total_fee')}")
197
+ print(f"Fastest route: {fastest.get('estimated_time')} seconds")
196
198
  ```
197
199
 
198
200
  ### Fee Analysis
@@ -210,23 +212,24 @@ success, data = await adapter.calculate_swap_fees(
210
212
  if success:
211
213
  input_amount = int(data.get("input_amount", 0))
212
214
  output_amount = int(data.get("output_amount", 0))
213
- total_fee_usd = float(data.get("total_fee", 0))
214
-
215
+ total_fee = int(data.get("total_fee", 0))
216
+
215
217
  # Calculate effective rate
216
218
  effective_rate = (input_amount - output_amount) / input_amount
217
219
  print(f"Effective rate: {effective_rate:.4f} ({effective_rate * 100:.2f}%)")
218
- print(f"Total fees: ${total_fee_usd:.4f}")
220
+ print(f"Total fees: {total_fee / 1e18:.6f} tokens")
219
221
  ```
220
222
 
221
223
  ## API Endpoints
222
224
 
223
225
  The adapter uses the following Wayfinder API endpoints:
224
226
 
225
- - `GET /api/v1/blockchain/braps/quote/` - Get swap/bridge quotes
227
+ - `POST /api/v1/public/quotes/` - Get swap/bridge quotes
226
228
 
227
229
  ## Error Handling
228
230
 
229
231
  All methods return a tuple of `(success: bool, data: Any)` where:
232
+
230
233
  - `success` is `True` if the operation succeeded
231
234
  - `data` contains the response data on success or error message on failure
232
235
 
@@ -15,8 +15,11 @@ from wayfinder_paths.core.clients.BRAPClient import (
15
15
  from wayfinder_paths.core.clients.LedgerClient import TransactionRecord
16
16
  from wayfinder_paths.core.clients.TokenClient import TokenClient
17
17
  from wayfinder_paths.core.constants import DEFAULT_SLIPPAGE, ZERO_ADDRESS
18
- from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
19
- from wayfinder_paths.core.services.base import Web3Service
18
+ from wayfinder_paths.core.utils.erc20_service import (
19
+ build_approve_transaction,
20
+ get_token_allowance,
21
+ )
22
+ from wayfinder_paths.core.utils.transaction import send_transaction
20
23
 
21
24
  _NEEDS_CLEAR_APPROVAL = {
22
25
  (1, "0xdac17f958d2ee523a2206206994597c13d831ec7"),
@@ -41,17 +44,16 @@ class BRAPAdapter(BaseAdapter):
41
44
  def __init__(
42
45
  self,
43
46
  config: dict[str, Any] | None = None,
44
- *,
45
- web3_service: Web3Service,
47
+ simulation: bool = False,
48
+ strategy_wallet_signing_callback=None,
46
49
  ):
47
50
  super().__init__("brap_adapter", config)
51
+ self.simulation = simulation
52
+ self.strategy_wallet_signing_callback = strategy_wallet_signing_callback
48
53
  self.brap_client = BRAPClient()
49
54
  self.token_client = TokenClient()
50
55
  self.token_adapter = TokenAdapter()
51
56
  self.ledger_adapter = LedgerAdapter()
52
- self.web3_service = web3_service
53
- self.wallet_provider = web3_service.evm_transactions
54
- self.token_transactions = web3_service.token_transactions
55
57
 
56
58
  async def get_swap_quote(
57
59
  self,
@@ -438,6 +440,8 @@ class BRAPAdapter(BaseAdapter):
438
440
  if not transaction or not transaction.get("data"):
439
441
  return (False, "Quote missing calldata")
440
442
  transaction["chainId"] = chain_id
443
+ if "value" in transaction:
444
+ transaction["value"] = int(transaction["value"])
441
445
  # Always set the sender to the strategy wallet for broadcast.
442
446
  # (Calldata may include either "from" or "from_address" depending on provider.)
443
447
  transaction["from"] = to_checksum_address(from_address)
@@ -490,8 +494,10 @@ class BRAPAdapter(BaseAdapter):
490
494
  if not approve_success:
491
495
  return (False, approve_response)
492
496
 
493
- broadcast_success, broadcast_response = await self._broadcast_transaction(
494
- transaction
497
+ broadcast_success, broadcast_response = await self._send_tx(transaction)
498
+ self.logger.info(
499
+ f"Swap broadcast result: success={broadcast_success}, "
500
+ f"response={broadcast_response}"
495
501
  )
496
502
  # Log only key fields to avoid spamming raw HexBytes logs
497
503
  if isinstance(broadcast_response, dict):
@@ -729,46 +735,39 @@ class BRAPAdapter(BaseAdapter):
729
735
  spender_checksum = to_checksum_address(spender_address)
730
736
 
731
737
  if (chain_id, token_checksum.lower()) in _NEEDS_CLEAR_APPROVAL:
732
- allowance = await self.token_transactions.read_erc20_allowance(
733
- {"id": chain_id},
738
+ allowance = await get_token_allowance(
734
739
  token_checksum,
740
+ chain_id,
735
741
  owner_checksum,
736
742
  spender_checksum,
737
743
  )
738
- if allowance.get("allowance", 0) > 0:
739
- clear_success, clear_tx = self.token_transactions.build_erc20_approve(
744
+ if allowance > 0:
745
+ clear_tx = await build_approve_transaction(
746
+ from_address=owner_checksum,
740
747
  chain_id=chain_id,
741
748
  token_address=token_checksum,
742
- from_address=owner_checksum,
743
- spender=spender_checksum,
749
+ spender_address=spender_checksum,
744
750
  amount=0,
745
751
  )
746
- if not clear_success:
747
- return False, clear_tx
748
- clear_result = await self._broadcast_transaction(clear_tx)
752
+ clear_result = await self._send_tx(clear_tx)
749
753
  if not clear_result[0]:
750
754
  return clear_result
751
755
 
752
- build_success, approve_tx = self.token_transactions.build_erc20_approve(
756
+ approve_tx = await build_approve_transaction(
757
+ from_address=owner_checksum,
753
758
  chain_id=chain_id,
754
759
  token_address=token_checksum,
755
- from_address=owner_checksum,
756
- spender=spender_checksum,
760
+ spender_address=spender_checksum,
757
761
  amount=int(amount),
758
762
  )
759
- if not build_success:
760
- return False, approve_tx
761
- return await self._broadcast_transaction(approve_tx, confirmations=2)
762
-
763
- async def _broadcast_transaction(
764
- self, transaction: dict[str, Any], confirmations: int | None = None
765
- ) -> tuple[bool, Any]:
766
- return await self.wallet_provider.broadcast_transaction(
767
- transaction,
768
- wait_for_receipt=True,
769
- timeout=DEFAULT_TRANSACTION_TIMEOUT,
770
- confirmations=confirmations,
771
- )
763
+ return await self._send_tx(approve_tx)
764
+
765
+ async def _send_tx(self, tx: dict[str, Any]) -> tuple[bool, Any]:
766
+ """Send transaction with simulation check."""
767
+ if self.simulation:
768
+ return True, {"simulation": tx}
769
+ txn_hash = await send_transaction(tx, self.strategy_wallet_signing_callback)
770
+ return True, txn_hash
772
771
 
773
772
  async def _record_swap_operation(
774
773
  self,
@@ -1,4 +1,3 @@
1
- from types import SimpleNamespace
2
1
  from unittest.mock import AsyncMock
3
2
 
4
3
  import pytest
@@ -16,24 +15,9 @@ class TestBRAPAdapter:
16
15
  return mock_client
17
16
 
18
17
  @pytest.fixture
19
- def mock_web3_service(self):
20
- """Minimal Web3Service stub for adapter construction."""
21
- wallet_provider = SimpleNamespace(
22
- broadcast_transaction=AsyncMock(return_value=(True, {}))
23
- )
24
- token_txn = SimpleNamespace(
25
- build_send=AsyncMock(return_value=(True, {})),
26
- build_erc20_approve=AsyncMock(return_value=(True, {})),
27
- read_erc20_allowance=AsyncMock(return_value={"allowance": 0}),
28
- )
29
- return SimpleNamespace(
30
- evm_transactions=wallet_provider, token_transactions=token_txn
31
- )
32
-
33
- @pytest.fixture
34
- def adapter(self, mock_brap_client, mock_web3_service):
18
+ def adapter(self, mock_brap_client):
35
19
  """Create a BRAPAdapter instance with mocked client for testing"""
36
- adapter = BRAPAdapter(web3_service=mock_web3_service)
20
+ adapter = BRAPAdapter()
37
21
  adapter.brap_client = mock_brap_client
38
22
  return adapter
39
23