wayfinder-paths 0.1.23__py3-none-any.whl → 0.1.25__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 (124) hide show
  1. wayfinder_paths/__init__.py +2 -0
  2. wayfinder_paths/adapters/balance_adapter/adapter.py +250 -0
  3. wayfinder_paths/adapters/balance_adapter/manifest.yaml +8 -0
  4. wayfinder_paths/adapters/balance_adapter/test_adapter.py +0 -11
  5. wayfinder_paths/adapters/boros_adapter/__init__.py +17 -0
  6. wayfinder_paths/adapters/boros_adapter/adapter.py +1574 -0
  7. wayfinder_paths/adapters/boros_adapter/client.py +476 -0
  8. wayfinder_paths/adapters/boros_adapter/manifest.yaml +10 -0
  9. wayfinder_paths/adapters/boros_adapter/parsers.py +88 -0
  10. wayfinder_paths/adapters/boros_adapter/test_adapter.py +460 -0
  11. wayfinder_paths/adapters/boros_adapter/test_golden.py +156 -0
  12. wayfinder_paths/adapters/boros_adapter/types.py +70 -0
  13. wayfinder_paths/adapters/boros_adapter/utils.py +85 -0
  14. wayfinder_paths/adapters/brap_adapter/adapter.py +1 -1
  15. wayfinder_paths/adapters/brap_adapter/manifest.yaml +9 -0
  16. wayfinder_paths/adapters/hyperlend_adapter/adapter.py +161 -26
  17. wayfinder_paths/adapters/hyperlend_adapter/manifest.yaml +9 -0
  18. wayfinder_paths/adapters/hyperlend_adapter/test_adapter.py +77 -13
  19. wayfinder_paths/adapters/hyperliquid_adapter/__init__.py +2 -9
  20. wayfinder_paths/adapters/hyperliquid_adapter/adapter.py +585 -61
  21. wayfinder_paths/adapters/hyperliquid_adapter/executor.py +47 -68
  22. wayfinder_paths/adapters/hyperliquid_adapter/manifest.yaml +14 -0
  23. wayfinder_paths/adapters/hyperliquid_adapter/paired_filler.py +2 -3
  24. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter.py +17 -21
  25. wayfinder_paths/adapters/hyperliquid_adapter/test_adapter_live.py +3 -6
  26. wayfinder_paths/adapters/hyperliquid_adapter/test_executor.py +4 -8
  27. wayfinder_paths/adapters/hyperliquid_adapter/test_utils.py +2 -2
  28. wayfinder_paths/adapters/ledger_adapter/manifest.yaml +7 -0
  29. wayfinder_paths/adapters/ledger_adapter/test_adapter.py +1 -2
  30. wayfinder_paths/adapters/moonwell_adapter/adapter.py +592 -400
  31. wayfinder_paths/adapters/moonwell_adapter/manifest.yaml +14 -0
  32. wayfinder_paths/adapters/moonwell_adapter/test_adapter.py +126 -219
  33. wayfinder_paths/adapters/multicall_adapter/__init__.py +7 -0
  34. wayfinder_paths/adapters/multicall_adapter/adapter.py +166 -0
  35. wayfinder_paths/adapters/multicall_adapter/manifest.yaml +5 -0
  36. wayfinder_paths/adapters/multicall_adapter/test_adapter.py +97 -0
  37. wayfinder_paths/adapters/pendle_adapter/README.md +102 -0
  38. wayfinder_paths/adapters/pendle_adapter/__init__.py +7 -0
  39. wayfinder_paths/adapters/pendle_adapter/adapter.py +1992 -0
  40. wayfinder_paths/adapters/pendle_adapter/examples.json +11 -0
  41. wayfinder_paths/adapters/pendle_adapter/manifest.yaml +21 -0
  42. wayfinder_paths/adapters/pendle_adapter/test_adapter.py +666 -0
  43. wayfinder_paths/adapters/pool_adapter/manifest.yaml +6 -0
  44. wayfinder_paths/adapters/token_adapter/examples.json +0 -4
  45. wayfinder_paths/adapters/token_adapter/manifest.yaml +7 -0
  46. wayfinder_paths/conftest.py +24 -17
  47. wayfinder_paths/core/__init__.py +2 -0
  48. wayfinder_paths/core/adapters/BaseAdapter.py +0 -25
  49. wayfinder_paths/core/adapters/models.py +17 -7
  50. wayfinder_paths/core/clients/BRAPClient.py +1 -1
  51. wayfinder_paths/core/clients/TokenClient.py +47 -1
  52. wayfinder_paths/core/clients/WayfinderClient.py +1 -2
  53. wayfinder_paths/core/clients/protocols.py +21 -22
  54. wayfinder_paths/core/clients/test_ledger_client.py +448 -0
  55. wayfinder_paths/core/config.py +12 -0
  56. wayfinder_paths/core/constants/__init__.py +15 -0
  57. wayfinder_paths/core/constants/base.py +6 -1
  58. wayfinder_paths/core/constants/contracts.py +39 -26
  59. wayfinder_paths/core/constants/erc20_abi.py +0 -1
  60. wayfinder_paths/core/constants/hyperlend_abi.py +0 -4
  61. wayfinder_paths/core/constants/hyperliquid.py +16 -0
  62. wayfinder_paths/core/constants/moonwell_abi.py +0 -15
  63. wayfinder_paths/core/engine/manifest.py +66 -0
  64. wayfinder_paths/core/strategies/Strategy.py +0 -61
  65. wayfinder_paths/core/strategies/__init__.py +10 -1
  66. wayfinder_paths/core/strategies/opa_loop.py +167 -0
  67. wayfinder_paths/core/utils/test_transaction.py +289 -0
  68. wayfinder_paths/core/utils/transaction.py +44 -1
  69. wayfinder_paths/core/utils/web3.py +3 -0
  70. wayfinder_paths/mcp/__init__.py +5 -0
  71. wayfinder_paths/mcp/preview.py +185 -0
  72. wayfinder_paths/mcp/scripting.py +84 -0
  73. wayfinder_paths/mcp/server.py +52 -0
  74. wayfinder_paths/mcp/state/profile_store.py +195 -0
  75. wayfinder_paths/mcp/state/store.py +89 -0
  76. wayfinder_paths/mcp/test_scripting.py +267 -0
  77. wayfinder_paths/mcp/tools/__init__.py +0 -0
  78. wayfinder_paths/mcp/tools/balances.py +290 -0
  79. wayfinder_paths/mcp/tools/discovery.py +158 -0
  80. wayfinder_paths/mcp/tools/execute.py +770 -0
  81. wayfinder_paths/mcp/tools/hyperliquid.py +931 -0
  82. wayfinder_paths/mcp/tools/quotes.py +288 -0
  83. wayfinder_paths/mcp/tools/run_script.py +286 -0
  84. wayfinder_paths/mcp/tools/strategies.py +188 -0
  85. wayfinder_paths/mcp/tools/tokens.py +46 -0
  86. wayfinder_paths/mcp/tools/wallets.py +354 -0
  87. wayfinder_paths/mcp/utils.py +129 -0
  88. wayfinder_paths/policies/hyperliquid.py +1 -1
  89. wayfinder_paths/policies/lifi.py +18 -0
  90. wayfinder_paths/policies/util.py +8 -2
  91. wayfinder_paths/strategies/basis_trading_strategy/strategy.py +28 -119
  92. wayfinder_paths/strategies/basis_trading_strategy/test_strategy.py +24 -53
  93. wayfinder_paths/strategies/boros_hype_strategy/__init__.py +3 -0
  94. wayfinder_paths/strategies/boros_hype_strategy/boros_ops_mixin.py +450 -0
  95. wayfinder_paths/strategies/boros_hype_strategy/constants.py +255 -0
  96. wayfinder_paths/strategies/boros_hype_strategy/examples.json +37 -0
  97. wayfinder_paths/strategies/boros_hype_strategy/hyperevm_ops_mixin.py +114 -0
  98. wayfinder_paths/strategies/boros_hype_strategy/hyperliquid_ops_mixin.py +642 -0
  99. wayfinder_paths/strategies/boros_hype_strategy/manifest.yaml +36 -0
  100. wayfinder_paths/strategies/boros_hype_strategy/planner.py +460 -0
  101. wayfinder_paths/strategies/boros_hype_strategy/risk_ops_mixin.py +886 -0
  102. wayfinder_paths/strategies/boros_hype_strategy/snapshot_mixin.py +494 -0
  103. wayfinder_paths/strategies/boros_hype_strategy/strategy.py +1194 -0
  104. wayfinder_paths/strategies/boros_hype_strategy/test_planner_golden.py +374 -0
  105. wayfinder_paths/strategies/boros_hype_strategy/test_strategy.py +202 -0
  106. wayfinder_paths/strategies/boros_hype_strategy/types.py +365 -0
  107. wayfinder_paths/strategies/boros_hype_strategy/withdraw_mixin.py +997 -0
  108. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/strategy.py +3 -12
  109. wayfinder_paths/strategies/hyperlend_stable_yield_strategy/test_strategy.py +7 -29
  110. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/strategy.py +63 -40
  111. wayfinder_paths/strategies/moonwell_wsteth_loop_strategy/test_strategy.py +5 -15
  112. wayfinder_paths/strategies/stablecoin_yield_strategy/strategy.py +0 -34
  113. wayfinder_paths/strategies/stablecoin_yield_strategy/test_strategy.py +11 -34
  114. wayfinder_paths/tests/test_mcp_quote_swap.py +165 -0
  115. wayfinder_paths/tests/test_test_coverage.py +1 -4
  116. wayfinder_paths-0.1.25.dist-info/METADATA +377 -0
  117. wayfinder_paths-0.1.25.dist-info/RECORD +185 -0
  118. wayfinder_paths/scripts/create_strategy.py +0 -139
  119. wayfinder_paths/scripts/make_wallets.py +0 -142
  120. wayfinder_paths-0.1.23.dist-info/METADATA +0 -354
  121. wayfinder_paths-0.1.23.dist-info/RECORD +0 -120
  122. /wayfinder_paths/{scripts → mcp/state}/__init__.py +0 -0
  123. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/LICENSE +0 -0
  124. {wayfinder_paths-0.1.23.dist-info → wayfinder_paths-0.1.25.dist-info}/WHEEL +0 -0
@@ -0,0 +1,166 @@
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Iterable, Sequence
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+ from hexbytes import HexBytes
8
+
9
+ from wayfinder_paths.core.adapters.BaseAdapter import BaseAdapter
10
+ from wayfinder_paths.core.constants.erc20_abi import ERC20_ABI
11
+
12
+ MULTICALL3_DEFAULT_ADDRESS = "0xcA11bde05977b3631167028862bE2a173976CA11"
13
+ MULTICALL3_ABI = [
14
+ {
15
+ "inputs": [
16
+ {
17
+ "components": [
18
+ {"internalType": "address", "name": "target", "type": "address"},
19
+ {"internalType": "bytes", "name": "callData", "type": "bytes"},
20
+ ],
21
+ "internalType": "struct Multicall3.Call[]",
22
+ "name": "calls",
23
+ "type": "tuple[]",
24
+ }
25
+ ],
26
+ "name": "aggregate",
27
+ "outputs": [
28
+ {"internalType": "uint256", "name": "blockNumber", "type": "uint256"},
29
+ {"internalType": "bytes[]", "name": "returnData", "type": "bytes[]"},
30
+ ],
31
+ "stateMutability": "payable",
32
+ "type": "function",
33
+ },
34
+ {
35
+ "inputs": [{"internalType": "address", "name": "addr", "type": "address"}],
36
+ "name": "getEthBalance",
37
+ "outputs": [
38
+ {"internalType": "uint256", "name": "balance", "type": "uint256"},
39
+ ],
40
+ "stateMutability": "view",
41
+ "type": "function",
42
+ },
43
+ ]
44
+
45
+
46
+ @dataclass(frozen=True)
47
+ class MulticallCall:
48
+ target: str
49
+ call_data: bytes | str
50
+
51
+ def as_tuple(self) -> tuple[str, bytes | str]:
52
+ return self.target, self.call_data
53
+
54
+
55
+ @dataclass
56
+ class MulticallResult:
57
+ block_number: int
58
+ return_data: Sequence[bytes]
59
+
60
+
61
+ class MulticallAdapter(BaseAdapter):
62
+ """Thin wrapper around Multicall3 for reusable multicall aggregates."""
63
+
64
+ adapter_type = "MULTICALL"
65
+
66
+ def __init__(
67
+ self,
68
+ config: dict[str, Any] | None = None,
69
+ *,
70
+ chain_id: int | None = None,
71
+ web3: Any | None = None,
72
+ address: str | None = None,
73
+ abi: list[dict[str, Any]] | None = None,
74
+ ) -> None:
75
+ super().__init__("multicall_adapter", config)
76
+
77
+ if web3 is None:
78
+ raise ValueError("MulticallAdapter requires web3 instance")
79
+ self.chain_id = int(chain_id) if chain_id is not None else None
80
+ self.web3 = web3
81
+
82
+ checksum_address = self.web3.to_checksum_address(
83
+ address or MULTICALL3_DEFAULT_ADDRESS
84
+ )
85
+ self.contract = self.web3.eth.contract(
86
+ address=checksum_address, abi=abi or MULTICALL3_ABI
87
+ )
88
+
89
+ async def aggregate(
90
+ self,
91
+ calls: Iterable[MulticallCall | tuple[str, bytes | str]],
92
+ *,
93
+ value: int = 0,
94
+ ) -> MulticallResult:
95
+ calls_list = list(calls)
96
+ if not calls_list:
97
+ return MulticallResult(block_number=0, return_data=[])
98
+
99
+ encoded_calls: list[tuple[str, bytes]] = []
100
+ for call in calls_list:
101
+ target, calldata = self._coerce_call(call)
102
+ encoded_calls.append((target, calldata))
103
+
104
+ block_number, return_data = await self.contract.functions.aggregate(
105
+ encoded_calls
106
+ ).call({"value": int(value)})
107
+ payload = tuple(self._ensure_bytes(r) for r in return_data)
108
+ return MulticallResult(block_number=int(block_number), return_data=payload)
109
+
110
+ def build_call(self, target: str, call_data: bytes | str) -> MulticallCall:
111
+ checksum = self.web3.to_checksum_address(target)
112
+ normalized = self._normalize_call_data(call_data)
113
+ return MulticallCall(target=checksum, call_data=normalized)
114
+
115
+ def encode_eth_balance(self, account: str) -> MulticallCall:
116
+ calldata = self.contract.encode_abi("getEthBalance", args=[account])
117
+ return self.build_call(self.contract.address, calldata)
118
+
119
+ def encode_erc20_balance(self, token: str, account: str) -> MulticallCall:
120
+ addr = self.web3.to_checksum_address(token)
121
+ erc20 = self.web3.eth.contract(address=addr, abi=ERC20_ABI)
122
+ calldata = erc20.encode_abi("balanceOf", args=[account])
123
+ return self.build_call(addr, calldata)
124
+
125
+ @staticmethod
126
+ def decode_uint256(data: bytes | str) -> int:
127
+ raw = MulticallAdapter._normalize_call_data(data)
128
+ if len(raw) < 32:
129
+ raw = raw.rjust(32, b"\x00")
130
+ return int.from_bytes(raw[-32:], byteorder="big")
131
+
132
+ def _coerce_call(
133
+ self, call: MulticallCall | tuple[str, bytes | str]
134
+ ) -> tuple[str, bytes]:
135
+ if isinstance(call, MulticallCall):
136
+ target = self.web3.to_checksum_address(call.target)
137
+ calldata = self._normalize_call_data(call.call_data)
138
+ else:
139
+ target_str, call_data = call
140
+ target = self.web3.to_checksum_address(target_str)
141
+ calldata = self._normalize_call_data(call_data)
142
+ return target, calldata
143
+
144
+ @staticmethod
145
+ def _normalize_call_data(data: bytes | str) -> bytes:
146
+ if isinstance(data, bytes):
147
+ return data
148
+ if isinstance(data, HexBytes):
149
+ return bytes(data)
150
+ if isinstance(data, str):
151
+ if data.startswith("0x"):
152
+ return bytes.fromhex(data[2:])
153
+ return data.encode()
154
+ raise TypeError("Unsupported calldata type")
155
+
156
+ @staticmethod
157
+ def _ensure_bytes(data: bytes | str | HexBytes) -> bytes:
158
+ if isinstance(data, bytes):
159
+ return data
160
+ if isinstance(data, HexBytes):
161
+ return bytes(data)
162
+ if isinstance(data, str):
163
+ if data.startswith("0x"):
164
+ return bytes.fromhex(data[2:])
165
+ return data.encode()
166
+ raise TypeError("Unexpected return data type from multicall")
@@ -0,0 +1,5 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "adapters.multicall_adapter.adapter.MulticallAdapter"
3
+ capabilities:
4
+ - "multicall.aggregate"
5
+ dependencies: []
@@ -0,0 +1,97 @@
1
+ from __future__ import annotations
2
+
3
+ import pytest
4
+ from web3 import AsyncHTTPProvider, AsyncWeb3
5
+
6
+ from wayfinder_paths.adapters.multicall_adapter.adapter import (
7
+ MulticallAdapter,
8
+ MulticallCall,
9
+ )
10
+
11
+
12
+ class _DummyAggregate:
13
+ def __init__(self, expected_value: int, result):
14
+ self._expected_value = expected_value
15
+ self._result = result
16
+
17
+ async def call(self, tx_params):
18
+ assert tx_params == {"value": self._expected_value}
19
+ return self._result
20
+
21
+
22
+ class _DummyFunctions:
23
+ def __init__(self, expected_calls, expected_value: int, result):
24
+ self._expected_calls = expected_calls
25
+ self._expected_value = expected_value
26
+ self._result = result
27
+
28
+ def aggregate(self, calls):
29
+ assert calls == self._expected_calls
30
+ return _DummyAggregate(self._expected_value, self._result)
31
+
32
+
33
+ class _DummyContract:
34
+ def __init__(self, expected_calls, expected_value: int, result):
35
+ self.functions = _DummyFunctions(expected_calls, expected_value, result)
36
+
37
+
38
+ class TestMulticallAdapter:
39
+ def test_normalize_call_data_hex_str(self):
40
+ raw = MulticallAdapter._normalize_call_data("0x1234")
41
+ assert raw == b"\x12\x34"
42
+
43
+ def test_decode_uint256(self):
44
+ value = 123
45
+ encoded = value.to_bytes(32, "big")
46
+ assert MulticallAdapter.decode_uint256(encoded) == value
47
+
48
+ def test_encode_balance_calls_produce_bytes(self):
49
+ w3 = AsyncWeb3(AsyncHTTPProvider("http://localhost:8545"))
50
+ adapter = MulticallAdapter(web3=w3)
51
+
52
+ eth_call = adapter.encode_eth_balance(
53
+ "0x0000000000000000000000000000000000000002"
54
+ )
55
+ assert isinstance(eth_call, MulticallCall)
56
+ assert isinstance(eth_call.call_data, (bytes, bytearray))
57
+ assert len(eth_call.call_data) > 0
58
+
59
+ erc20_call = adapter.encode_erc20_balance(
60
+ "0x0000000000000000000000000000000000000001",
61
+ "0x0000000000000000000000000000000000000002",
62
+ )
63
+ assert isinstance(erc20_call, MulticallCall)
64
+ assert isinstance(erc20_call.call_data, (bytes, bytearray))
65
+ assert len(erc20_call.call_data) > 0
66
+
67
+ @pytest.mark.asyncio
68
+ async def test_aggregate_coerces_calls_and_returns_bytes(self):
69
+ w3 = AsyncWeb3(AsyncHTTPProvider("http://localhost:8545"))
70
+ adapter = MulticallAdapter(web3=w3)
71
+
72
+ call = adapter.build_call(
73
+ "0x0000000000000000000000000000000000000001", "0x1234"
74
+ )
75
+ expected_calls = [
76
+ (w3.to_checksum_address(call.target), b"\x12\x34"),
77
+ ]
78
+
79
+ dummy_contract = _DummyContract(
80
+ expected_calls=expected_calls,
81
+ expected_value=7,
82
+ result=(123, ["0x" + ("00" * 32)]),
83
+ )
84
+ adapter.contract = dummy_contract
85
+
86
+ result = await adapter.aggregate([call], value=7)
87
+ assert result.block_number == 123
88
+ assert result.return_data == (b"\x00" * 32,)
89
+
90
+ @pytest.mark.asyncio
91
+ async def test_aggregate_empty_returns_empty_result(self):
92
+ w3 = AsyncWeb3(AsyncHTTPProvider("http://localhost:8545"))
93
+ adapter = MulticallAdapter(web3=w3)
94
+
95
+ result = await adapter.aggregate([])
96
+ assert result.block_number == 0
97
+ assert list(result.return_data) == []
@@ -0,0 +1,102 @@
1
+ # Pendle Adapter
2
+
3
+ Adapter for Pendle API + Hosted SDK endpoints to support:
4
+
5
+ - Market discovery (PT/YT markets, APYs, liquidity/volume/expiry filtering)
6
+ - Historical metrics (per-market time series)
7
+ - Execution planning (swap quote → ready-to-send `tx` + required `tokenApprovals`)
8
+
9
+ ## Capabilities
10
+
11
+ - `pendle.markets.read`: Fetch whitelisted markets (`/v1/markets/all`)
12
+ - `pendle.market.snapshot`: Fetch a market snapshot (`/v2/{chainId}/markets/{market}/data`)
13
+ - `pendle.market.history`: Fetch market historical data (`/v2/{chainId}/markets/{market}/historical-data`)
14
+ - `pendle.prices.ohlcv`: Fetch token OHLCV (`/v4/{chainId}/prices/{token}/ohlcv`)
15
+ - `pendle.prices.assets`: Fetch all asset prices (`/v1/prices/assets`)
16
+ - `pendle.swap.quote`: Build Hosted SDK swap payload (`/v2/sdk/{chainId}/markets/{market}/swap`)
17
+ - `pendle.swap.best_pt`: Select and quote “best” PT swap on a chain
18
+ - `pendle.convert.quote`: Universal Hosted SDK convert quote (`/v2/sdk/{chainId}/convert`)
19
+ - `pendle.convert.best_pt`: Select and quote “best” PT via convert endpoint
20
+ - `pendle.convert.execute`: Broadcast Hosted SDK convert tx (incl approvals)
21
+ - `pendle.positions.database`: Indexed positions snapshot (`/v1/dashboard/positions/database/{user}`; claimables cached)
22
+ - `pendle.limit_orders.*`: Limit order discovery + maker APIs (`/v1/limit-orders/...`)
23
+ - `pendle.deployments.read`: Load Pendle core deployments JSON (router/routerStatic/limitRouter)
24
+ - `pendle.router_static.rates`: Off-chain spot-rate sanity checks via RouterStatic contract
25
+
26
+ ## Configuration
27
+
28
+ - `PENDLE_API_URL` (env var): defaults to `https://api-v2.pendle.finance/core`
29
+ - Optional config:
30
+ - `config["pendle_adapter"]["base_url"]`
31
+ - `config["pendle_adapter"]["timeout"]`
32
+ - `config["pendle_adapter"]["deployments_base_url"]` (defaults to Pendle’s public core deployments on GitHub)
33
+ - `config["pendle_adapter"]["max_retries"]`, `retry_backoff_seconds`
34
+
35
+ ## Usage
36
+
37
+ ### List active PT/YT markets (multi-chain)
38
+
39
+ ```python
40
+ from adapters.pendle_adapter.adapter import PendleAdapter
41
+
42
+ adapter = PendleAdapter()
43
+
44
+ rows = await adapter.list_active_pt_yt_markets(
45
+ chains=["arbitrum", "base", "hyperevm"],
46
+ min_liquidity_usd=250_000,
47
+ min_volume_usd_24h=25_000,
48
+ min_days_to_expiry=7,
49
+ sort_by="fixed_apy",
50
+ descending=True,
51
+ )
52
+ ```
53
+
54
+ ### Build the best PT swap transaction (single chain)
55
+
56
+ ```python
57
+ best = await adapter.build_best_pt_swap_tx(
58
+ chain="arbitrum",
59
+ token_in="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # example: USDC (Arbitrum)
60
+ amount_in=str(1000 * 10**6), # 1000 USDC, base units (6 decimals)
61
+ receiver="0xYourEOAHere",
62
+ slippage=0.01,
63
+ enable_aggregator=True,
64
+ )
65
+
66
+ if best["ok"]:
67
+ tx = best["tx"]
68
+ approvals = best["tokenApprovals"]
69
+ ```
70
+
71
+ ### Build a universal convert transaction (token -> PT)
72
+
73
+ ```python
74
+ convert = await adapter.sdk_convert_v2(
75
+ chain="arbitrum",
76
+ slippage=0.01,
77
+ receiver="0xYourEOAHere",
78
+ inputs=[{"token": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": str(1 * 10**6)}], # 1 USDC
79
+ outputs=["0x97c1a4ae3e0da8009aff13e3e3ee7ea5ee4afe84"], # PT token address
80
+ enable_aggregator=True,
81
+ aggregators=["kyberswap"],
82
+ additional_data=["impliedApy", "effectiveApy", "priceImpact"],
83
+ )
84
+ plan = adapter.build_convert_plan(chain="arbitrum", convert_response=convert)
85
+ ```
86
+
87
+ ### Execute a universal convert (handles approvals + broadcast)
88
+
89
+ ```python
90
+ ok, res = await adapter.execute_convert(
91
+ chain="arbitrum",
92
+ slippage=0.01,
93
+ inputs=[{"token": "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "amount": str(1 * 10**6)}],
94
+ outputs=["0x97c1a4ae3e0da8009aff13e3e3ee7ea5ee4afe84"],
95
+ )
96
+ ```
97
+
98
+ ## Notes
99
+
100
+ - “Fixed APY” proxy is `details.impliedApy` from `/v1/markets/all`.
101
+ - `build_best_pt_swap_tx()` requests Hosted SDK `additionalData=impliedApy,effectiveApy` and prefers `effectiveApy` when present.
102
+ - All Pendle REST/SDK responses include a `rateLimit` field populated from headers (x-ratelimit-* and x-computing-unit) for CU-aware budgeting.
@@ -0,0 +1,7 @@
1
+ """
2
+ Pendle Adapter
3
+ """
4
+
5
+ from wayfinder_paths.adapters.pendle_adapter.adapter import PendleAdapter
6
+
7
+ __all__ = ["PendleAdapter"]