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,476 @@
1
+ """Boros HTTP Client for interacting with Boros API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any
6
+
7
+ import aiohttp
8
+ from aiocache import Cache
9
+ from loguru import logger
10
+
11
+ # Default Boros API endpoints
12
+ DEFAULT_ENDPOINTS = {
13
+ "markets": "/core/v1/markets",
14
+ "market": "/core/v1/markets",
15
+ "orderbook": "/core/v1/order-books",
16
+ "active_positions": "/core/v1/pnl/positions/active",
17
+ "closed_positions": "/core/v1/pnl/positions/closed",
18
+ "collaterals": "/core/v1/collaterals/summary",
19
+ "collateral_summary": "/core/v1/collaterals/summary",
20
+ "build_deposit_calldata": "/core/v2/calldata/deposit",
21
+ "build_withdraw_calldata": "/core/v1/calldata/withdraw/request",
22
+ "build_finalize_withdrawal_calldata": "/core/v1/calldata/withdraw/finalize",
23
+ "build_place_order_calldata": "/core/v4/calldata/place-order",
24
+ "build_close_position_calldata": "/core/v4/calldata/close-active-position",
25
+ "build_cancel_order_calldata": "/core/v3/calldata/cancel-order",
26
+ "build_cash_transfer_calldata": "/core/v3/calldata/cash-transfer",
27
+ "limit_orders": "/core/v1/pnl/limit-orders",
28
+ "amm_summary": "/core/v1/amm/summary",
29
+ "amm_info": "/core/v2/amm",
30
+ "amm_chart": "/core/v2/amm/chart",
31
+ "simulate_add_liquidity": "/core/v1/simulations/add-liquidity-single-cash",
32
+ "simulate_remove_liquidity": "/core/v1/simulations/remove-liquidity-single-cash",
33
+ "build_add_liquidity_calldata": "/core/v4/calldata/add-liquidity-single-cash-to-amm",
34
+ "build_remove_liquidity_calldata": "/core/v4/calldata/remove-liquidity-single-cash-from-amm",
35
+ "amm_rewards": "/core/v1/amm/rewards",
36
+ "amm_rewards_proof": "/core/v1/merkels/amm_lp_rewards/user",
37
+ }
38
+
39
+
40
+ class BorosClient:
41
+ """HTTP client for Boros API.
42
+
43
+ Provides low-level HTTP methods for interacting with the Boros API.
44
+ """
45
+
46
+ def __init__(
47
+ self,
48
+ base_url: str = "https://api.boros.finance",
49
+ endpoints: dict[str, str] | None = None,
50
+ user_address: str | None = None,
51
+ account_id: int = 0,
52
+ timeout: int = 30,
53
+ ) -> None:
54
+ """Initialize Boros client.
55
+
56
+ Args:
57
+ base_url: Base URL for Boros API.
58
+ endpoints: Custom endpoint paths (optional).
59
+ user_address: User wallet address for authenticated requests.
60
+ account_id: Boros account ID (0 = cross margin).
61
+ timeout: Request timeout in seconds.
62
+ """
63
+ self.base_url = base_url.rstrip("/")
64
+ self.endpoints = {**DEFAULT_ENDPOINTS, **(endpoints or {})}
65
+ self.user_address = user_address
66
+ self.account_id = account_id
67
+ self.timeout = timeout
68
+ self._cache = Cache(Cache.MEMORY)
69
+
70
+ async def _http(
71
+ self,
72
+ method: str,
73
+ path: str,
74
+ *,
75
+ params: dict[str, Any] | None = None,
76
+ json_body: dict[str, Any] | None = None,
77
+ timeout: int | None = None,
78
+ ) -> Any:
79
+ """Make HTTP request to Boros API.
80
+
81
+ Args:
82
+ method: HTTP method (GET, POST, etc.).
83
+ path: API endpoint path.
84
+ params: Query parameters.
85
+ json_body: JSON body for POST requests.
86
+ timeout: Request timeout override.
87
+
88
+ Returns:
89
+ Parsed JSON response.
90
+ """
91
+ url = f"{self.base_url}{path}"
92
+ timeout_val = timeout or self.timeout
93
+
94
+ async with aiohttp.ClientSession() as session:
95
+ async with session.request(
96
+ method,
97
+ url,
98
+ params=params,
99
+ json=json_body,
100
+ timeout=aiohttp.ClientTimeout(total=timeout_val),
101
+ ) as resp:
102
+ resp.raise_for_status()
103
+ return await resp.json()
104
+
105
+ async def list_markets(
106
+ self,
107
+ *,
108
+ is_whitelisted: bool | None = True,
109
+ skip: int = 0,
110
+ limit: int = 100,
111
+ ) -> list[dict[str, Any]]:
112
+ """List available Boros markets.
113
+
114
+ Args:
115
+ is_whitelisted: Filter by whitelisted markets.
116
+ skip: Number of markets to skip.
117
+ limit: Maximum markets to return.
118
+
119
+ Returns:
120
+ List of market dictionaries.
121
+ """
122
+ cache_key = f"boros:markets:{self.base_url}:{is_whitelisted}:{skip}:{limit}"
123
+ cached = await self._cache.get(cache_key)
124
+ if cached:
125
+ return cached
126
+
127
+ path = self.endpoints["markets"]
128
+ params: dict[str, Any] = {"skip": skip, "limit": limit}
129
+ if is_whitelisted is not None:
130
+ params["isWhitelisted"] = "true" if is_whitelisted else "false"
131
+
132
+ data = await self._http("GET", path, params=params)
133
+ markets: list[dict[str, Any]] = (
134
+ data.get("markets") or data.get("results") or data.get("data") or data
135
+ )
136
+ await self._cache.set(cache_key, markets, ttl=300)
137
+ return markets
138
+
139
+ async def get_market(self, market_id: int) -> dict[str, Any]:
140
+ """Fetch a single market by ID.
141
+
142
+ Args:
143
+ market_id: Boros market ID.
144
+
145
+ Returns:
146
+ Market dictionary.
147
+ """
148
+ cache_key = f"boros:market:{self.base_url}:{int(market_id)}"
149
+ cached = await self._cache.get(cache_key)
150
+ if cached:
151
+ return cached
152
+
153
+ path = self.endpoints["market"]
154
+ try:
155
+ market = await self._http("GET", path, params={"marketId": market_id})
156
+ except Exception:
157
+ # Fallback to list_markets
158
+ markets = await self.list_markets(skip=0, limit=200, is_whitelisted=None)
159
+ market = next(
160
+ (
161
+ m
162
+ for m in markets
163
+ if int(m.get("marketId") or m.get("id") or 0) == int(market_id)
164
+ ),
165
+ None,
166
+ )
167
+ if not market:
168
+ raise
169
+
170
+ await self._cache.set(cache_key, market, ttl=300)
171
+ return market
172
+
173
+ async def get_order_book(
174
+ self,
175
+ market_id: int,
176
+ *,
177
+ tick_size: float = 0.001,
178
+ ) -> dict[str, Any]:
179
+ """Get order book for a market.
180
+
181
+ Args:
182
+ market_id: Boros market ID.
183
+ tick_size: Tick size for aggregation.
184
+
185
+ Returns:
186
+ Order book with long/short sides.
187
+ """
188
+ path = f"{self.endpoints['orderbook']}/{int(market_id)}"
189
+ params = {"tickSize": tick_size}
190
+ return await self._http("GET", path, params=params)
191
+
192
+ async def get_collaterals(
193
+ self,
194
+ user_address: str | None = None,
195
+ account_id: int | None = None,
196
+ ) -> dict[str, Any]:
197
+ """Get collateral summary for user.
198
+
199
+ Args:
200
+ user_address: User wallet address (defaults to client's user_address).
201
+ account_id: Account ID (defaults to client's account_id).
202
+
203
+ Returns:
204
+ Collateral summary with positions.
205
+ """
206
+ path = self.endpoints["collaterals"]
207
+ params = {
208
+ "userAddress": user_address or self.user_address,
209
+ "accountId": int(account_id if account_id is not None else self.account_id),
210
+ }
211
+ return await self._http("GET", path, params=params)
212
+
213
+ async def get_open_orders(
214
+ self,
215
+ user_address: str | None = None,
216
+ *,
217
+ limit: int = 50,
218
+ ) -> list[dict[str, Any]]:
219
+ """Get open limit orders.
220
+
221
+ Args:
222
+ user_address: User wallet address.
223
+ limit: Maximum orders to return.
224
+
225
+ Returns:
226
+ List of open orders.
227
+ """
228
+ path = self.endpoints["limit_orders"]
229
+ params = {
230
+ "userAddress": user_address or self.user_address,
231
+ "limit": limit,
232
+ }
233
+ try:
234
+ data = await self._http("GET", path, params=params)
235
+ return data.get("orders") or data.get("results") or []
236
+ except Exception as e:
237
+ logger.warning(f"Failed to fetch open orders: {e}")
238
+ return []
239
+
240
+ async def build_deposit_calldata(
241
+ self,
242
+ *,
243
+ token_id: int,
244
+ amount_wei: int,
245
+ market_id: int,
246
+ user_address: str | None = None,
247
+ account_id: int | None = None,
248
+ ) -> dict[str, Any]:
249
+ """Build calldata for deposit.
250
+
251
+ Args:
252
+ token_id: Boros token ID (e.g., 3 for USDT).
253
+ amount_wei: Amount in scaled wei (1e18).
254
+ market_id: Target market ID.
255
+ user_address: User wallet address.
256
+ account_id: Boros account ID (0 = cross margin).
257
+
258
+ Returns:
259
+ Calldata dictionary with 'to', 'data', 'value' fields.
260
+ """
261
+ path = self.endpoints["build_deposit_calldata"]
262
+ return await self._http(
263
+ "GET",
264
+ path,
265
+ params={
266
+ "userAddress": user_address or self.user_address,
267
+ "accountId": account_id if account_id is not None else self.account_id,
268
+ "tokenId": int(token_id),
269
+ "amount": str(amount_wei),
270
+ "marketId": int(market_id),
271
+ },
272
+ )
273
+
274
+ async def build_withdraw_calldata(
275
+ self,
276
+ *,
277
+ token_id: int,
278
+ amount_wei: int,
279
+ user_address: str | None = None,
280
+ account_id: int | None = None,
281
+ ) -> dict[str, Any]:
282
+ """Build calldata for withdrawal.
283
+
284
+ Args:
285
+ token_id: Boros token ID.
286
+ amount_wei: Amount in scaled wei.
287
+ user_address: User wallet address.
288
+ account_id: Account ID.
289
+
290
+ Returns:
291
+ Calldata dictionary.
292
+ """
293
+ path = self.endpoints["build_withdraw_calldata"]
294
+ return await self._http(
295
+ "GET",
296
+ path,
297
+ params={
298
+ "userAddress": user_address or self.user_address,
299
+ "accountId": int(
300
+ account_id if account_id is not None else self.account_id
301
+ ),
302
+ "tokenId": int(token_id),
303
+ "amount": str(int(amount_wei)),
304
+ },
305
+ )
306
+
307
+ async def build_place_order_calldata(
308
+ self,
309
+ *,
310
+ market_acc: str,
311
+ market_id: int,
312
+ side: int,
313
+ size_wei: int,
314
+ limit_tick: int,
315
+ tif: int = 0,
316
+ slippage: float = 0.05,
317
+ ) -> dict[str, Any]:
318
+ """Build calldata for placing an order.
319
+
320
+ Args:
321
+ market_acc: Packed marketAcc bytes.
322
+ market_id: Boros market ID.
323
+ side: 0 = long, 1 = short.
324
+ size_wei: Position size in YU wei.
325
+ limit_tick: Limit tick (APR in bps).
326
+ tif: Time in force (0=GTC, 1=IOC, 2=FOK).
327
+ slippage: Slippage tolerance.
328
+
329
+ Returns:
330
+ Calldata dictionary.
331
+ """
332
+ path = self.endpoints["build_place_order_calldata"]
333
+ return await self._http(
334
+ "GET",
335
+ path,
336
+ params={
337
+ "marketAcc": market_acc,
338
+ "marketId": market_id,
339
+ "side": side,
340
+ "size": str(size_wei),
341
+ "limitTick": int(limit_tick),
342
+ "tif": tif,
343
+ "slippage": slippage,
344
+ },
345
+ )
346
+
347
+ async def build_close_position_calldata(
348
+ self,
349
+ *,
350
+ market_acc: str,
351
+ market_id: int,
352
+ side: int,
353
+ size_wei: int,
354
+ limit_tick: int,
355
+ tif: int = 1, # IOC for market-like close
356
+ ) -> dict[str, Any]:
357
+ """Build calldata for closing a position.
358
+
359
+ Args:
360
+ market_acc: Packed marketAcc bytes.
361
+ market_id: Boros market ID.
362
+ side: Close side (opposite of position side).
363
+ size_wei: Size to close.
364
+ limit_tick: Limit tick.
365
+ tif: Time in force.
366
+
367
+ Returns:
368
+ Calldata dictionary.
369
+ """
370
+ path = self.endpoints["build_close_position_calldata"]
371
+ return await self._http(
372
+ "GET",
373
+ path,
374
+ params={
375
+ "marketAcc": market_acc,
376
+ "marketId": int(market_id),
377
+ "side": int(side),
378
+ "size": str(size_wei),
379
+ "limitTick": int(limit_tick),
380
+ "tif": int(tif),
381
+ },
382
+ )
383
+
384
+ async def build_cancel_order_calldata(
385
+ self,
386
+ *,
387
+ market_acc: str,
388
+ market_id: int,
389
+ order_ids: list[str] | None = None,
390
+ cancel_all: bool = False,
391
+ ) -> dict[str, Any]:
392
+ """Build calldata for canceling orders.
393
+
394
+ Args:
395
+ market_acc: Packed marketAcc bytes.
396
+ market_id: Boros market ID.
397
+ order_ids: Specific order IDs to cancel.
398
+ cancel_all: Cancel all orders on market.
399
+
400
+ Returns:
401
+ Calldata dictionary.
402
+ """
403
+ path = self.endpoints["build_cancel_order_calldata"]
404
+ params: dict[str, Any] = {
405
+ "marketAcc": market_acc,
406
+ "marketId": int(market_id),
407
+ "cancelAll": "true" if cancel_all else "false",
408
+ }
409
+ if order_ids and not cancel_all:
410
+ params["orderIds"] = ",".join(str(oid) for oid in order_ids)
411
+
412
+ return await self._http("GET", path, params=params)
413
+
414
+ async def build_cash_transfer_calldata(
415
+ self,
416
+ *,
417
+ market_id: int,
418
+ amount_wei: int,
419
+ is_deposit: bool = False,
420
+ user_address: str | None = None,
421
+ ) -> dict[str, Any]:
422
+ """Build calldata for transferring cash between isolated and cross margin.
423
+
424
+ Matches the upstream behavior:
425
+ - is_deposit=True: cross -> isolated
426
+ - is_deposit=False: isolated -> cross
427
+
428
+ Args:
429
+ market_id: Boros market ID.
430
+ amount_wei: Amount in 1e18 cash units.
431
+ is_deposit: True = cross->isolated, False = isolated->cross.
432
+ user_address: User wallet address.
433
+
434
+ Notes:
435
+ - Boros uses 1e18 internal "cash" units for this call.
436
+ - This call does NOT send accountId, only userAddress.
437
+ """
438
+ path = self.endpoints["build_cash_transfer_calldata"]
439
+ return await self._http(
440
+ "GET",
441
+ path,
442
+ params={
443
+ "userAddress": user_address or self.user_address,
444
+ "marketId": int(market_id),
445
+ "isDeposit": str(bool(is_deposit)).lower(),
446
+ "amount": str(int(amount_wei)),
447
+ },
448
+ )
449
+
450
+ async def build_finalize_withdrawal_calldata(
451
+ self,
452
+ *,
453
+ token_id: int,
454
+ root_address: str,
455
+ ) -> dict[str, Any]:
456
+ """Build calldata for finalizing a vault withdrawal.
457
+
458
+ This completes a previously requested withdrawal by transferring
459
+ collateral from MarketHub to the specified root address.
460
+
461
+ Args:
462
+ token_id: Boros token ID.
463
+ root_address: Destination wallet address.
464
+
465
+ Returns:
466
+ Calldata dictionary with 'to', 'data', 'value' fields.
467
+ """
468
+ path = self.endpoints["build_finalize_withdrawal_calldata"]
469
+ return await self._http(
470
+ "GET",
471
+ path,
472
+ params={
473
+ "rootAddress": root_address,
474
+ "tokenId": int(token_id),
475
+ },
476
+ )
@@ -0,0 +1,10 @@
1
+ schema_version: "0.1"
2
+ entrypoint: "adapters.boros_adapter.adapter.BorosAdapter"
3
+ capabilities:
4
+ - "market.read"
5
+ - "market.quote"
6
+ - "position.open"
7
+ - "position.close"
8
+ - "collateral.deposit"
9
+ - "collateral.withdraw"
10
+ dependencies: []
@@ -0,0 +1,88 @@
1
+ """Parsing helpers for BorosAdapter response payloads."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import UTC, datetime
6
+ from decimal import Decimal
7
+ from typing import Any
8
+
9
+
10
+ def extract_symbol(market: dict[str, Any]) -> str:
11
+ im = market.get("imData") or {}
12
+ return im.get("symbol") or market.get("symbol") or market.get("name") or ""
13
+
14
+
15
+ def extract_underlying(market: dict[str, Any]) -> str:
16
+ im = market.get("imData") or {}
17
+ meta = market.get("metadata") or {}
18
+ return (
19
+ im.get("underlying")
20
+ or meta.get("assetSymbol")
21
+ or meta.get("underlyingSymbol")
22
+ or market.get("underlyingSymbol")
23
+ or market.get("underlying")
24
+ or ""
25
+ )
26
+
27
+
28
+ def extract_collateral(market: dict[str, Any]) -> str:
29
+ im = market.get("imData") or {}
30
+ return (
31
+ im.get("collateral")
32
+ or market.get("collateral")
33
+ or market.get("collateralAddress")
34
+ or ""
35
+ )
36
+
37
+
38
+ def extract_maturity_ts(market: dict[str, Any]) -> int | None:
39
+ im = market.get("imData") or {}
40
+ maturity = im.get("maturity") or market.get("maturity")
41
+ if maturity:
42
+ return int(maturity)
43
+ return None
44
+
45
+
46
+ def time_to_maturity_days(maturity_ts: int) -> float:
47
+ now = datetime.now(UTC).timestamp()
48
+ return max(0.0, (maturity_ts - now) / 86400.0)
49
+
50
+
51
+ def parse_market_position(
52
+ mkt_pos: dict[str, Any], token_id: int | None, *, is_cross: bool
53
+ ) -> dict[str, Any] | None:
54
+ """Parse a market position from collaterals response."""
55
+ size_wei = int(
56
+ mkt_pos.get("notionalSize")
57
+ or mkt_pos.get("size")
58
+ or mkt_pos.get("sizeWei")
59
+ or 0
60
+ )
61
+ if size_wei == 0:
62
+ return None
63
+
64
+ pnl = mkt_pos.get("pnl", {})
65
+ unrealized_pnl_wei = int(pnl.get("unrealisedPnl", 0) or 0)
66
+ settled_pnl_wei = int(pnl.get("rateSettlementPnl", 0) or 0)
67
+
68
+ return {
69
+ "marketId": mkt_pos.get("marketId"),
70
+ "marketAddress": mkt_pos.get("marketAddress"),
71
+ "side": mkt_pos.get("side"),
72
+ "sizeWei": size_wei,
73
+ "size": float(Decimal(str(size_wei)) / Decimal(1e18)),
74
+ "notionalSizeFloat": abs(float(Decimal(str(size_wei)) / Decimal(1e18))),
75
+ "entryPrice": mkt_pos.get("entryPrice"),
76
+ "tokenId": token_id,
77
+ "isCross": is_cross,
78
+ # APR fields (locked and current market rate)
79
+ "fixedApr": mkt_pos.get("fixedApr"),
80
+ "markApr": mkt_pos.get("markApr"),
81
+ "impliedApr": mkt_pos.get("impliedApr"),
82
+ "lastTradedApr": mkt_pos.get("lastTradedApr"),
83
+ # PnL fields
84
+ "unrealizedPnl": float(Decimal(str(unrealized_pnl_wei)) / Decimal(1e18)),
85
+ "settledPnl": float(Decimal(str(settled_pnl_wei)) / Decimal(1e18)),
86
+ # Progress
87
+ "settledProgressPercentage": mkt_pos.get("settledProgressPercentage"),
88
+ }