wayfinder-paths 0.1.13__py3-none-any.whl → 0.1.15__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 (61) hide show
  1. wayfinder_paths/adapters/balance_adapter/README.md +13 -14
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +73 -32
  3. wayfinder_paths/adapters/balance_adapter/test_adapter.py +123 -0
  4. wayfinder_paths/adapters/brap_adapter/README.md +11 -16
  5. wayfinder_paths/adapters/brap_adapter/adapter.py +144 -78
  6. wayfinder_paths/adapters/brap_adapter/examples.json +63 -52
  7. wayfinder_paths/adapters/brap_adapter/test_adapter.py +127 -65
  8. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +30 -14
  9. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +121 -67
  10. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +6 -6
  11. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +12 -12
  12. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +6 -6
  13. wayfinder_paths/adapters/moonwell_adapter/adapter.py +332 -9
  14. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +13 -13
  15. wayfinder_paths/adapters/pool_adapter/README.md +9 -10
  16. wayfinder_paths/adapters/pool_adapter/adapter.py +9 -10
  17. wayfinder_paths/adapters/pool_adapter/test_adapter.py +2 -2
  18. wayfinder_paths/adapters/token_adapter/README.md +2 -14
  19. wayfinder_paths/adapters/token_adapter/adapter.py +16 -10
  20. wayfinder_paths/adapters/token_adapter/examples.json +4 -8
  21. wayfinder_paths/adapters/token_adapter/test_adapter.py +9 -7
  22. wayfinder_paths/core/clients/BRAPClient.py +102 -61
  23. wayfinder_paths/core/clients/ClientManager.py +1 -68
  24. wayfinder_paths/core/clients/HyperlendClient.py +125 -64
  25. wayfinder_paths/core/clients/LedgerClient.py +1 -4
  26. wayfinder_paths/core/clients/PoolClient.py +122 -48
  27. wayfinder_paths/core/clients/TokenClient.py +91 -36
  28. wayfinder_paths/core/clients/WalletClient.py +26 -56
  29. wayfinder_paths/core/clients/WayfinderClient.py +28 -160
  30. wayfinder_paths/core/clients/__init__.py +0 -2
  31. wayfinder_paths/core/clients/protocols.py +35 -46
  32. wayfinder_paths/core/clients/sdk_example.py +37 -22
  33. wayfinder_paths/core/constants/erc20_abi.py +0 -11
  34. wayfinder_paths/core/engine/StrategyJob.py +10 -56
  35. wayfinder_paths/core/services/base.py +1 -0
  36. wayfinder_paths/core/services/local_evm_txn.py +25 -9
  37. wayfinder_paths/core/services/local_token_txn.py +2 -6
  38. wayfinder_paths/core/services/test_local_evm_txn.py +145 -0
  39. wayfinder_paths/core/strategies/Strategy.py +16 -4
  40. wayfinder_paths/core/utils/evm_helpers.py +2 -9
  41. wayfinder_paths/policies/erc20.py +1 -1
  42. wayfinder_paths/run_strategy.py +13 -19
  43. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +77 -11
  44. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +6 -6
  45. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +107 -23
  46. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +54 -9
  47. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/README.md +6 -5
  48. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +2246 -1279
  49. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +276 -109
  50. wayfinder_paths/strategies/stablecoin_yield_strategy/README.md +1 -1
  51. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +153 -56
  52. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +16 -12
  53. wayfinder_paths/templates/adapter/README.md +1 -1
  54. wayfinder_paths/templates/strategy/README.md +3 -3
  55. wayfinder_paths/templates/strategy/test_strategy.py +3 -2
  56. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/METADATA +14 -49
  57. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/RECORD +59 -60
  58. wayfinder_paths/abis/generic/erc20.json +0 -383
  59. wayfinder_paths/core/clients/AuthClient.py +0 -83
  60. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/LICENSE +0 -0
  61. {wayfinder_paths-0.1.13.dist-info → wayfinder_paths-0.1.15.dist-info}/WHEEL +0 -0
@@ -16,6 +16,7 @@ from eth_utils import to_checksum_address
16
16
  from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
17
17
  from wayfinder_paths.core.clients.TokenClient import TokenClient
18
18
  from wayfinder_paths.core.constants.base import DEFAULT_TRANSACTION_TIMEOUT
19
+ from wayfinder_paths.core.constants.erc20_abi import ERC20_ABI
19
20
  from wayfinder_paths.core.constants.moonwell_abi import (
20
21
  COMPTROLLER_ABI,
21
22
  MTOKEN_ABI,
@@ -57,6 +58,11 @@ CF_CACHE_TTL = 3600
57
58
  DEFAULT_MAX_RETRIES = 5
58
59
  DEFAULT_BASE_DELAY = 3.0 # seconds
59
60
 
61
+ # Compound-style Failure(uint256,uint256,uint256) topic0
62
+ FAILURE_EVENT_TOPIC0 = (
63
+ "0x45b96fe442630264581b197e84bbada861235052c5a1aadfff9ea4e40a969aa0"
64
+ )
65
+
60
66
 
61
67
  def _is_rate_limit_error(error: Exception | str) -> bool:
62
68
  """Check if an error is a rate limit (429) error."""
@@ -163,6 +169,79 @@ class MoonwellAdapter(BaseAdapter):
163
169
  # Public API - Lending Operations #
164
170
  # ------------------------------------------------------------------ #
165
171
 
172
+ def _tx_pinned_block(self, result: Any) -> int | None:
173
+ if isinstance(result, dict):
174
+ return (
175
+ result.get("confirmed_block_number")
176
+ or result.get("block_number")
177
+ or (result.get("receipt", {}) or {}).get("blockNumber")
178
+ )
179
+ return None
180
+
181
+ def _as_bytes(self, value: Any) -> bytes | None:
182
+ if value is None:
183
+ return None
184
+ if isinstance(value, (bytes, bytearray)):
185
+ return bytes(value)
186
+ if hasattr(value, "hex"):
187
+ try:
188
+ hex_str = value.hex()
189
+ if isinstance(hex_str, str):
190
+ if hex_str.startswith("0x"):
191
+ return bytes.fromhex(hex_str[2:])
192
+ return bytes.fromhex(hex_str)
193
+ except Exception:
194
+ return None
195
+ if isinstance(value, str):
196
+ v = value
197
+ if v.startswith("0x"):
198
+ v = v[2:]
199
+ try:
200
+ return bytes.fromhex(v)
201
+ except Exception:
202
+ return None
203
+ return None
204
+
205
+ def _failure_event_details(
206
+ self, result: Any, contract_address: str
207
+ ) -> dict[str, int] | None:
208
+ if not isinstance(result, dict):
209
+ return None
210
+
211
+ receipt = (
212
+ result.get("receipt") if isinstance(result.get("receipt"), dict) else {}
213
+ )
214
+ logs = receipt.get("logs") if isinstance(receipt, dict) else None
215
+ if not isinstance(logs, list):
216
+ return None
217
+
218
+ addr_l = str(contract_address or "").lower()
219
+ for log in logs:
220
+ if not isinstance(log, dict):
221
+ continue
222
+ if str(log.get("address") or "").lower() != addr_l:
223
+ continue
224
+ topics = log.get("topics") or []
225
+ if not topics:
226
+ continue
227
+ topic0_bytes = self._as_bytes(topics[0])
228
+ if not topic0_bytes:
229
+ continue
230
+ if topic0_bytes.hex() != FAILURE_EVENT_TOPIC0.lower().removeprefix("0x"):
231
+ continue
232
+
233
+ data_b = self._as_bytes(log.get("data"))
234
+ if not data_b or len(data_b) < 96:
235
+ return {"error": 0, "info": 0, "detail": 0}
236
+
237
+ return {
238
+ "error": int.from_bytes(data_b[0:32], "big"),
239
+ "info": int.from_bytes(data_b[32:64], "big"),
240
+ "detail": int.from_bytes(data_b[64:96], "big"),
241
+ }
242
+
243
+ return None
244
+
166
245
  async def lend(
167
246
  self,
168
247
  *,
@@ -170,7 +249,11 @@ class MoonwellAdapter(BaseAdapter):
170
249
  underlying_token: str,
171
250
  amount: int,
172
251
  ) -> tuple[bool, Any]:
173
- """Supply tokens to Moonwell by minting mTokens."""
252
+ """Supply tokens to Moonwell by minting mTokens.
253
+
254
+ Note: mint() returns an error code and can emit Failure without reverting.
255
+ We verify success by checking that the mToken balance increased.
256
+ """
174
257
  strategy = self._strategy_address()
175
258
  amount = int(amount)
176
259
  if amount <= 0:
@@ -179,6 +262,17 @@ class MoonwellAdapter(BaseAdapter):
179
262
  mtoken = self._checksum(mtoken)
180
263
  underlying_token = self._checksum(underlying_token)
181
264
 
265
+ mtoken_bal_before = None
266
+ if self.web3 and not self.simulation:
267
+ try:
268
+ web3 = self.web3.get_web3(self.chain_id)
269
+ mtoken_contract = web3.eth.contract(address=mtoken, abi=MTOKEN_ABI)
270
+ mtoken_bal_before = await mtoken_contract.functions.balanceOf(
271
+ strategy
272
+ ).call()
273
+ except Exception:
274
+ mtoken_bal_before = None
275
+
182
276
  # Approve mToken to spend underlying tokens
183
277
  approved = await self._ensure_allowance(
184
278
  token_address=underlying_token,
@@ -197,7 +291,41 @@ class MoonwellAdapter(BaseAdapter):
197
291
  args=[amount],
198
292
  from_address=strategy,
199
293
  )
200
- return await self._execute(tx)
294
+ result = await self._execute(tx)
295
+
296
+ if not result[0] or not self.web3 or self.simulation:
297
+ return result
298
+
299
+ if isinstance(result[1], dict):
300
+ failure = self._failure_event_details(result[1], mtoken)
301
+ if failure is not None:
302
+ return (
303
+ False,
304
+ f"Mint failed (Failure event): error={failure['error']} info={failure['info']} detail={failure['detail']}",
305
+ )
306
+
307
+ if mtoken_bal_before is None:
308
+ return result
309
+
310
+ try:
311
+ pinned_block = self._tx_pinned_block(result[1])
312
+ block_id = pinned_block if pinned_block is not None else "latest"
313
+
314
+ web3 = self.web3.get_web3(self.chain_id)
315
+ mtoken_contract = web3.eth.contract(address=mtoken, abi=MTOKEN_ABI)
316
+ mtoken_bal_after = await mtoken_contract.functions.balanceOf(strategy).call(
317
+ block_identifier=block_id
318
+ )
319
+ if int(mtoken_bal_after) <= int(mtoken_bal_before):
320
+ return (
321
+ False,
322
+ f"Mint verification failed: mToken balance did not increase (before={mtoken_bal_before}, after={mtoken_bal_after})",
323
+ )
324
+ except Exception:
325
+ # If verification fails due to RPC/ABI issues, keep original result.
326
+ return result
327
+
328
+ return result
201
329
 
202
330
  async def unlend(
203
331
  self,
@@ -205,7 +333,13 @@ class MoonwellAdapter(BaseAdapter):
205
333
  mtoken: str,
206
334
  amount: int,
207
335
  ) -> tuple[bool, Any]:
208
- """Withdraw tokens from Moonwell by redeeming mTokens."""
336
+ """Withdraw tokens from Moonwell by redeeming mTokens.
337
+
338
+ Note: redeem() returns an error code and can emit Failure without reverting.
339
+ We verify success by checking that either:
340
+ - underlying wallet balance increased, or
341
+ - mToken wallet balance decreased.
342
+ """
209
343
  strategy = self._strategy_address()
210
344
  amount = int(amount)
211
345
  if amount <= 0:
@@ -213,6 +347,38 @@ class MoonwellAdapter(BaseAdapter):
213
347
 
214
348
  mtoken = self._checksum(mtoken)
215
349
 
350
+ mtoken_bal_before = None
351
+ underlying_addr = None
352
+ underlying_bal_before = None
353
+
354
+ if self.web3 and not self.simulation:
355
+ try:
356
+ web3 = self.web3.get_web3(self.chain_id)
357
+ mtoken_contract = web3.eth.contract(address=mtoken, abi=MTOKEN_ABI)
358
+
359
+ # Snapshot balances for verification.
360
+ mtoken_bal_before = await mtoken_contract.functions.balanceOf(
361
+ strategy
362
+ ).call()
363
+ try:
364
+ underlying_addr = (
365
+ await mtoken_contract.functions.underlying().call()
366
+ )
367
+ except Exception:
368
+ underlying_addr = None
369
+ if underlying_addr:
370
+ underlying_contract = web3.eth.contract(
371
+ address=to_checksum_address(underlying_addr),
372
+ abi=ERC20_ABI,
373
+ )
374
+ underlying_bal_before = (
375
+ await underlying_contract.functions.balanceOf(strategy).call()
376
+ )
377
+ except Exception:
378
+ mtoken_bal_before = None
379
+ underlying_addr = None
380
+ underlying_bal_before = None
381
+
216
382
  # Redeem mTokens for underlying
217
383
  tx = await self._encode_call(
218
384
  target=mtoken,
@@ -221,7 +387,60 @@ class MoonwellAdapter(BaseAdapter):
221
387
  args=[amount],
222
388
  from_address=strategy,
223
389
  )
224
- return await self._execute(tx)
390
+ result = await self._execute(tx)
391
+
392
+ if not result[0] or not self.web3 or self.simulation:
393
+ return result
394
+
395
+ if isinstance(result[1], dict):
396
+ failure = self._failure_event_details(result[1], mtoken)
397
+ if failure is not None:
398
+ return (
399
+ False,
400
+ f"Redeem failed (Failure event): error={failure['error']} info={failure['info']} detail={failure['detail']}",
401
+ )
402
+
403
+ if mtoken_bal_before is None:
404
+ return result
405
+
406
+ try:
407
+ pinned_block = self._tx_pinned_block(result[1])
408
+ block_id = pinned_block if pinned_block is not None else "latest"
409
+
410
+ web3 = self.web3.get_web3(self.chain_id)
411
+ mtoken_contract = web3.eth.contract(address=mtoken, abi=MTOKEN_ABI)
412
+ mtoken_bal_after = await mtoken_contract.functions.balanceOf(strategy).call(
413
+ block_identifier=block_id
414
+ )
415
+
416
+ underlying_bal_after = None
417
+ if underlying_addr:
418
+ underlying_contract = web3.eth.contract(
419
+ address=to_checksum_address(underlying_addr),
420
+ abi=ERC20_ABI,
421
+ )
422
+ underlying_bal_after = await underlying_contract.functions.balanceOf(
423
+ strategy
424
+ ).call(block_identifier=block_id)
425
+
426
+ mtoken_decreased = int(mtoken_bal_after) < int(mtoken_bal_before)
427
+ underlying_increased = (
428
+ underlying_bal_before is not None
429
+ and underlying_bal_after is not None
430
+ and int(underlying_bal_after) > int(underlying_bal_before)
431
+ )
432
+
433
+ if not mtoken_decreased and not underlying_increased:
434
+ return (
435
+ False,
436
+ "Redeem verification failed: no observed balance change "
437
+ f"(mtoken before={mtoken_bal_before}, after={mtoken_bal_after}; "
438
+ f"underlying before={underlying_bal_before}, after={underlying_bal_after})",
439
+ )
440
+ except Exception:
441
+ return result
442
+
443
+ return result
225
444
 
226
445
  # ------------------------------------------------------------------ #
227
446
  # Public API - Borrowing Operations #
@@ -292,11 +511,20 @@ class MoonwellAdapter(BaseAdapter):
292
511
  # Verify the borrow actually succeeded by checking balance increased
293
512
  if self.web3:
294
513
  try:
514
+ pinned_block = None
515
+ if isinstance(result[1], dict):
516
+ pinned_block = (
517
+ result[1].get("confirmed_block_number")
518
+ or result[1].get("block_number")
519
+ or (result[1].get("receipt", {}) or {}).get("blockNumber")
520
+ )
521
+ block_id = pinned_block if pinned_block is not None else "latest"
522
+
295
523
  web3 = self.web3.get_web3(self.chain_id)
296
524
  mtoken_contract = web3.eth.contract(address=mtoken, abi=MTOKEN_ABI)
297
525
  borrow_after = await mtoken_contract.functions.borrowBalanceStored(
298
526
  strategy
299
- ).call()
527
+ ).call(block_identifier=block_id)
300
528
 
301
529
  # Borrow balance should have increased by approximately the amount
302
530
  # Allow for some interest accrual
@@ -349,6 +577,17 @@ class MoonwellAdapter(BaseAdapter):
349
577
  mtoken = self._checksum(mtoken)
350
578
  underlying_token = self._checksum(underlying_token)
351
579
 
580
+ borrow_before = None
581
+ if self.web3 and not self.simulation:
582
+ try:
583
+ web3 = self.web3.get_web3(self.chain_id)
584
+ mtoken_contract = web3.eth.contract(address=mtoken, abi=MTOKEN_ABI)
585
+ borrow_before = await mtoken_contract.functions.borrowBalanceStored(
586
+ strategy
587
+ ).call()
588
+ except Exception:
589
+ borrow_before = None
590
+
352
591
  # Approve mToken to spend underlying tokens for repayment
353
592
  # When repay_full=True, approve the amount we have, Moonwell will use only what's needed
354
593
  approved = await self._ensure_allowance(
@@ -370,7 +609,49 @@ class MoonwellAdapter(BaseAdapter):
370
609
  args=[repay_amount],
371
610
  from_address=strategy,
372
611
  )
373
- return await self._execute(tx)
612
+ result = await self._execute(tx)
613
+
614
+ if not result[0] or not self.web3 or self.simulation:
615
+ return result
616
+
617
+ if isinstance(result[1], dict):
618
+ failure = self._failure_event_details(result[1], mtoken)
619
+ if failure is not None:
620
+ return (
621
+ False,
622
+ f"Repay failed (Failure event): error={failure['error']} info={failure['info']} detail={failure['detail']}",
623
+ )
624
+
625
+ if borrow_before is None or int(borrow_before) <= 0:
626
+ return result
627
+
628
+ try:
629
+ pinned_block = self._tx_pinned_block(result[1])
630
+ block_id = pinned_block if pinned_block is not None else "latest"
631
+
632
+ web3 = self.web3.get_web3(self.chain_id)
633
+ mtoken_contract = web3.eth.contract(address=mtoken, abi=MTOKEN_ABI)
634
+ borrow_after = await mtoken_contract.functions.borrowBalanceStored(
635
+ strategy
636
+ ).call(block_identifier=block_id)
637
+
638
+ if repay_full:
639
+ # Full repayment should clear the borrow balance (allow 1 wei dust).
640
+ if int(borrow_after) > 1:
641
+ return (
642
+ False,
643
+ f"Repay verification failed: repay_full did not clear debt (before={borrow_before}, after={borrow_after})",
644
+ )
645
+ else:
646
+ if int(borrow_after) >= int(borrow_before):
647
+ return (
648
+ False,
649
+ f"Repay verification failed: borrow balance did not decrease (before={borrow_before}, after={borrow_after})",
650
+ )
651
+ except Exception:
652
+ return result
653
+
654
+ return result
374
655
 
375
656
  # ------------------------------------------------------------------ #
376
657
  # Public API - Collateral Management #
@@ -404,13 +685,22 @@ class MoonwellAdapter(BaseAdapter):
404
685
  # Verify the market was actually entered
405
686
  if self.web3:
406
687
  try:
688
+ pinned_block = None
689
+ if isinstance(result[1], dict):
690
+ pinned_block = (
691
+ result[1].get("confirmed_block_number")
692
+ or result[1].get("block_number")
693
+ or (result[1].get("receipt", {}) or {}).get("blockNumber")
694
+ )
695
+ block_id = pinned_block if pinned_block is not None else "latest"
696
+
407
697
  web3 = self.web3.get_web3(self.chain_id)
408
698
  comptroller = web3.eth.contract(
409
699
  address=self.comptroller_address, abi=COMPTROLLER_ABI
410
700
  )
411
701
  is_member = await comptroller.functions.checkMembership(
412
702
  strategy, mtoken
413
- ).call()
703
+ ).call(block_identifier=block_id)
414
704
 
415
705
  if not is_member:
416
706
  from loguru import logger
@@ -430,6 +720,31 @@ class MoonwellAdapter(BaseAdapter):
430
720
 
431
721
  return result
432
722
 
723
+ async def is_market_entered(
724
+ self,
725
+ *,
726
+ mtoken: str,
727
+ account: str | None = None,
728
+ ) -> tuple[bool, bool | str]:
729
+ """Check whether an account has entered a given market (as collateral / borrowing market)."""
730
+ if self.simulation:
731
+ return True, True
732
+ if not self.web3:
733
+ return False, "web3 service not configured"
734
+
735
+ try:
736
+ acct = self._checksum(account) if account else self._strategy_address()
737
+ mtoken = self._checksum(mtoken)
738
+
739
+ web3 = self.web3.get_web3(self.chain_id)
740
+ comptroller = web3.eth.contract(
741
+ address=self.comptroller_address, abi=COMPTROLLER_ABI
742
+ )
743
+ is_member = await comptroller.functions.checkMembership(acct, mtoken).call()
744
+ return True, bool(is_member)
745
+ except Exception as exc:
746
+ return False, str(exc)
747
+
433
748
  async def remove_collateral(
434
749
  self,
435
750
  *,
@@ -1107,9 +1422,17 @@ class MoonwellAdapter(BaseAdapter):
1107
1422
 
1108
1423
  result = await self._broadcast_transaction(approve_tx)
1109
1424
 
1110
- # Small delay after approval to ensure state is propagated
1425
+ # Small delay after approval to ensure state is propagated on providers/chains
1426
+ # where we don't wait for additional confirmations by default.
1111
1427
  if result[0]:
1112
- await asyncio.sleep(1.0)
1428
+ confirmations = 0
1429
+ if isinstance(result[1], dict):
1430
+ try:
1431
+ confirmations = int(result[1].get("confirmations") or 0)
1432
+ except (TypeError, ValueError):
1433
+ confirmations = 0
1434
+ if confirmations == 0:
1435
+ await asyncio.sleep(1.0)
1113
1436
 
1114
1437
  return result
1115
1438
 
@@ -103,7 +103,7 @@ class TestMoonwellAdapter:
103
103
  amount=10**6,
104
104
  )
105
105
 
106
- assert success is True
106
+ assert success
107
107
  assert "simulation" in result
108
108
 
109
109
  @pytest.mark.asyncio
@@ -137,7 +137,7 @@ class TestMoonwellAdapter:
137
137
  amount=10**8,
138
138
  )
139
139
 
140
- assert success is True
140
+ assert success
141
141
  assert "simulation" in result
142
142
 
143
143
  @pytest.mark.asyncio
@@ -170,7 +170,7 @@ class TestMoonwellAdapter:
170
170
  amount=10**6,
171
171
  )
172
172
 
173
- assert success is True
173
+ assert success
174
174
  assert "simulation" in result
175
175
 
176
176
  @pytest.mark.asyncio
@@ -198,7 +198,7 @@ class TestMoonwellAdapter:
198
198
  amount=10**6,
199
199
  )
200
200
 
201
- assert success is True
201
+ assert success
202
202
  assert "simulation" in result
203
203
 
204
204
  @pytest.mark.asyncio
@@ -219,7 +219,7 @@ class TestMoonwellAdapter:
219
219
  mtoken=MOONWELL_DEFAULTS["m_wsteth"],
220
220
  )
221
221
 
222
- assert success is True
222
+ assert success
223
223
  assert "simulation" in result
224
224
 
225
225
  @pytest.mark.asyncio
@@ -250,7 +250,7 @@ class TestMoonwellAdapter:
250
250
 
251
251
  success, result = await adapter.claim_rewards()
252
252
 
253
- assert success is True
253
+ assert success
254
254
  assert isinstance(result, dict)
255
255
 
256
256
  @pytest.mark.asyncio
@@ -290,7 +290,7 @@ class TestMoonwellAdapter:
290
290
 
291
291
  success, result = await adapter.get_pos(mtoken=MOONWELL_DEFAULTS["m_usdc"])
292
292
 
293
- assert success is True
293
+ assert success
294
294
  assert "mtoken_balance" in result
295
295
  assert "underlying_balance" in result
296
296
  assert "borrow_balance" in result
@@ -316,7 +316,7 @@ class TestMoonwellAdapter:
316
316
  mtoken=MOONWELL_DEFAULTS["m_wsteth"]
317
317
  )
318
318
 
319
- assert success is True
319
+ assert success
320
320
  assert result == 0.75
321
321
 
322
322
  @pytest.mark.asyncio
@@ -465,7 +465,7 @@ class TestMoonwellAdapter:
465
465
  mtoken=MOONWELL_DEFAULTS["m_usdc"], apy_type="supply", include_rewards=False
466
466
  )
467
467
 
468
- assert success is True
468
+ assert success
469
469
  assert isinstance(result, float)
470
470
  assert result >= 0
471
471
 
@@ -502,7 +502,7 @@ class TestMoonwellAdapter:
502
502
  mtoken=MOONWELL_DEFAULTS["m_usdc"], apy_type="borrow", include_rewards=False
503
503
  )
504
504
 
505
- assert success is True
505
+ assert success
506
506
  assert isinstance(result, float)
507
507
  assert result >= 0
508
508
 
@@ -523,7 +523,7 @@ class TestMoonwellAdapter:
523
523
 
524
524
  success, result = await adapter.get_borrowable_amount()
525
525
 
526
- assert success is True
526
+ assert success
527
527
  assert result == 10**18
528
528
 
529
529
  @pytest.mark.asyncio
@@ -559,7 +559,7 @@ class TestMoonwellAdapter:
559
559
 
560
560
  success, result = await adapter.wrap_eth(amount=10**18)
561
561
 
562
- assert success is True
562
+ assert success
563
563
  assert "simulation" in result
564
564
 
565
565
  def test_strategy_address_missing(self):
@@ -630,6 +630,6 @@ class TestMoonwellAdapter:
630
630
  mtoken=MOONWELL_DEFAULTS["m_usdc"]
631
631
  )
632
632
 
633
- assert success is True
633
+ assert success
634
634
  assert result["cTokens_raw"] == 0
635
635
  assert result["underlying_raw"] == 0
@@ -44,30 +44,29 @@ else:
44
44
  print(f"Error: {data}")
45
45
  ```
46
46
 
47
- ### Get Llama Matches
47
+ ### Get pools (all or filtered)
48
+
49
+ Pass at least `chain_id` when fetching all pools (e.g. `chain_id=8453` for Base):
48
50
 
49
51
  ```python
50
- success, data = await adapter.get_pools()
52
+ success, data = await adapter.get_pools(chain_id=8453)
51
53
  if success:
52
54
  matches = data.get("matches", [])
53
- print(f"Found {len(matches)} Llama matches")
54
55
  for match in matches:
55
56
  if match.get("stablecoin"):
56
- print(f"Stablecoin pool: {match.get('id')} - APY: {match.get('apy')}%")
57
+ print(f"Pool {match.get('id')} - APY: {match.get('apy')}%")
57
58
  else:
58
59
  print(f"Error: {data}")
59
60
  ```
60
61
 
61
- ## Advanced Usage
62
+ Optional: `project="lido"` to filter by project.
62
63
 
63
64
  ## API Endpoints
64
65
 
65
- The adapter uses the following Wayfinder API endpoints:
66
+ The adapter uses the Wayfinder API:
66
67
 
67
- - `GET /api/v1/public/pools/?pool_ids=X` - Get pools by IDs
68
- - `GET /api/v1/public/pools/` - Get all pools
69
- - `GET /api/v1/public/pools/llama/matches/` - Get Llama matches
70
- - `GET /api/v1/public/pools/llama/reports/` - Get Llama reports
68
+ - `GET /v1/blockchain/pools/?chain_id=1&project=lido` - List pools (filter by chain_id, optional project)
69
+ - `POST /v1/blockchain/pools/` - Get pools by IDs (body: `{"pool_ids": ["id1", "id2"]}`)
71
70
 
72
71
  ## Error Handling
73
72
 
@@ -2,7 +2,7 @@ from typing import Any
2
2
 
3
3
  from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
4
4
  from wayfinder_paths.core.clients.PoolClient import (
5
- LlamaMatch,
5
+ LlamaMatchesResponse,
6
6
  PoolClient,
7
7
  PoolList,
8
8
  )
@@ -48,16 +48,15 @@ class PoolAdapter(BaseAdapter):
48
48
  self.logger.error(f"Error fetching pools by IDs: {e}")
49
49
  return (False, str(e))
50
50
 
51
- async def get_pools(self) -> tuple[bool, dict[str, LlamaMatch] | str]:
52
- """
53
- Get Llama protocol matches for pools.
54
-
55
- Returns:
56
- Tuple of (success, data) where data is Llama matches or error message
57
- """
51
+ async def get_pools(
52
+ self,
53
+ *,
54
+ chain_id: int | None = None,
55
+ project: str | None = None,
56
+ ) -> tuple[bool, LlamaMatchesResponse | str]:
58
57
  try:
59
- data = await self.pool_client.get_pools()
58
+ data = await self.pool_client.get_pools(chain_id=chain_id, project=project)
60
59
  return (True, data)
61
60
  except Exception as e:
62
- self.logger.error(f"Error fetching Llama matches: {e}")
61
+ self.logger.error(f"Error fetching pools: {e}")
63
62
  return (False, str(e))
@@ -41,7 +41,7 @@ class TestPoolAdapter:
41
41
  pool_ids=["pool-123", "pool-456"]
42
42
  )
43
43
 
44
- assert success is True
44
+ assert success
45
45
  assert data == mock_response
46
46
  mock_pool_client.get_pools_by_ids.assert_called_once_with(
47
47
  pool_ids=["pool-123", "pool-456"]
@@ -66,7 +66,7 @@ class TestPoolAdapter:
66
66
 
67
67
  success, data = await adapter.get_pools()
68
68
 
69
- assert success is True
69
+ assert success
70
70
  assert data == mock_response
71
71
 
72
72
  @pytest.mark.asyncio
@@ -63,23 +63,11 @@ else:
63
63
  print(f"Error: {data}")
64
64
  ```
65
65
 
66
- ### Get Token (Flexible)
67
-
68
- ```python
69
- # Try by address first, then by token_id
70
- success, data = await adapter.get_token(address="0x1234...", token_id="token-123")
71
- if success:
72
- print(f"Token data: {data}")
73
- else:
74
- print(f"Error: {data}")
75
- ```
76
-
77
66
  ## API Endpoints
78
67
 
79
- The adapter uses the following existing public API endpoints:
68
+ The adapter uses the following Wayfinder API endpoint:
80
69
 
81
- 1. **Token Details** (by address): `GET /public/tokens/detail/?query={address}&get_chart=false`
82
- 2. **Token by ID**: `GET /public/tokens/lookup/?token_id={token_id}`
70
+ - `GET /api/v1/blockchain/tokens/detail/?query=...&market_data=...&chain_id=...`
83
71
 
84
72
  ## Error Handling
85
73