fere-sdk 0.4.0.dev33__tar.gz → 0.5.0.dev35__tar.gz

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.
@@ -8,6 +8,8 @@ __pycache__
8
8
  # Devcontainer local env (contains secrets)
9
9
  .devcontainer/.env.local
10
10
  .devcontainer/.env.local.override
11
+ .devcontainer/.env.runtime.local
12
+ .devcontainer/.env.runtime.*.local
11
13
  .devcontainer/.env.gitpod
12
14
 
13
15
  # Bruno gateway collection (GATEWAY_AGENT_ID / GATEWAY_PRIVATE_KEY_B64)
@@ -43,6 +45,7 @@ dev/logs/*
43
45
  # Cursor SEO skill — regenerated report JSON (not source)
44
46
  .cursor/skills/seo-agent/artifacts/
45
47
  .cursor/skills/docker-images-cve-scan/artifacts/
48
+ docs/plans/artifacts/
46
49
 
47
50
  # Skills installed locally by the skills package manager (see skills-lock.json)
48
51
  .claude/skills/stripe-best-practices
@@ -52,6 +55,7 @@ dev/logs/*
52
55
 
53
56
  # agentmemory iii state (SQLite + streams under .agentmemory/data/)
54
57
  .agentmemory/data/
58
+ .devcontainer/agentmemory-mcp/node_modules/
55
59
 
56
60
  # Local GEO upstream reference (optional clone; not part of the product tree)
57
61
  geo-seo/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fere-sdk
3
- Version: 0.4.0.dev33
3
+ Version: 0.5.0.dev35
4
4
  Summary: Python SDK for the FereAI Gateway API
5
5
  Author-email: Fere AI <info@fere.ai>
6
6
  License-Expression: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "fere-sdk"
3
- version = "0.4.0.dev33"
3
+ version = "0.5.0.dev35"
4
4
  description = "Python SDK for the FereAI Gateway API"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -22,6 +22,11 @@ from .models import (
22
22
  DepositRequest,
23
23
  HooksRequest,
24
24
  LimitOrderRequest,
25
+ PerpCancelRequest,
26
+ PerpCloseRequest,
27
+ PerpFundRequest,
28
+ PerpOpenRequest,
29
+ PerpWithdrawRequest,
25
30
  SecurityCheckResponse,
26
31
  SwapRequest,
27
32
  TokenToCheck,
@@ -274,10 +279,124 @@ class FereClient:
274
279
  req = WithdrawRequest(position_id=position_id, amount_usdc=amount_usdc)
275
280
  return await self._api.withdraw(req, wait=True, timeout=timeout)
276
281
 
282
+ # ----------------------------------------------------------
283
+ # Perp — venue-neutral perpetual futures (blocks until complete)
284
+ # ----------------------------------------------------------
285
+
286
+ async def perp_open(
287
+ self,
288
+ asset: str,
289
+ is_buy: bool,
290
+ size: float,
291
+ leverage: int = 1,
292
+ is_cross: bool = True,
293
+ order_type: str = "market",
294
+ limit_price: float | None = None,
295
+ slippage_pct: float = 0.5,
296
+ reduce_only: bool = False,
297
+ tp_price: float | None = None,
298
+ sl_price: float | None = None,
299
+ timeout: int = DEFAULT_TIMEOUT,
300
+ ) -> dict:
301
+ """Open a perpetual position and wait for completion.
302
+
303
+ ``size`` is in contracts (not smallest units).
304
+ """
305
+ req = PerpOpenRequest(
306
+ asset=asset,
307
+ is_buy=is_buy,
308
+ size=size,
309
+ leverage=leverage,
310
+ is_cross=is_cross,
311
+ order_type=order_type,
312
+ limit_price=limit_price,
313
+ slippage_pct=slippage_pct,
314
+ reduce_only=reduce_only,
315
+ tp_price=tp_price,
316
+ sl_price=sl_price,
317
+ )
318
+ return await self._api.perp_open(req, wait=True, timeout=timeout)
319
+
320
+ async def perp_close(
321
+ self,
322
+ asset: str,
323
+ size: float | None = None,
324
+ slippage_pct: float = 0.5,
325
+ timeout: int = DEFAULT_TIMEOUT,
326
+ ) -> dict:
327
+ """Close a perpetual position and wait for completion."""
328
+ req = PerpCloseRequest(
329
+ asset=asset, size=size, slippage_pct=slippage_pct
330
+ )
331
+ return await self._api.perp_close(req, wait=True, timeout=timeout)
332
+
333
+ async def perp_cancel_order(
334
+ self,
335
+ asset: str,
336
+ order_id: int,
337
+ timeout: int = DEFAULT_TIMEOUT,
338
+ ) -> dict:
339
+ """Cancel an open perp order and wait for completion."""
340
+ req = PerpCancelRequest(asset=asset, order_id=order_id)
341
+ return await self._api.perp_cancel_order(
342
+ req, wait=True, timeout=timeout
343
+ )
344
+
345
+ async def perp_setup(
346
+ self,
347
+ force_rerun: bool = False,
348
+ timeout: int = DEFAULT_TIMEOUT,
349
+ ) -> dict:
350
+ """Provision perp trading and wait for completion."""
351
+ return await self._api.perp_setup(
352
+ force_rerun=force_rerun, wait=True, timeout=timeout
353
+ )
354
+
355
+ async def perp_fund(
356
+ self,
357
+ amount: str,
358
+ source_chain_id: int,
359
+ source_token: str | None = None,
360
+ display_amount_usd: float | None = None,
361
+ timeout: int = DEFAULT_TIMEOUT,
362
+ ) -> dict:
363
+ """Fund the perp account and wait for completion."""
364
+ req = PerpFundRequest(
365
+ amount=amount,
366
+ source_chain_id=source_chain_id,
367
+ source_token=source_token,
368
+ display_amount_usd=display_amount_usd,
369
+ )
370
+ return await self._api.perp_fund(req, wait=True, timeout=timeout)
371
+
372
+ async def perp_withdraw(
373
+ self,
374
+ amount_usd: float,
375
+ destination_chain_id: int,
376
+ destination_token: str | None = None,
377
+ timeout: int = DEFAULT_TIMEOUT,
378
+ ) -> dict:
379
+ """Withdraw perp collateral and wait for completion."""
380
+ req = PerpWithdrawRequest(
381
+ amount_usd=amount_usd,
382
+ destination_chain_id=destination_chain_id,
383
+ destination_token=destination_token,
384
+ )
385
+ return await self._api.perp_withdraw(req, wait=True, timeout=timeout)
386
+
277
387
  # ----------------------------------------------------------
278
388
  # Read-only (pass-through)
279
389
  # ----------------------------------------------------------
280
390
 
391
+ async def get_perp_markets(self, search_text: str | None = None) -> dict:
392
+ return await self._api.get_perp_markets(search_text=search_text)
393
+
394
+ async def get_perp_setup_status(self) -> dict:
395
+ return await self._api.get_perp_setup_status()
396
+
397
+ async def get_perp_orders(self) -> dict:
398
+ return await self._api.get_perp_orders()
399
+
281
400
  async def get_wallets(self) -> dict:
282
401
  return await self._api.get_wallets()
283
402
 
@@ -16,6 +16,11 @@ from .models import (
16
16
  DepositRequest,
17
17
  HooksRequest,
18
18
  LimitOrderRequest,
19
+ PerpCancelRequest,
20
+ PerpCloseRequest,
21
+ PerpFundRequest,
22
+ PerpOpenRequest,
23
+ PerpWithdrawRequest,
19
24
  SecurityCheckResponse,
20
25
  SecurityCheckSummary,
21
26
  SwapRequest,
@@ -420,6 +425,111 @@ class FereAPI:
420
425
  """GET /v1/earn/positions"""
421
426
  return await self._authed_get("/v1/earn/positions")
422
427
 
428
+ # ----------------------------------------------------------
429
+ # Perp (venue-neutral perpetual futures)
430
+ # ----------------------------------------------------------
431
+
432
+ async def get_perp_markets(self, search_text: str | None = None) -> dict:
433
+ """GET /v1/perp/markets"""
434
+ params = {}
435
+ if search_text:
436
+ params["search_text"] = search_text
437
+ return await self._authed_get("/v1/perp/markets", params=params)
438
+
439
+ async def get_perp_setup_status(self) -> dict:
440
+ """GET /v1/perp/setup"""
441
+ return await self._authed_get("/v1/perp/setup")
442
+
443
+ async def get_perp_orders(self) -> dict:
444
+ """GET /v1/perp/orders"""
445
+ return await self._authed_get("/v1/perp/orders")
446
+
447
+ async def perp_setup(
448
+ self,
449
+ force_rerun: bool = False,
450
+ wait: bool = False,
451
+ timeout: int = 120,
452
+ ) -> TaskResponse | dict:
453
+ """POST /v1/perp/setup"""
454
+ return await self._perp_task(
455
+ "/v1/perp/setup", {"force_rerun": force_rerun}, wait, timeout
456
+ )
457
+
458
+ async def perp_open(
459
+ self,
460
+ request: PerpOpenRequest,
461
+ wait: bool = False,
462
+ timeout: int = 120,
463
+ ) -> TaskResponse | dict:
464
+ """POST /v1/perp/open"""
465
+ return await self._perp_task(
466
+ "/v1/perp/open", request.to_dict(), wait, timeout
467
+ )
468
+
469
+ async def perp_close(
470
+ self,
471
+ request: PerpCloseRequest,
472
+ wait: bool = False,
473
+ timeout: int = 120,
474
+ ) -> TaskResponse | dict:
475
+ """POST /v1/perp/close"""
476
+ return await self._perp_task(
477
+ "/v1/perp/close", request.to_dict(), wait, timeout
478
+ )
479
+
480
+ async def perp_cancel_order(
481
+ self,
482
+ request: PerpCancelRequest,
483
+ wait: bool = False,
484
+ timeout: int = 120,
485
+ ) -> TaskResponse | dict:
486
+ """POST /v1/perp/orders/cancel"""
487
+ return await self._perp_task(
488
+ "/v1/perp/orders/cancel", request.to_dict(), wait, timeout
489
+ )
490
+
491
+ async def perp_fund(
492
+ self,
493
+ request: PerpFundRequest,
494
+ wait: bool = False,
495
+ timeout: int = 120,
496
+ ) -> TaskResponse | dict:
497
+ """POST /v1/perp/fund"""
498
+ return await self._perp_task(
499
+ "/v1/perp/fund", request.to_dict(), wait, timeout
500
+ )
501
+
502
+ async def perp_withdraw(
503
+ self,
504
+ request: PerpWithdrawRequest,
505
+ wait: bool = False,
506
+ timeout: int = 120,
507
+ ) -> TaskResponse | dict:
508
+ """POST /v1/perp/withdraw"""
509
+ return await self._perp_task(
510
+ "/v1/perp/withdraw", request.to_dict(), wait, timeout
511
+ )
512
+
513
+ async def _perp_task(
514
+ self,
515
+ path: str,
516
+ body: dict,
517
+ wait: bool,
518
+ timeout: int,
519
+ ) -> TaskResponse | dict:
520
+ params = {}
521
+ if wait:
522
+ params = {"wait": "true", "timeout": str(timeout)}
523
+ data = await self._authed_post(path, body, params=params)
524
+ if wait:
525
+ return data
526
+ return TaskResponse(
527
+ task_id=data["task_id"],
528
+ status=data.get("status", "queued"),
529
+ poll_url=data.get("poll_url"),
530
+ notification_stream=data.get("notification_stream"),
531
+ )
532
+
423
533
  # ----------------------------------------------------------
424
534
  # Tasks
425
535
  # ----------------------------------------------------------
@@ -5,6 +5,15 @@ from __future__ import annotations
5
5
  from dataclasses import dataclass
6
6
  from typing import Any
7
7
 
8
+ # Perp models live in their own module; re-exported here for back-compat.
9
+ from .perp_models import ( # noqa: E402,F401
10
+ PerpCancelRequest,
11
+ PerpCloseRequest,
12
+ PerpFundRequest,
13
+ PerpOpenRequest,
14
+ PerpWithdrawRequest,
15
+ )
16
+
8
17
  __all__ = [
9
18
  "TaskResponse",
10
19
  "TaskStatusResponse",
@@ -18,6 +27,11 @@ __all__ = [
18
27
  "HooksRequest",
19
28
  "DepositRequest",
20
29
  "WithdrawRequest",
30
+ "PerpOpenRequest",
31
+ "PerpCloseRequest",
32
+ "PerpCancelRequest",
33
+ "PerpFundRequest",
34
+ "PerpWithdrawRequest",
21
35
  "TokenToCheck",
22
36
  "TokenSecurityResult",
23
37
  "SecurityCheckSummary",
@@ -0,0 +1,115 @@
1
+ """Venue-neutral perpetual-futures request models.
2
+
3
+ Kept in a dedicated module (rather than appended to ``models.py``) per the
4
+ team convention of giving new model groups their own file. Re-exported from
5
+ ``models.py`` for backwards-compatible imports.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+
12
+ __all__ = [
13
+ "PerpOpenRequest",
14
+ "PerpCloseRequest",
15
+ "PerpCancelRequest",
16
+ "PerpFundRequest",
17
+ "PerpWithdrawRequest",
18
+ ]
19
+
20
+
21
+ @dataclass
22
+ class PerpOpenRequest:
23
+ """Open a venue-neutral perpetual position. size is in contracts."""
24
+
25
+ asset: str
26
+ is_buy: bool
27
+ size: float
28
+ leverage: int = 1
29
+ is_cross: bool = True
30
+ order_type: str = "market"
31
+ limit_price: float | None = None
32
+ slippage_pct: float = 0.5
33
+ reduce_only: bool = False
34
+ tp_price: float | None = None
35
+ sl_price: float | None = None
36
+ tp_is_market: bool = True
37
+ sl_is_market: bool = True
38
+
39
+ def to_dict(self) -> dict:
40
+ d: dict = {
41
+ "asset": self.asset,
42
+ "is_buy": self.is_buy,
43
+ "size": self.size,
44
+ "leverage": self.leverage,
45
+ "is_cross": self.is_cross,
46
+ "order_type": self.order_type,
47
+ "slippage_pct": self.slippage_pct,
48
+ "reduce_only": self.reduce_only,
49
+ "tp_is_market": self.tp_is_market,
50
+ "sl_is_market": self.sl_is_market,
51
+ }
52
+ if self.limit_price is not None:
53
+ d["limit_price"] = self.limit_price
54
+ if self.tp_price is not None:
55
+ d["tp_price"] = self.tp_price
56
+ if self.sl_price is not None:
57
+ d["sl_price"] = self.sl_price
58
+ return d
59
+
60
+
61
+ @dataclass
62
+ class PerpCloseRequest:
63
+ asset: str
64
+ size: float | None = None
65
+ slippage_pct: float = 0.5
66
+
67
+ def to_dict(self) -> dict:
68
+ d: dict = {"asset": self.asset, "slippage_pct": self.slippage_pct}
69
+ if self.size is not None:
70
+ d["size"] = self.size
71
+ return d
72
+
73
+
74
+ @dataclass
75
+ class PerpCancelRequest:
76
+ asset: str
77
+ order_id: int
78
+
79
+ def to_dict(self) -> dict:
80
+ return {"asset": self.asset, "order_id": self.order_id}
81
+
82
+
83
+ @dataclass
84
+ class PerpFundRequest:
85
+ amount: str
86
+ source_chain_id: int
87
+ source_token: str | None = None
88
+ display_amount_usd: float | None = None
89
+
90
+ def to_dict(self) -> dict:
91
+ d: dict = {
92
+ "amount": self.amount,
93
+ "source_chain_id": self.source_chain_id,
94
+ }
95
+ if self.source_token is not None:
96
+ d["source_token"] = self.source_token
97
+ if self.display_amount_usd is not None:
98
+ d["display_amount_usd"] = self.display_amount_usd
99
+ return d
100
+
101
+
102
+ @dataclass
103
+ class PerpWithdrawRequest:
104
+ amount_usd: float
105
+ destination_chain_id: int
106
+ destination_token: str | None = None
107
+
108
+ def to_dict(self) -> dict:
109
+ d: dict = {
110
+ "amount_usd": self.amount_usd,
111
+ "destination_chain_id": self.destination_chain_id,
112
+ }
113
+ if self.destination_token is not None:
114
+ d["destination_token"] = self.destination_token
115
+ return d
File without changes