trading-cli 0.3.0__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.
@@ -0,0 +1,1169 @@
1
+ """HTTP client for FastBooks API services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import sys
8
+ from typing import Any
9
+
10
+ import requests
11
+
12
+ DEFAULT_ROUTING_URL = "http://localhost:3002"
13
+ DEFAULT_BOTS_URL = "http://localhost:8003"
14
+
15
+
16
+ def _stored_api_key() -> str | None:
17
+ """FastBooks data key persisted by `fastbooks init` (falls back after env)."""
18
+ try:
19
+ from . import config_store
20
+ return config_store.get_api_key()
21
+ except Exception:
22
+ return None
23
+
24
+
25
+ class FastBooksClient:
26
+ """Thin wrapper around the FastBooks REST APIs."""
27
+
28
+ def __init__(
29
+ self,
30
+ routing_url: str | None = None,
31
+ bots_url: str | None = None,
32
+ timeout: int = 30,
33
+ token: str | None = None,
34
+ api_key: str | None = None,
35
+ ):
36
+ self.routing_url = (
37
+ routing_url or os.environ.get("FASTBOOKS_ROUTING_URL", DEFAULT_ROUTING_URL)
38
+ ).rstrip("/")
39
+ self.bots_url = (
40
+ bots_url or os.environ.get("FASTBOOKS_BOTS_URL", DEFAULT_BOTS_URL)
41
+ ).rstrip("/")
42
+ self.timeout = timeout
43
+ # JWT access token for authenticated routes (api-keys, saved backtests).
44
+ # Sent as `Authorization: Bearer <token>` on every request when present.
45
+ self.token = token or os.environ.get("FASTBOOKS_API_TOKEN") or None
46
+ # FastBooks PUBLIC-API key (fb_live_...). Sent as `x-api-key` on every request so the
47
+ # platform meters/throttles this bot's data usage per key. Mint one, free & self-service,
48
+ # with `fastbooks apikey create`. Keyless still works for the frontend, but bots should key.
49
+ self.api_key = api_key or os.environ.get("FASTBOOKS_API_KEY") or _stored_api_key()
50
+ self._session = requests.Session()
51
+ if self.token:
52
+ self._session.headers["Authorization"] = f"Bearer {self.token}"
53
+ if self.api_key:
54
+ self._session.headers["x-api-key"] = self.api_key
55
+
56
+ # ── helpers ──────────────────────────────────────────────────────
57
+
58
+ def _get(self, base: str, path: str, params: dict | None = None, headers: dict | None = None) -> Any:
59
+ url = f"{base}{path}"
60
+ r = self._session.get(url, params=params, headers=headers, timeout=self.timeout)
61
+ r.raise_for_status()
62
+ return r.json()
63
+
64
+ def _post(self, base: str, path: str, data: dict | None = None, headers: dict | None = None) -> Any:
65
+ url = f"{base}{path}"
66
+ r = self._session.post(url, json=data, headers=headers, timeout=self.timeout)
67
+ r.raise_for_status()
68
+ return r.json()
69
+
70
+ def _patch(self, base: str, path: str, data: dict | None = None) -> Any:
71
+ url = f"{base}{path}"
72
+ r = self._session.patch(url, json=data, timeout=self.timeout)
73
+ r.raise_for_status()
74
+ return r.json()
75
+
76
+ def _delete(self, base: str, path: str, params: dict | None = None) -> Any:
77
+ url = f"{base}{path}"
78
+ r = self._session.delete(url, params=params, timeout=self.timeout)
79
+ r.raise_for_status()
80
+ return r.json()
81
+
82
+ # ── markets ──────────────────────────────────────────────────────
83
+
84
+ def orderbook(self, exchange: str, symbol: str) -> dict:
85
+ return self._get(self.routing_url, "/api/markets/orderbooks", {"exchange": exchange, "symbol": symbol})
86
+
87
+ def orderbooks_aggregated(self, category: str = "all") -> dict:
88
+ return self._get(self.routing_url, "/api/markets/orderbooks/aggregated", {"category": category})
89
+
90
+ def tickers(
91
+ self,
92
+ page: int = 1,
93
+ limit: int = 50,
94
+ category: str | None = None,
95
+ exchange: str | None = None,
96
+ search: str | None = None,
97
+ ) -> dict:
98
+ params: dict[str, Any] = {"page": page, "limit": limit}
99
+ if category:
100
+ params["category"] = category
101
+ if exchange:
102
+ params["exchange"] = exchange
103
+ if search:
104
+ params["search"] = search
105
+ return self._get(self.routing_url, "/api/markets/tickers/all", params)
106
+
107
+ def ohlcv(
108
+ self,
109
+ exchange: str,
110
+ symbol: str,
111
+ timeframe: str = "1h",
112
+ limit: int = 300,
113
+ ) -> dict:
114
+ return self._get(
115
+ self.routing_url,
116
+ "/api/markets/ohlcv",
117
+ {"exchange": exchange, "symbol": symbol, "timeframe": timeframe, "limit": limit},
118
+ )
119
+
120
+ def symbols_universe(
121
+ self,
122
+ region: str = "us",
123
+ sector: str | None = None,
124
+ limit: int = 100,
125
+ ) -> dict:
126
+ params: dict[str, Any] = {"region": region, "limit": limit}
127
+ if sector:
128
+ params["sector"] = sector
129
+ return self._get(self.routing_url, "/api/markets/symbols/universe", params)
130
+
131
+ def symbols_summary(self, symbols: list[str], fields: str | None = None) -> dict:
132
+ params: dict[str, Any] = {"symbols": ",".join(symbols)}
133
+ if fields:
134
+ params["fields"] = fields
135
+ return self._get(self.routing_url, "/api/markets/symbols/summary", params)
136
+
137
+ def options_chain(self, underlying: str) -> dict:
138
+ return self._get(self.routing_url, "/api/markets/options/chain", {"underlying": underlying})
139
+
140
+ # ── execution ────────────────────────────────────────────────────
141
+
142
+ def place_order(
143
+ self,
144
+ venue: str,
145
+ symbol: str,
146
+ side: str,
147
+ order_type: str,
148
+ amount: float,
149
+ price: float | None = None,
150
+ client_order_id: str | None = None,
151
+ ) -> dict:
152
+ body: dict[str, Any] = {
153
+ "venue": venue,
154
+ "symbol": symbol,
155
+ "side": side,
156
+ "type": order_type,
157
+ "amount": amount,
158
+ }
159
+ if price is not None:
160
+ body["price"] = price
161
+ if client_order_id:
162
+ body["clientOrderId"] = client_order_id
163
+ return self._post(self.routing_url, "/api/execution/orders", body)
164
+
165
+ def cancel_order(self, venue: str, order_id: str, symbol: str | None = None) -> dict:
166
+ params: dict[str, Any] = {"venue": venue}
167
+ if symbol:
168
+ params["symbol"] = symbol
169
+ return self._delete(self.routing_url, f"/api/execution/orders/{order_id}", params)
170
+
171
+ def list_orders(self, venue: str, symbol: str | None = None) -> dict:
172
+ params: dict[str, Any] = {"venue": venue}
173
+ if symbol:
174
+ params["symbol"] = symbol
175
+ return self._get(self.routing_url, "/api/execution/orders", params)
176
+
177
+ def get_order(self, venue: str, order_id: str, symbol: str | None = None) -> dict:
178
+ params: dict[str, Any] = {"venue": venue}
179
+ if symbol:
180
+ params["symbol"] = symbol
181
+ return self._get(self.routing_url, f"/api/execution/orders/{order_id}", params)
182
+
183
+ def order_history(self, venue: str = "ibkr") -> dict:
184
+ return self._get(self.routing_url, "/api/execution/orders/history", {"venue": venue})
185
+
186
+ def balance(self, venue: str, asset: str | None = None) -> dict:
187
+ params: dict[str, Any] = {"venue": venue}
188
+ if asset:
189
+ params["asset"] = asset
190
+ return self._get(self.routing_url, "/api/execution/balance", params)
191
+
192
+ def positions(self, venue: str) -> dict:
193
+ return self._get(self.routing_url, "/api/execution/positions", {"venue": venue})
194
+
195
+ def update_leverage(
196
+ self,
197
+ venue: str,
198
+ symbol: str,
199
+ leverage: int,
200
+ is_cross: bool = False,
201
+ ) -> dict:
202
+ return self._post(
203
+ self.routing_url,
204
+ "/api/execution/leverage",
205
+ {"venue": venue, "symbol": symbol, "leverage": leverage, "isCross": is_cross},
206
+ )
207
+
208
+ def venues(self) -> dict:
209
+ return self._get(self.routing_url, "/api/execution/venues", None)
210
+
211
+ def quote(self, venue: str, symbol: str) -> dict:
212
+ return self._get(self.routing_url, "/api/execution/quote", {"venue": venue, "symbol": symbol})
213
+
214
+ def paper_init(self, balance: float | None = None, venue: str | None = None) -> dict:
215
+ body: dict[str, Any] = {}
216
+ if balance is not None:
217
+ body["balance"] = balance
218
+ if venue:
219
+ body["venue"] = venue
220
+ return self._post(self.routing_url, "/api/execution/paper/init", body)
221
+
222
+ # ── alpha dashboard (/api/alpha) ─────────────────────────────────
223
+
224
+ def alpha_health(self) -> dict:
225
+ return self._get(self.routing_url, "/api/alpha/health", None)
226
+
227
+ def alpha_summary(self, market: str = "us") -> dict:
228
+ return self._get(self.routing_url, "/api/alpha/summary", {"market": market})
229
+
230
+ def alpha_movers(self, kind: str, limit: int = 20, market: str = "us") -> dict:
231
+ # kind: gainers | losers | breakouts | volume-alerts | news | snapshots
232
+ return self._get(self.routing_url, f"/api/alpha/{kind}", {"limit": limit, "market": market})
233
+
234
+ # ── market regime overlay (/api/regime) ──────────────────────────
235
+
236
+ def regime_health(self) -> dict:
237
+ return self._get(self.routing_url, "/api/regime/health", None)
238
+
239
+ def regime_summary(self, date: str | None = None) -> dict:
240
+ params = {"date": date} if date else None
241
+ return self._get(self.routing_url, "/api/regime/summary", params)
242
+
243
+ def regime_dates(self) -> dict:
244
+ return self._get(self.routing_url, "/api/regime/dates", None)
245
+
246
+ # ── AI analyst (/api/analyst) ────────────────────────────────────
247
+
248
+ def analyst_health(self) -> dict:
249
+ return self._get(self.routing_url, "/api/analyst/health", None)
250
+
251
+ def analyst_analyze(self, analysis_type: str, query: str, symbol: str | None = None) -> dict:
252
+ body: dict[str, Any] = {"analysis_type": analysis_type, "query": query}
253
+ if symbol:
254
+ body["symbol"] = symbol
255
+ return self._post(self.routing_url, "/api/analyst/analyze", body)
256
+
257
+ # ── api keys ─────────────────────────────────────────────────────
258
+ # NOTE: these live under /api/markets/api-keys and require a JWT
259
+ # (pass --token / FASTBOOKS_API_TOKEN). They are NOT at /api/api-keys.
260
+
261
+ def list_api_keys(self) -> dict:
262
+ return self._get(self.routing_url, "/api/markets/api-keys", None)
263
+
264
+ def get_api_key(self, exchange: str) -> dict:
265
+ return self._get(self.routing_url, f"/api/markets/api-keys/{exchange}", None)
266
+
267
+ def store_api_key(
268
+ self,
269
+ exchange: str,
270
+ api_key: str,
271
+ api_secret: str | None = None,
272
+ passphrase: str | None = None,
273
+ sandbox: bool = False,
274
+ testnet: bool = False,
275
+ ) -> dict:
276
+ body: dict[str, Any] = {"exchange": exchange, "apiKey": api_key}
277
+ if api_secret:
278
+ body["apiSecret"] = api_secret
279
+ if passphrase:
280
+ body["passphrase"] = passphrase
281
+ if sandbox:
282
+ body["sandbox"] = True
283
+ if testnet:
284
+ body["testnet"] = True
285
+ return self._post(self.routing_url, "/api/markets/api-keys", body)
286
+
287
+ def delete_api_key(self, exchange: str) -> dict:
288
+ return self._delete(self.routing_url, f"/api/markets/api-keys/{exchange}")
289
+
290
+ def test_api_key(self, exchange: str) -> dict:
291
+ return self._post(self.routing_url, f"/api/markets/api-keys/{exchange}/test")
292
+
293
+ # ── FastBooks platform API keys (fully self-service, no admin token) ──
294
+ # These authenticate the FastBooks PUBLIC API (/api/public/v1). Any human
295
+ # or agent can mint a free, read-only key rate-limited to 5 req/sec, then
296
+ # inspect or revoke it by presenting the key itself (proof of ownership).
297
+
298
+ def create_fastbooks_api_key(
299
+ self,
300
+ label: str,
301
+ user_id: str | None = None,
302
+ email: str | None = None,
303
+ ) -> dict:
304
+ body: dict[str, Any] = {"label": label}
305
+ if user_id:
306
+ body["userId"] = user_id
307
+ if email:
308
+ body["email"] = email
309
+ return self._post(self.routing_url, "/api/keys", body)
310
+
311
+ def fastbooks_api_key_limits(self) -> dict:
312
+ return self._get(self.routing_url, "/api/keys/limits", None)
313
+
314
+ # ── on-chain (FastBots engine via /api/onchain proxy) ─────────────
315
+ # The proxy is auth+email-gated: needs --token (a JWT) whose email is on the
316
+ # engine allowlist. Paths map to the isolated solana-trading-bots engine.
317
+
318
+ def onchain_get(self, engine_path: str, params: dict | None = None) -> Any:
319
+ return self._get(self.routing_url, f"/api/onchain{engine_path}", params)
320
+
321
+ def onchain_post(self, engine_path: str, data: dict | None = None) -> Any:
322
+ return self._post(self.routing_url, f"/api/onchain{engine_path}", data)
323
+
324
+ def onchain_live_status(self) -> Any:
325
+ return self.onchain_get("/api/exec/live/status")
326
+
327
+ def onchain_stats(self, by_bot: bool = False) -> Any:
328
+ return self.onchain_get("/api/exec/stats/by-bot" if by_bot else "/api/exec/stats")
329
+
330
+ def onchain_strategies(self) -> Any:
331
+ return self.onchain_get("/api/exec/strategies")
332
+
333
+ def onchain_positions(self, status: str | None = None) -> Any:
334
+ return self.onchain_get("/api/exec/positions", {"status": status} if status else None)
335
+
336
+ def onchain_trades(self, limit: int = 40) -> Any:
337
+ return self.onchain_get("/api/exec/trades", {"limit": limit})
338
+
339
+ def onchain_live_start(self, strategy_id: str, opts: dict | None = None) -> Any:
340
+ return self.onchain_post("/api/exec/live/start", {"strategyId": strategy_id, "opts": opts or {}})
341
+
342
+ def onchain_live_stop(self, strategy_id: str) -> Any:
343
+ return self.onchain_post("/api/exec/live/stop", {"strategyId": strategy_id})
344
+
345
+ def fastbooks_api_key_info(self, api_key: str) -> dict:
346
+ return self._get(self.routing_url, "/api/keys/me", headers={"x-api-key": api_key})
347
+
348
+ def revoke_fastbooks_api_key(self, api_key: str) -> dict:
349
+ return self._post(self.routing_url, "/api/keys/revoke", {"apiKey": api_key})
350
+
351
+ # ── FastBooks public API (/api/public/v1, x-api-key) ─────────────
352
+
353
+ def public_markets(
354
+ self,
355
+ api_key: str,
356
+ category: str | None = None,
357
+ exchange: str | None = None,
358
+ search: str | None = None,
359
+ country: str | None = None,
360
+ ) -> dict:
361
+ params: dict[str, Any] = {}
362
+ if category:
363
+ params["category"] = category
364
+ if exchange:
365
+ params["exchange"] = exchange
366
+ if search:
367
+ params["search"] = search
368
+ if country:
369
+ params["country"] = country
370
+ return self._get(self.routing_url, "/api/public/v1/markets", params, headers={"x-api-key": api_key})
371
+
372
+ def public_market_get(self, api_key: str, market_id: str) -> dict:
373
+ return self._get(self.routing_url, f"/api/public/v1/markets/{market_id}", headers={"x-api-key": api_key})
374
+
375
+ def public_markets_summary(self, api_key: str) -> dict:
376
+ return self._get(self.routing_url, "/api/public/v1/stats/markets/summary", headers={"x-api-key": api_key})
377
+
378
+ # ── bots ─────────────────────────────────────────────────────────
379
+
380
+ def create_bot(self, config: dict) -> dict:
381
+ return self._post(self.bots_url, "/bots", config)
382
+
383
+ def list_bots(self, investor_id: str | None = None) -> dict:
384
+ params = {"investor_id": investor_id} if investor_id else None
385
+ return self._get(self.bots_url, "/bots", params)
386
+
387
+ def get_bot(self, bot_id: str) -> dict:
388
+ return self._get(self.bots_url, f"/bots/{bot_id}", None)
389
+
390
+ def update_bot(self, bot_id: str, updates: dict) -> dict:
391
+ return self._patch(self.bots_url, f"/bots/{bot_id}", updates)
392
+
393
+ def delete_bot(self, bot_id: str) -> dict:
394
+ return self._delete(self.bots_url, f"/bots/{bot_id}")
395
+
396
+ def start_bot(self, bot_id: str) -> dict:
397
+ return self._post(self.bots_url, f"/bots/{bot_id}/start")
398
+
399
+ def stop_bot(self, bot_id: str) -> dict:
400
+ return self._post(self.bots_url, f"/bots/{bot_id}/stop")
401
+
402
+ def pause_bot(self, bot_id: str) -> dict:
403
+ return self._post(self.bots_url, f"/bots/{bot_id}/pause")
404
+
405
+ def resume_bot(self, bot_id: str) -> dict:
406
+ return self._post(self.bots_url, f"/bots/{bot_id}/resume")
407
+
408
+ def bot_events(self, bot_id: str, limit: int = 50) -> dict:
409
+ return self._get(self.bots_url, f"/bots/{bot_id}/events", {"limit": limit})
410
+
411
+ def bot_logs(self, bot_id: str, since: int = 0) -> dict:
412
+ return self._get(self.bots_url, f"/bots/{bot_id}/logs", {"since": since})
413
+
414
+ def bot_report(self, bot_id: str) -> dict:
415
+ return self._get(self.bots_url, f"/bots/{bot_id}/report", None)
416
+
417
+ def rebalance_bot(self, bot_id: str) -> dict:
418
+ return self._post(self.bots_url, f"/bots/{bot_id}/rebalance")
419
+
420
+ def bot_trade(self, bot_id: str, intent: dict) -> dict:
421
+ return self._post(self.bots_url, f"/bots/{bot_id}/trade", intent)
422
+
423
+ def deploy_bot(self, config: dict) -> dict:
424
+ """Deploy a bot from a marketplace/template definition (newer path)."""
425
+ return self._post(self.bots_url, "/bots/deploy", config)
426
+
427
+ def bot_command(self, bot_id: str, command: str, args: dict | None = None) -> dict:
428
+ body: dict[str, Any] = {"command": command}
429
+ if args:
430
+ body["args"] = args
431
+ return self._post(self.bots_url, f"/bots/{bot_id}/command", body)
432
+
433
+ def all_events(self, limit: int = 50) -> dict:
434
+ return self._get(self.bots_url, "/events", {"limit": limit})
435
+
436
+ def emergency_stop_all(self) -> dict:
437
+ return self._post(self.bots_url, "/emergency/stop-all")
438
+
439
+ def backtest_report(self, payload: dict) -> dict:
440
+ return self._post(self.bots_url, "/bots/backtest-report", payload)
441
+
442
+ # ── backtesting ──────────────────────────────────────────────────
443
+
444
+ def run_backtest(self, config: dict) -> dict:
445
+ return self._post(self.bots_url, "/bots/crypto-backtest", config)
446
+
447
+ def forward_test(self, config: dict) -> dict:
448
+ return self._post(self.bots_url, "/bots/forward-test", config)
449
+
450
+ def list_backtests(self) -> dict:
451
+ return self._get(self.routing_url, "/api/markets/bots/backtests", None)
452
+
453
+ def delete_backtest(self, backtest_id: str) -> dict:
454
+ return self._delete(self.routing_url, f"/api/markets/bots/backtests/{backtest_id}")
455
+
456
+ # ── DEX ──────────────────────────────────────────────────────────
457
+
458
+ def dex_tickers(self) -> dict:
459
+ return self._get(self.routing_url, "/api/markets/tickers/all", {"category": "dex"})
460
+
461
+ # ── prediction markets ───────────────────────────────────────────
462
+
463
+ def polymarket_search(self, query: str, limit: int = 20) -> dict:
464
+ return self._get(
465
+ self.routing_url,
466
+ "/api/markets/orderbooks",
467
+ {"exchange": "polymarket", "symbol": query},
468
+ )
469
+
470
+ def limitless_market(self, slug: str) -> dict:
471
+ return self._get(
472
+ self.routing_url,
473
+ "/api/markets/orderbooks",
474
+ {"exchange": "limitless", "symbol": slug},
475
+ )
476
+
477
+ # ── Polymarket — markets & events ───────────────────────────────
478
+
479
+ def polymarket_markets_list(
480
+ self, limit: int = 20, offset: int = 0, active: bool | None = None,
481
+ closed: bool | None = None, order: str | None = None, ascending: bool = False,
482
+ ) -> dict:
483
+ params: dict[str, Any] = {"limit": limit, "offset": offset}
484
+ if active is not None:
485
+ params["active"] = active
486
+ if closed is not None:
487
+ params["closed"] = closed
488
+ if order:
489
+ params["order"] = order
490
+ if ascending:
491
+ params["ascending"] = True
492
+ return self._get(self.routing_url, "/api/polymarket/markets", params)
493
+
494
+ def polymarket_market_get(self, id_or_slug: str) -> dict:
495
+ return self._get(self.routing_url, f"/api/polymarket/markets/{id_or_slug}")
496
+
497
+ def polymarket_market_search(self, query: str, limit: int = 10) -> dict:
498
+ return self._get(self.routing_url, "/api/polymarket/markets/search", {"q": query, "limit": limit})
499
+
500
+ def polymarket_events_list(
501
+ self, limit: int = 20, offset: int = 0, tag: str | None = None,
502
+ active: bool | None = None, closed: bool | None = None,
503
+ ) -> dict:
504
+ params: dict[str, Any] = {"limit": limit, "offset": offset}
505
+ if tag:
506
+ params["tag"] = tag
507
+ if active is not None:
508
+ params["active"] = active
509
+ if closed is not None:
510
+ params["closed"] = closed
511
+ return self._get(self.routing_url, "/api/polymarket/events", params)
512
+
513
+ def polymarket_event_get(self, event_id: str) -> dict:
514
+ return self._get(self.routing_url, f"/api/polymarket/events/{event_id}")
515
+
516
+ def polymarket_tags_list(self) -> dict:
517
+ return self._get(self.routing_url, "/api/polymarket/tags")
518
+
519
+ # ── Polymarket — CLOB (order book, prices) ─────────────────────
520
+
521
+ def polymarket_clob_book(self, token_id: str) -> dict:
522
+ return self._get(self.routing_url, "/api/polymarket/clob/book", {"token": token_id})
523
+
524
+ def polymarket_clob_price(self, token_id: str, side: str = "buy") -> dict:
525
+ return self._get(self.routing_url, "/api/polymarket/clob/price", {"token": token_id, "side": side})
526
+
527
+ def polymarket_clob_midpoint(self, token_id: str) -> dict:
528
+ return self._get(self.routing_url, "/api/polymarket/clob/midpoint", {"token": token_id})
529
+
530
+ def polymarket_clob_spread(self, token_id: str) -> dict:
531
+ return self._get(self.routing_url, "/api/polymarket/clob/spread", {"token": token_id})
532
+
533
+ def polymarket_clob_last_trade(self, token_id: str) -> dict:
534
+ return self._get(self.routing_url, "/api/polymarket/clob/last-trade", {"token": token_id})
535
+
536
+ def polymarket_clob_price_history(
537
+ self, token_id: str, interval: str = "1d", fidelity: int = 30,
538
+ ) -> dict:
539
+ return self._get(
540
+ self.routing_url, "/api/polymarket/clob/price-history",
541
+ {"token": token_id, "interval": interval, "fidelity": fidelity},
542
+ )
543
+
544
+ def polymarket_clob_markets(self) -> dict:
545
+ return self._get(self.routing_url, "/api/polymarket/clob/markets")
546
+
547
+ def polymarket_clob_market(self, condition_id: str) -> dict:
548
+ return self._get(self.routing_url, f"/api/polymarket/clob/markets/{condition_id}")
549
+
550
+ # ── Polymarket — trading (authenticated) ───────────────────────
551
+
552
+ def polymarket_create_order(
553
+ self, token_id: str, side: str, price: float, size: float,
554
+ order_type: str = "GTC", private_key: str | None = None,
555
+ ) -> dict:
556
+ return self._post(
557
+ self.routing_url, "/api/polymarket/clob/order",
558
+ {
559
+ "token": token_id, "side": side, "price": price,
560
+ "size": size, "orderType": order_type,
561
+ "privateKey": private_key,
562
+ },
563
+ )
564
+
565
+ def polymarket_market_order(
566
+ self, token_id: str, side: str, amount: float,
567
+ private_key: str | None = None,
568
+ ) -> dict:
569
+ return self._post(
570
+ self.routing_url, "/api/polymarket/clob/market-order",
571
+ {"token": token_id, "side": side, "amount": amount, "privateKey": private_key},
572
+ )
573
+
574
+ def polymarket_cancel_order(self, order_id: str, private_key: str | None = None) -> dict:
575
+ return self._post(
576
+ self.routing_url, "/api/polymarket/clob/cancel",
577
+ {"orderId": order_id, "privateKey": private_key},
578
+ )
579
+
580
+ def polymarket_cancel_all(self, private_key: str | None = None) -> dict:
581
+ return self._post(
582
+ self.routing_url, "/api/polymarket/clob/cancel-all",
583
+ {"privateKey": private_key},
584
+ )
585
+
586
+ def polymarket_cancel_market(self, condition_id: str, private_key: str | None = None) -> dict:
587
+ return self._post(
588
+ self.routing_url, "/api/polymarket/clob/cancel-market",
589
+ {"market": condition_id, "privateKey": private_key},
590
+ )
591
+
592
+ # ── Polymarket — account queries (authenticated) ───────────────
593
+
594
+ def polymarket_orders(self, market: str | None = None, private_key: str | None = None) -> dict:
595
+ params: dict[str, Any] = {}
596
+ if market:
597
+ params["market"] = market
598
+ if private_key:
599
+ params["privateKey"] = private_key
600
+ return self._get(self.routing_url, "/api/polymarket/clob/orders", params)
601
+
602
+ def polymarket_trades(self, private_key: str | None = None) -> dict:
603
+ params: dict[str, Any] = {}
604
+ if private_key:
605
+ params["privateKey"] = private_key
606
+ return self._get(self.routing_url, "/api/polymarket/clob/trades", params)
607
+
608
+ def polymarket_balance(self, asset_type: str = "collateral", token_id: str | None = None, private_key: str | None = None) -> dict:
609
+ params: dict[str, Any] = {"assetType": asset_type}
610
+ if token_id:
611
+ params["token"] = token_id
612
+ if private_key:
613
+ params["privateKey"] = private_key
614
+ return self._get(self.routing_url, "/api/polymarket/clob/balance", params)
615
+
616
+ # ── Polymarket — on-chain data (public) ────────────────────────
617
+
618
+ def polymarket_data_positions(self, address: str) -> dict:
619
+ return self._get(self.routing_url, f"/api/polymarket/data/positions/{address}")
620
+
621
+ def polymarket_data_value(self, address: str) -> dict:
622
+ return self._get(self.routing_url, f"/api/polymarket/data/value/{address}")
623
+
624
+ def polymarket_data_trades(self, address: str, limit: int = 50) -> dict:
625
+ return self._get(self.routing_url, f"/api/polymarket/data/trades/{address}", {"limit": limit})
626
+
627
+ def polymarket_data_leaderboard(self, period: str = "month", order_by: str = "pnl", limit: int = 20) -> dict:
628
+ return self._get(
629
+ self.routing_url, "/api/polymarket/data/leaderboard",
630
+ {"period": period, "orderBy": order_by, "limit": limit},
631
+ )
632
+
633
+ # ── Polymarket — CTF operations (on-chain, authenticated) ──────
634
+
635
+ def polymarket_ctf_split(self, condition_id: str, amount: float, private_key: str | None = None) -> dict:
636
+ return self._post(
637
+ self.routing_url, "/api/polymarket/ctf/split",
638
+ {"condition": condition_id, "amount": amount, "privateKey": private_key},
639
+ )
640
+
641
+ def polymarket_ctf_merge(self, condition_id: str, amount: float, private_key: str | None = None) -> dict:
642
+ return self._post(
643
+ self.routing_url, "/api/polymarket/ctf/merge",
644
+ {"condition": condition_id, "amount": amount, "privateKey": private_key},
645
+ )
646
+
647
+ def polymarket_ctf_redeem(self, condition_id: str, private_key: str | None = None) -> dict:
648
+ return self._post(
649
+ self.routing_url, "/api/polymarket/ctf/redeem",
650
+ {"condition": condition_id, "privateKey": private_key},
651
+ )
652
+
653
+ # ── Polymarket — approvals ─────────────────────────────────────
654
+
655
+ def polymarket_approve_check(self, address: str | None = None) -> dict:
656
+ params = {"address": address} if address else None
657
+ return self._get(self.routing_url, "/api/polymarket/approve/check", params)
658
+
659
+ def polymarket_approve_set(self, private_key: str | None = None) -> dict:
660
+ return self._post(
661
+ self.routing_url, "/api/polymarket/approve/set",
662
+ {"privateKey": private_key},
663
+ )
664
+
665
+ # ── Polymarket — bridge ────────────────────────────────────────
666
+
667
+ def polymarket_bridge_deposit(self, address: str) -> dict:
668
+ return self._get(self.routing_url, f"/api/polymarket/bridge/deposit/{address}")
669
+
670
+ def polymarket_bridge_supported_assets(self) -> dict:
671
+ return self._get(self.routing_url, "/api/polymarket/bridge/supported-assets")
672
+
673
+ def polymarket_bridge_status(self, deposit_address: str) -> dict:
674
+ return self._get(self.routing_url, f"/api/polymarket/bridge/status/{deposit_address}")
675
+
676
+ # ── Polymarket — wallet management ─────────────────────────────
677
+
678
+ def polymarket_wallet_create(self, signature_type: str = "proxy") -> dict:
679
+ return self._post(
680
+ self.routing_url, "/api/polymarket/wallet/create",
681
+ {"signatureType": signature_type},
682
+ )
683
+
684
+ def polymarket_wallet_import(self, private_key: str, signature_type: str = "proxy") -> dict:
685
+ return self._post(
686
+ self.routing_url, "/api/polymarket/wallet/import",
687
+ {"privateKey": private_key, "signatureType": signature_type},
688
+ )
689
+
690
+ def polymarket_wallet_show(self) -> dict:
691
+ return self._get(self.routing_url, "/api/polymarket/wallet/show")
692
+
693
+ def polymarket_wallet_address(self) -> dict:
694
+ return self._get(self.routing_url, "/api/polymarket/wallet/address")
695
+
696
+ def polymarket_wallet_reset(self) -> dict:
697
+ return self._post(self.routing_url, "/api/polymarket/wallet/reset")
698
+
699
+ # ── Polymarket — market/event tags ─────────────────────────────
700
+
701
+ def polymarket_market_tags(self, market_id: str) -> dict:
702
+ return self._get(self.routing_url, f"/api/polymarket/markets/{market_id}/tags")
703
+
704
+ def polymarket_event_tags(self, event_id: str) -> dict:
705
+ return self._get(self.routing_url, f"/api/polymarket/events/{event_id}/tags")
706
+
707
+ # ── Polymarket — tags, series, comments, profiles, sports ──────
708
+
709
+ def polymarket_tag_get(self, id_or_slug: str) -> dict:
710
+ return self._get(self.routing_url, f"/api/polymarket/tags/{id_or_slug}")
711
+
712
+ def polymarket_tags_related(self, id_or_slug: str) -> dict:
713
+ return self._get(self.routing_url, f"/api/polymarket/tags/{id_or_slug}/related")
714
+
715
+ def polymarket_series_list(self, limit: int = 20, offset: int = 0) -> dict:
716
+ return self._get(self.routing_url, "/api/polymarket/series", {"limit": limit, "offset": offset})
717
+
718
+ def polymarket_series_get(self, series_id: str) -> dict:
719
+ return self._get(self.routing_url, f"/api/polymarket/series/{series_id}")
720
+
721
+ def polymarket_comments_list(self, entity_type: str, entity_id: str) -> dict:
722
+ return self._get(
723
+ self.routing_url, "/api/polymarket/comments",
724
+ {"entityType": entity_type, "entityId": entity_id},
725
+ )
726
+
727
+ def polymarket_comment_get(self, comment_id: str) -> dict:
728
+ return self._get(self.routing_url, f"/api/polymarket/comments/{comment_id}")
729
+
730
+ def polymarket_comments_by_user(self, address: str) -> dict:
731
+ return self._get(self.routing_url, f"/api/polymarket/comments/user/{address}")
732
+
733
+ def polymarket_profile_get(self, address: str) -> dict:
734
+ return self._get(self.routing_url, f"/api/polymarket/profiles/{address}")
735
+
736
+ def polymarket_sports_list(self) -> dict:
737
+ return self._get(self.routing_url, "/api/polymarket/sports")
738
+
739
+ def polymarket_sports_market_types(self) -> dict:
740
+ return self._get(self.routing_url, "/api/polymarket/sports/market-types")
741
+
742
+ def polymarket_sports_teams(self, league: str, limit: int = 50) -> dict:
743
+ return self._get(self.routing_url, "/api/polymarket/sports/teams", {"league": league, "limit": limit})
744
+
745
+ # ── Polymarket — CLOB batch & metadata ─────────────────────────
746
+
747
+ def polymarket_clob_books(self, token_ids: str) -> dict:
748
+ return self._get(self.routing_url, "/api/polymarket/clob/books", {"tokens": token_ids})
749
+
750
+ def polymarket_clob_batch_prices(self, token_ids: str, side: str = "buy") -> dict:
751
+ return self._get(self.routing_url, "/api/polymarket/clob/batch-prices", {"tokens": token_ids, "side": side})
752
+
753
+ def polymarket_clob_midpoints(self, token_ids: str) -> dict:
754
+ return self._get(self.routing_url, "/api/polymarket/clob/midpoints", {"tokens": token_ids})
755
+
756
+ def polymarket_clob_spreads(self, token_ids: str) -> dict:
757
+ return self._get(self.routing_url, "/api/polymarket/clob/spreads", {"tokens": token_ids})
758
+
759
+ def polymarket_clob_last_trades(self, token_ids: str) -> dict:
760
+ return self._get(self.routing_url, "/api/polymarket/clob/last-trades", {"tokens": token_ids})
761
+
762
+ def polymarket_clob_tick_size(self, token_id: str) -> dict:
763
+ return self._get(self.routing_url, "/api/polymarket/clob/tick-size", {"token": token_id})
764
+
765
+ def polymarket_clob_fee_rate(self, token_id: str) -> dict:
766
+ return self._get(self.routing_url, "/api/polymarket/clob/fee-rate", {"token": token_id})
767
+
768
+ def polymarket_clob_neg_risk(self, token_id: str) -> dict:
769
+ return self._get(self.routing_url, "/api/polymarket/clob/neg-risk", {"token": token_id})
770
+
771
+ def polymarket_clob_ok(self) -> dict:
772
+ return self._get(self.routing_url, "/api/polymarket/clob/ok")
773
+
774
+ def polymarket_clob_time(self) -> dict:
775
+ return self._get(self.routing_url, "/api/polymarket/clob/time")
776
+
777
+ def polymarket_clob_geoblock(self) -> dict:
778
+ return self._get(self.routing_url, "/api/polymarket/clob/geoblock")
779
+
780
+ # ── Polymarket — CLOB trading extras ───────────────────────────
781
+
782
+ def polymarket_post_orders(
783
+ self, token_ids: str, side: str, prices: str, sizes: str,
784
+ order_type: str = "GTC", private_key: str | None = None,
785
+ ) -> dict:
786
+ return self._post(
787
+ self.routing_url, "/api/polymarket/clob/post-orders",
788
+ {
789
+ "tokens": token_ids, "side": side, "prices": prices,
790
+ "sizes": sizes, "orderType": order_type, "privateKey": private_key,
791
+ },
792
+ )
793
+
794
+ def polymarket_cancel_orders(self, order_ids: str, private_key: str | None = None) -> dict:
795
+ return self._post(
796
+ self.routing_url, "/api/polymarket/clob/cancel-orders",
797
+ {"orderIds": order_ids, "privateKey": private_key},
798
+ )
799
+
800
+ def polymarket_order_get(self, order_id: str, private_key: str | None = None) -> dict:
801
+ params: dict[str, Any] = {}
802
+ if private_key:
803
+ params["privateKey"] = private_key
804
+ return self._get(self.routing_url, f"/api/polymarket/clob/orders/{order_id}", params)
805
+
806
+ def polymarket_update_balance(self, asset_type: str = "collateral", token_id: str | None = None, private_key: str | None = None) -> dict:
807
+ body: dict[str, Any] = {"assetType": asset_type, "privateKey": private_key}
808
+ if token_id:
809
+ body["token"] = token_id
810
+ return self._post(self.routing_url, "/api/polymarket/clob/update-balance", body)
811
+
812
+ # ── Polymarket — rewards & API keys (authenticated) ────────────
813
+
814
+ def polymarket_rewards(self, date: str, private_key: str | None = None) -> dict:
815
+ return self._get(self.routing_url, "/api/polymarket/clob/rewards", {"date": date, "privateKey": private_key})
816
+
817
+ def polymarket_earnings(self, date: str, private_key: str | None = None) -> dict:
818
+ return self._get(self.routing_url, "/api/polymarket/clob/earnings", {"date": date, "privateKey": private_key})
819
+
820
+ def polymarket_earnings_markets(self, date: str, private_key: str | None = None) -> dict:
821
+ return self._get(self.routing_url, "/api/polymarket/clob/earnings-markets", {"date": date, "privateKey": private_key})
822
+
823
+ def polymarket_reward_percentages(self) -> dict:
824
+ return self._get(self.routing_url, "/api/polymarket/clob/reward-percentages")
825
+
826
+ def polymarket_current_rewards(self) -> dict:
827
+ return self._get(self.routing_url, "/api/polymarket/clob/current-rewards")
828
+
829
+ def polymarket_market_reward(self, condition_id: str) -> dict:
830
+ return self._get(self.routing_url, f"/api/polymarket/clob/market-reward/{condition_id}")
831
+
832
+ def polymarket_order_scoring(self, order_id: str, private_key: str | None = None) -> dict:
833
+ return self._get(self.routing_url, f"/api/polymarket/clob/order-scoring/{order_id}", {"privateKey": private_key} if private_key else None)
834
+
835
+ def polymarket_orders_scoring(self, order_ids: str, private_key: str | None = None) -> dict:
836
+ return self._get(self.routing_url, "/api/polymarket/clob/orders-scoring", {"orderIds": order_ids, "privateKey": private_key} if private_key else {"orderIds": order_ids})
837
+
838
+ def polymarket_api_keys(self, private_key: str | None = None) -> dict:
839
+ return self._get(self.routing_url, "/api/polymarket/clob/api-keys", {"privateKey": private_key} if private_key else None)
840
+
841
+ def polymarket_create_api_key(self, private_key: str | None = None) -> dict:
842
+ return self._post(self.routing_url, "/api/polymarket/clob/api-keys", {"privateKey": private_key})
843
+
844
+ def polymarket_delete_api_key(self, private_key: str | None = None) -> dict:
845
+ return self._delete(self.routing_url, "/api/polymarket/clob/api-keys", {"privateKey": private_key} if private_key else None)
846
+
847
+ def polymarket_account_status(self, private_key: str | None = None) -> dict:
848
+ return self._get(self.routing_url, "/api/polymarket/clob/account-status", {"privateKey": private_key} if private_key else None)
849
+
850
+ def polymarket_notifications(self, private_key: str | None = None) -> dict:
851
+ return self._get(self.routing_url, "/api/polymarket/clob/notifications", {"privateKey": private_key} if private_key else None)
852
+
853
+ def polymarket_delete_notifications(self, notification_ids: str, private_key: str | None = None) -> dict:
854
+ return self._post(
855
+ self.routing_url, "/api/polymarket/clob/delete-notifications",
856
+ {"notificationIds": notification_ids, "privateKey": private_key},
857
+ )
858
+
859
+ # ── Polymarket — extended on-chain data ────────────────────────
860
+
861
+ def polymarket_data_closed_positions(self, address: str, limit: int = 25, offset: int = 0) -> dict:
862
+ return self._get(self.routing_url, f"/api/polymarket/data/closed-positions/{address}", {"limit": limit, "offset": offset})
863
+
864
+ def polymarket_data_traded(self, address: str) -> dict:
865
+ return self._get(self.routing_url, f"/api/polymarket/data/traded/{address}")
866
+
867
+ def polymarket_data_activity(self, address: str, limit: int = 25, offset: int = 0) -> dict:
868
+ return self._get(self.routing_url, f"/api/polymarket/data/activity/{address}", {"limit": limit, "offset": offset})
869
+
870
+ def polymarket_data_holders(self, condition_id: str, limit: int = 10) -> dict:
871
+ return self._get(self.routing_url, f"/api/polymarket/data/holders/{condition_id}", {"limit": limit})
872
+
873
+ def polymarket_data_open_interest(self, condition_id: str) -> dict:
874
+ return self._get(self.routing_url, f"/api/polymarket/data/open-interest/{condition_id}")
875
+
876
+ def polymarket_data_volume(self, event_id: str) -> dict:
877
+ return self._get(self.routing_url, f"/api/polymarket/data/volume/{event_id}")
878
+
879
+ def polymarket_data_builder_leaderboard(self, period: str = "month", limit: int = 25, offset: int = 0) -> dict:
880
+ return self._get(self.routing_url, "/api/polymarket/data/builder-leaderboard", {"period": period, "limit": limit, "offset": offset})
881
+
882
+ def polymarket_data_builder_volume(self, period: str = "month") -> dict:
883
+ return self._get(self.routing_url, "/api/polymarket/data/builder-volume", {"period": period})
884
+
885
+ # ── Polymarket — CTF extras ────────────────────────────────────
886
+
887
+ def polymarket_ctf_redeem_neg_risk(self, condition_id: str, amounts: str, private_key: str | None = None) -> dict:
888
+ return self._post(
889
+ self.routing_url, "/api/polymarket/ctf/redeem-neg-risk",
890
+ {"condition": condition_id, "amounts": amounts, "privateKey": private_key},
891
+ )
892
+
893
+ def polymarket_ctf_condition_id(self, oracle: str, question: str, outcomes: int) -> dict:
894
+ return self._get(
895
+ self.routing_url, "/api/polymarket/ctf/condition-id",
896
+ {"oracle": oracle, "question": question, "outcomes": outcomes},
897
+ )
898
+
899
+ def polymarket_ctf_collection_id(self, condition: str, index_set: int, parent_collection: str | None = None) -> dict:
900
+ params: dict[str, Any] = {"condition": condition, "indexSet": index_set}
901
+ if parent_collection:
902
+ params["parentCollection"] = parent_collection
903
+ return self._get(self.routing_url, "/api/polymarket/ctf/collection-id", params)
904
+
905
+ def polymarket_ctf_position_id(self, collateral: str, collection: str) -> dict:
906
+ return self._get(
907
+ self.routing_url, "/api/polymarket/ctf/position-id",
908
+ {"collateral": collateral, "collection": collection},
909
+ )
910
+
911
+ # ── Jupiter swap ────────────────────────────────────────────────
912
+
913
+ def jupiter_quote(
914
+ self,
915
+ input_mint: str,
916
+ output_mint: str,
917
+ amount: float,
918
+ slippage_bps: int = 100,
919
+ ) -> dict:
920
+ return self._post(
921
+ self.routing_url,
922
+ "/api/swap/jupiter/quote",
923
+ {
924
+ "inputMint": input_mint,
925
+ "outputMint": output_mint,
926
+ "amount": amount,
927
+ "slippageBps": slippage_bps,
928
+ },
929
+ )
930
+
931
+ def jupiter_swap_execute(
932
+ self,
933
+ input_mint: str,
934
+ output_mint: str,
935
+ amount: float,
936
+ slippage_bps: int = 100,
937
+ wallet_key: str | None = None,
938
+ priority_fee: int = 50000,
939
+ ) -> dict:
940
+ return self._post(
941
+ self.routing_url,
942
+ "/api/swap/jupiter/execute",
943
+ {
944
+ "inputMint": input_mint,
945
+ "outputMint": output_mint,
946
+ "amount": amount,
947
+ "slippageBps": slippage_bps,
948
+ "walletKey": wallet_key,
949
+ "priorityFee": priority_fee,
950
+ },
951
+ )
952
+
953
+ # ── Uniswap swap ───────────────────────────────────────────────
954
+
955
+ def uniswap_quote(
956
+ self,
957
+ input_address: str,
958
+ output_address: str,
959
+ amount: float,
960
+ chain: str = "ethereum",
961
+ slippage_bps: int = 50,
962
+ ) -> dict:
963
+ return self._post(
964
+ self.routing_url,
965
+ "/api/swap/uniswap/quote",
966
+ {
967
+ "inputAddress": input_address,
968
+ "outputAddress": output_address,
969
+ "amount": amount,
970
+ "chain": chain,
971
+ "slippageBps": slippage_bps,
972
+ },
973
+ )
974
+
975
+ def uniswap_swap_execute(
976
+ self,
977
+ input_address: str,
978
+ output_address: str,
979
+ amount: float,
980
+ chain: str = "ethereum",
981
+ slippage_bps: int = 50,
982
+ wallet_key: str | None = None,
983
+ gas_limit: int | None = None,
984
+ ) -> dict:
985
+ body: dict[str, Any] = {
986
+ "inputAddress": input_address,
987
+ "outputAddress": output_address,
988
+ "amount": amount,
989
+ "chain": chain,
990
+ "slippageBps": slippage_bps,
991
+ "walletKey": wallet_key,
992
+ }
993
+ if gas_limit:
994
+ body["gasLimit"] = gas_limit
995
+ return self._post(self.routing_url, "/api/swap/uniswap/execute", body)
996
+
997
+ # ── SushiSwap ──────────────────────────────────────────────────
998
+
999
+ def sushi_swap(
1000
+ self,
1001
+ input_address: str,
1002
+ output_address: str,
1003
+ amount: float,
1004
+ chain: str = "ethereum",
1005
+ slippage_bps: int = 50,
1006
+ wallet_key: str | None = None,
1007
+ quote_only: bool = False,
1008
+ ) -> dict:
1009
+ return self._post(
1010
+ self.routing_url,
1011
+ "/api/swap/sushi/execute",
1012
+ {
1013
+ "inputAddress": input_address,
1014
+ "outputAddress": output_address,
1015
+ "amount": amount,
1016
+ "chain": chain,
1017
+ "slippageBps": slippage_bps,
1018
+ "walletKey": wallet_key,
1019
+ "quoteOnly": quote_only,
1020
+ },
1021
+ )
1022
+
1023
+ # ── GMX V2 ─────────────────────────────────────────────────────
1024
+
1025
+ def gmx_markets(self) -> dict:
1026
+ return self._get(self.routing_url, "/api/dex/gmx/markets", None)
1027
+
1028
+ def gmx_order(
1029
+ self,
1030
+ symbol: str,
1031
+ side: str,
1032
+ size_usd: float,
1033
+ leverage: float = 1.0,
1034
+ order_type: str = "market",
1035
+ price: float | None = None,
1036
+ stop_loss: float | None = None,
1037
+ take_profit: float | None = None,
1038
+ wallet_key: str | None = None,
1039
+ ) -> dict:
1040
+ body: dict[str, Any] = {
1041
+ "symbol": symbol,
1042
+ "side": side,
1043
+ "sizeUsd": size_usd,
1044
+ "leverage": leverage,
1045
+ "orderType": order_type,
1046
+ "walletKey": wallet_key,
1047
+ }
1048
+ if price is not None:
1049
+ body["price"] = price
1050
+ if stop_loss is not None:
1051
+ body["stopLoss"] = stop_loss
1052
+ if take_profit is not None:
1053
+ body["takeProfit"] = take_profit
1054
+ return self._post(self.routing_url, "/api/dex/gmx/order", body)
1055
+
1056
+ def gmx_positions(self, wallet_key: str) -> dict:
1057
+ return self._post(self.routing_url, "/api/dex/gmx/positions", {"walletKey": wallet_key})
1058
+
1059
+ def gmx_close_position(self, position_key: str, wallet_key: str) -> dict:
1060
+ return self._post(
1061
+ self.routing_url,
1062
+ "/api/dex/gmx/close",
1063
+ {"positionKey": position_key, "walletKey": wallet_key},
1064
+ )
1065
+
1066
+ # ── dYdX V4 ────────────────────────────────────────────────────
1067
+
1068
+ def dydx_markets(self) -> dict:
1069
+ return self._get(self.routing_url, "/api/dex/dydx/markets", None)
1070
+
1071
+ def dydx_order(
1072
+ self,
1073
+ symbol: str,
1074
+ side: str,
1075
+ size: float,
1076
+ order_type: str = "market",
1077
+ price: float | None = None,
1078
+ leverage: float = 1.0,
1079
+ reduce_only: bool = False,
1080
+ time_in_force: str = "GTT",
1081
+ mnemonic: str | None = None,
1082
+ ) -> dict:
1083
+ body: dict[str, Any] = {
1084
+ "symbol": symbol,
1085
+ "side": side,
1086
+ "size": size,
1087
+ "orderType": order_type,
1088
+ "leverage": leverage,
1089
+ "reduceOnly": reduce_only,
1090
+ "timeInForce": time_in_force,
1091
+ "mnemonic": mnemonic,
1092
+ }
1093
+ if price is not None:
1094
+ body["price"] = price
1095
+ return self._post(self.routing_url, "/api/dex/dydx/order", body)
1096
+
1097
+ def dydx_positions(self, mnemonic: str) -> dict:
1098
+ return self._post(self.routing_url, "/api/dex/dydx/positions", {"mnemonic": mnemonic})
1099
+
1100
+ def dydx_orderbook(self, symbol: str) -> dict:
1101
+ return self._get(self.routing_url, "/api/dex/dydx/orderbook", {"symbol": symbol})
1102
+
1103
+ # ── Aevo ───────────────────────────────────────────────────────
1104
+
1105
+ def aevo_markets(self, market_type: str = "all") -> dict:
1106
+ return self._get(self.routing_url, "/api/dex/aevo/markets", {"type": market_type})
1107
+
1108
+ def aevo_order(
1109
+ self,
1110
+ instrument: str,
1111
+ side: str,
1112
+ amount: float,
1113
+ order_type: str = "market",
1114
+ price: float | None = None,
1115
+ leverage: float = 1.0,
1116
+ reduce_only: bool = False,
1117
+ wallet_key: str | None = None,
1118
+ api_key: str | None = None,
1119
+ api_secret: str | None = None,
1120
+ ) -> dict:
1121
+ body: dict[str, Any] = {
1122
+ "instrument": instrument,
1123
+ "side": side,
1124
+ "amount": amount,
1125
+ "orderType": order_type,
1126
+ "leverage": leverage,
1127
+ "reduceOnly": reduce_only,
1128
+ "walletKey": wallet_key,
1129
+ "apiKey": api_key,
1130
+ "apiSecret": api_secret,
1131
+ }
1132
+ if price is not None:
1133
+ body["price"] = price
1134
+ return self._post(self.routing_url, "/api/dex/aevo/order", body)
1135
+
1136
+ def aevo_positions(self, api_key: str, api_secret: str | None = None) -> dict:
1137
+ return self._post(
1138
+ self.routing_url,
1139
+ "/api/dex/aevo/positions",
1140
+ {"apiKey": api_key, "apiSecret": api_secret},
1141
+ )
1142
+
1143
+ def aevo_orderbook(self, instrument: str) -> dict:
1144
+ return self._get(self.routing_url, "/api/dex/aevo/orderbook", {"instrument": instrument})
1145
+
1146
+ # ── wallet creation ────────────────────────────────────────────
1147
+
1148
+ def create_wallet(self, wallet_type: str, options: dict | None = None) -> dict:
1149
+ body: dict[str, Any] = {"walletType": wallet_type}
1150
+ if options:
1151
+ body.update(options)
1152
+ return self._post(self.routing_url, "/api/wallet/create", body)
1153
+
1154
+ def link_wallet(self, wallet_address: str, wallet_type: str, is_primary: bool = False) -> dict:
1155
+ return self._post(
1156
+ self.routing_url,
1157
+ "/api/wallet/link",
1158
+ {"walletAddress": wallet_address, "walletType": wallet_type, "isPrimary": is_primary},
1159
+ )
1160
+
1161
+ def list_wallets(self) -> dict:
1162
+ return self._get(self.routing_url, "/api/wallet/list", None)
1163
+
1164
+ def wallet_balance_onchain(self, wallet_address: str, chain: str) -> dict:
1165
+ return self._get(
1166
+ self.routing_url,
1167
+ "/api/wallet/balance",
1168
+ {"address": wallet_address, "chain": chain},
1169
+ )