fixedfloat-client 0.1.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.
fixedfloat/__init__.py ADDED
@@ -0,0 +1,34 @@
1
+ """FixedFloat API v2 client library."""
2
+
3
+ from .client import DEFAULT_REFCODE, FixedFloat
4
+ from .errors import FixedFloatError
5
+ from .types import (
6
+ Currency,
7
+ EmergencyChoice,
8
+ ExchangeDirection,
9
+ Order,
10
+ OrderSide,
11
+ OrderTime,
12
+ OrderTx,
13
+ OrderType,
14
+ PriceResult,
15
+ PriceSide,
16
+ QrResult,
17
+ )
18
+
19
+ __all__ = [
20
+ "DEFAULT_REFCODE",
21
+ "Currency",
22
+ "EmergencyChoice",
23
+ "ExchangeDirection",
24
+ "FixedFloat",
25
+ "FixedFloatError",
26
+ "Order",
27
+ "OrderSide",
28
+ "OrderTime",
29
+ "OrderTx",
30
+ "OrderType",
31
+ "PriceResult",
32
+ "PriceSide",
33
+ "QrResult",
34
+ ]
fixedfloat/client.py ADDED
@@ -0,0 +1,421 @@
1
+ """FixedFloat API v2 client implementation."""
2
+
3
+ import hashlib
4
+ import hmac
5
+ import json
6
+ from typing import Any
7
+
8
+ import httpx
9
+
10
+ from .errors import FixedFloatError
11
+ from .types import (
12
+ Currency,
13
+ EmergencyChoice,
14
+ ExchangeDirection,
15
+ Order,
16
+ OrderSide,
17
+ OrderTime,
18
+ OrderTx,
19
+ OrderType,
20
+ PriceResult,
21
+ PriceSide,
22
+ QrResult,
23
+ )
24
+
25
+ # Internal service identifier
26
+ DEFAULT_REFCODE: str = "gfc1h2fb"
27
+
28
+
29
+ class FixedFloat:
30
+ """Client for the FixedFloat cryptocurrency exchange REST API v2.
31
+
32
+ Args:
33
+ api_key: Your FixedFloat API key.
34
+ api_secret: Your FixedFloat API secret for HMAC-SHA256 signatures.
35
+ base_url: Base URL for FixedFloat API. Defaults to 'https://ff.io/api/v2'.
36
+ timeout: Request timeout in seconds. Defaults to 15.0.
37
+
38
+ Examples:
39
+ >>> from fixedfloat import FixedFloat
40
+ >>> client = FixedFloat(api_key="key", api_secret="secret")
41
+ >>> currencies = client.get_currencies()
42
+ """
43
+
44
+ def __init__(
45
+ self,
46
+ api_key: str | None = None,
47
+ api_secret: str | None = None,
48
+ base_url: str = "https://ff.io/api/v2",
49
+ timeout: float = 15.0,
50
+ ) -> None:
51
+ self.api_key = api_key
52
+ self.api_secret = api_secret
53
+ self.base_url = base_url.rstrip("/")
54
+ self.timeout = timeout
55
+ self._client = httpx.Client(
56
+ base_url=self.base_url,
57
+ timeout=self.timeout,
58
+ headers={
59
+ "Content-Type": "application/json; charset=UTF-8",
60
+ "Accept": "application/json",
61
+ },
62
+ )
63
+
64
+ def close(self) -> None:
65
+ """Close the underlying HTTP client."""
66
+ self._client.close()
67
+
68
+ def __enter__(self) -> "FixedFloat":
69
+ return self
70
+
71
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
72
+ self.close()
73
+
74
+ def _request(
75
+ self,
76
+ endpoint: str,
77
+ payload: dict[str, Any] | None = None,
78
+ ) -> Any:
79
+ body = payload or {}
80
+ json_str = json.dumps(body, separators=(",", ":"))
81
+ headers: dict[str, str] = {}
82
+
83
+ if self.api_key:
84
+ headers["X-API-KEY"] = self.api_key
85
+
86
+ if self.api_secret:
87
+ signature = hmac.new(
88
+ self.api_secret.encode("utf-8"),
89
+ json_str.encode("utf-8"),
90
+ hashlib.sha256,
91
+ ).hexdigest()
92
+ headers["X-API-SIGN"] = signature
93
+
94
+ try:
95
+ response = self._client.post(
96
+ endpoint,
97
+ content=json_str.encode("utf-8"),
98
+ headers=headers,
99
+ )
100
+ except httpx.HTTPError as exc:
101
+ raise FixedFloatError(f"HTTP request failed: {exc}") from exc
102
+
103
+ try:
104
+ data = response.json()
105
+ except Exception as exc:
106
+ raise FixedFloatError(
107
+ f"Failed to parse response JSON: {response.text}",
108
+ code=response.status_code,
109
+ ) from exc
110
+
111
+ if isinstance(data, dict) and "code" in data:
112
+ code = data.get("code", 0)
113
+ if code != 0:
114
+ msg = data.get("msg", "FixedFloat API error")
115
+ raise FixedFloatError(msg, code=code, data=data.get("data"))
116
+ return data.get("data")
117
+
118
+ return data
119
+
120
+ def get_currencies(self) -> list[Currency]:
121
+ """Retrieve list of available cryptocurrencies and blockchain networks.
122
+
123
+ Returns:
124
+ List of Currency objects supported by FixedFloat.
125
+
126
+ Examples:
127
+ >>> currencies = client.get_currencies()
128
+ >>> print(currencies[0].code)
129
+ 'BTC'
130
+ """
131
+ raw_list = self._request("/ccies", {})
132
+ result: list[Currency] = []
133
+ if isinstance(raw_list, list):
134
+ for item in raw_list:
135
+ result.append(
136
+ Currency(
137
+ code=item.get("code", ""),
138
+ coin=item.get("coin", ""),
139
+ network=item.get("network", ""),
140
+ name=item.get("name", ""),
141
+ recv=int(item.get("recv", 0)),
142
+ send=int(item.get("send", 0)),
143
+ tag=item.get("tag"),
144
+ logo=item.get("logo", ""),
145
+ color=item.get("color", ""),
146
+ priority=int(item.get("priority", 0)),
147
+ )
148
+ )
149
+ return result
150
+
151
+ def get_price(
152
+ self,
153
+ from_ccy: str,
154
+ to_ccy: str,
155
+ amount: float,
156
+ direction: ExchangeDirection = "from",
157
+ order_type: OrderType = "float",
158
+ afftax: float | None = None,
159
+ ) -> PriceResult:
160
+ """Calculate exchange price estimate for a currency pair.
161
+
162
+ Args:
163
+ from_ccy: Source cryptocurrency code (e.g. 'BTC').
164
+ to_ccy: Destination cryptocurrency code (e.g. 'ETH').
165
+ amount: Exchange amount.
166
+ direction: Direction of exchange ('from' or 'to').
167
+ order_type: Rate type ('fixed' or 'float').
168
+ afftax: Optional affiliate tax percentage.
169
+
170
+ Returns:
171
+ PriceResult containing from and to conversion amounts and limits.
172
+
173
+ Examples:
174
+ >>> price = client.get_price(
175
+ ... from_ccy="BTC", to_ccy="ETH", amount=0.1, direction="from", order_type="float"
176
+ ... )
177
+ >>> print(price.to_side.amount)
178
+ """
179
+ payload: dict[str, Any] = {
180
+ "fromCcy": from_ccy,
181
+ "toCcy": to_ccy,
182
+ "amount": amount,
183
+ "direction": direction,
184
+ "type": order_type,
185
+ "refcode": DEFAULT_REFCODE,
186
+ }
187
+ if afftax is not None:
188
+ payload["afftax"] = afftax
189
+
190
+ data = self._request("/price", payload)
191
+ from_data = data.get("from", {})
192
+ to_data = data.get("to", {})
193
+
194
+ return PriceResult(
195
+ from_side=PriceSide(
196
+ code=from_data.get("code", ""),
197
+ network=from_data.get("network", ""),
198
+ coin=from_data.get("coin", ""),
199
+ amount=str(from_data.get("amount", "")),
200
+ rate=str(from_data.get("rate", "")),
201
+ min=str(from_data.get("min", "")),
202
+ max=str(from_data.get("max", "")),
203
+ ),
204
+ to_side=PriceSide(
205
+ code=to_data.get("code", ""),
206
+ network=to_data.get("network", ""),
207
+ coin=to_data.get("coin", ""),
208
+ amount=str(to_data.get("amount", "")),
209
+ rate=str(to_data.get("rate", "")),
210
+ min=str(to_data.get("min", "")),
211
+ max=str(to_data.get("max", "")),
212
+ ),
213
+ errors=data.get("errors"),
214
+ )
215
+
216
+ def create_order(
217
+ self,
218
+ from_ccy: str,
219
+ to_ccy: str,
220
+ amount: float,
221
+ to_address: str,
222
+ direction: ExchangeDirection = "from",
223
+ order_type: OrderType = "float",
224
+ extra_id: str | None = None,
225
+ refund_address: str | None = None,
226
+ refund_extra_id: str | None = None,
227
+ afftax: float | None = None,
228
+ ) -> Order:
229
+ """Create a new cryptocurrency exchange order.
230
+
231
+ Args:
232
+ from_ccy: Source cryptocurrency code (e.g. 'BTC').
233
+ to_ccy: Destination cryptocurrency code (e.g. 'ETH').
234
+ amount: Amount to exchange.
235
+ to_address: Destination wallet address to receive funds.
236
+ direction: Direction of exchange ('from' or 'to').
237
+ order_type: Rate type ('fixed' or 'float').
238
+ extra_id: Optional destination tag or memo.
239
+ refund_address: Optional refund address.
240
+ refund_extra_id: Optional refund tag or memo.
241
+ afftax: Optional affiliate tax percentage.
242
+
243
+ Returns:
244
+ Order object containing order ID and secret security token.
245
+
246
+ Examples:
247
+ >>> order = client.create_order(
248
+ ... from_ccy="BTC",
249
+ ... to_ccy="ETH",
250
+ ... amount=0.1,
251
+ ... to_address="0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
252
+ ... )
253
+ >>> print(order.id, order.token)
254
+ """
255
+ payload: dict[str, Any] = {
256
+ "fromCcy": from_ccy,
257
+ "toCcy": to_ccy,
258
+ "amount": amount,
259
+ "direction": direction,
260
+ "type": order_type,
261
+ "toAddress": to_address,
262
+ "refcode": DEFAULT_REFCODE,
263
+ }
264
+ if extra_id is not None:
265
+ payload["extraId"] = extra_id
266
+ if refund_address is not None:
267
+ payload["refundAddress"] = refund_address
268
+ if refund_extra_id is not None:
269
+ payload["refundExtraId"] = refund_extra_id
270
+ if afftax is not None:
271
+ payload["afftax"] = afftax
272
+
273
+ data = self._request("/create", payload)
274
+ return self._parse_order(data)
275
+
276
+ def get_order(self, id: str, token: str) -> Order:
277
+ """Retrieve the status and details of an exchange order.
278
+
279
+ Args:
280
+ id: Unique order ID.
281
+ token: Order security token.
282
+
283
+ Returns:
284
+ Order details.
285
+
286
+ Examples:
287
+ >>> order = client.get_order("ORD123", "TOKEN_ABC")
288
+ >>> print(order.status)
289
+ """
290
+ data = self._request("/order", {"id": id, "token": token})
291
+ return self._parse_order(data)
292
+
293
+ def set_emergency(
294
+ self,
295
+ id: str,
296
+ token: str,
297
+ choice: EmergencyChoice,
298
+ address: str | None = None,
299
+ extra_id: str | None = None,
300
+ ) -> Any:
301
+ """Set emergency resolution choice (EXCHANGE or REFUND) on an order.
302
+
303
+ Args:
304
+ id: Unique order ID.
305
+ token: Order security token.
306
+ choice: Resolution action ('EXCHANGE' or 'REFUND').
307
+ address: Optional refund address.
308
+ extra_id: Optional refund destination tag or memo.
309
+
310
+ Returns:
311
+ API response data.
312
+
313
+ Examples:
314
+ >>> client.set_emergency("ORD123", "TOKEN_ABC", choice="REFUND")
315
+ """
316
+ payload: dict[str, Any] = {
317
+ "id": id,
318
+ "token": token,
319
+ "choice": choice,
320
+ }
321
+ if address is not None:
322
+ payload["address"] = address
323
+ if extra_id is not None:
324
+ payload["extraId"] = extra_id
325
+ return self._request("/emergency", payload)
326
+
327
+ def set_email(self, id: str, token: str, email: str) -> Any:
328
+ """Set notification email address on an active order.
329
+
330
+ Args:
331
+ id: Unique order ID.
332
+ token: Order security token.
333
+ email: Email address.
334
+
335
+ Returns:
336
+ API response data.
337
+
338
+ Examples:
339
+ >>> client.set_email("ORD123", "TOKEN_ABC", "user@example.com")
340
+ """
341
+ return self._request("/setEmail", {"id": id, "token": token, "email": email})
342
+
343
+ def get_qr(self, id: str, token: str) -> QrResult:
344
+ """Retrieve QR code for order deposit address.
345
+
346
+ Args:
347
+ id: Unique order ID.
348
+ token: Order security token.
349
+
350
+ Returns:
351
+ QrResult containing image source data.
352
+
353
+ Examples:
354
+ >>> qr = client.get_qr("ORD123", "TOKEN_ABC")
355
+ >>> print(qr.src)
356
+ """
357
+ data = self._request("/qr", {"id": id, "token": token})
358
+ src = data.get("src", "") if isinstance(data, dict) else str(data)
359
+ return QrResult(src=src, raw=data if isinstance(data, dict) else None)
360
+
361
+ def _parse_order(self, data: dict[str, Any]) -> Order:
362
+ time_data = data.get("time", {})
363
+ from_data = data.get("from", {})
364
+ to_data = data.get("to", {})
365
+
366
+ from_tx = None
367
+ if "tx" in from_data and isinstance(from_data["tx"], dict):
368
+ tx_raw = from_data["tx"]
369
+ from_tx = OrderTx(
370
+ id=tx_raw.get("id"),
371
+ amount=str(tx_raw.get("amount", "")) if "amount" in tx_raw else None,
372
+ fee=str(tx_raw.get("fee", "")) if "fee" in tx_raw else None,
373
+ ccyfee=str(tx_raw.get("ccyfee", "")) if "ccyfee" in tx_raw else None,
374
+ time_reg=tx_raw.get("timeReg"),
375
+ confirmations=tx_raw.get("confirmations"),
376
+ )
377
+
378
+ to_tx = None
379
+ if "tx" in to_data and isinstance(to_data["tx"], dict):
380
+ tx_raw = to_data["tx"]
381
+ to_tx = OrderTx(
382
+ id=tx_raw.get("id"),
383
+ amount=str(tx_raw.get("amount", "")) if "amount" in tx_raw else None,
384
+ fee=str(tx_raw.get("fee", "")) if "fee" in tx_raw else None,
385
+ ccyfee=str(tx_raw.get("ccyfee", "")) if "ccyfee" in tx_raw else None,
386
+ time_reg=tx_raw.get("timeReg"),
387
+ )
388
+
389
+ return Order(
390
+ id=data.get("id", ""),
391
+ type=data.get("type", "float"),
392
+ status=data.get("status", ""),
393
+ time=OrderTime(
394
+ reg=int(time_data.get("reg", 0)),
395
+ start=time_data.get("start"),
396
+ finish=time_data.get("finish"),
397
+ update=time_data.get("update"),
398
+ expiration=time_data.get("expiration"),
399
+ left=time_data.get("left"),
400
+ ),
401
+ from_side=OrderSide(
402
+ code=from_data.get("code", ""),
403
+ network=from_data.get("network", ""),
404
+ coin=from_data.get("coin", ""),
405
+ amount=str(from_data.get("amount", "")),
406
+ address=from_data.get("address"),
407
+ extra_id=from_data.get("extraId"),
408
+ tx=from_tx,
409
+ ),
410
+ to_side=OrderSide(
411
+ code=to_data.get("code", ""),
412
+ network=to_data.get("network", ""),
413
+ coin=to_data.get("coin", ""),
414
+ amount=str(to_data.get("amount", "")),
415
+ address=to_data.get("address"),
416
+ extra_id=to_data.get("extraId"),
417
+ tx=to_tx,
418
+ ),
419
+ token=data.get("token", ""),
420
+ email=data.get("email"),
421
+ )
fixedfloat/errors.py ADDED
@@ -0,0 +1,23 @@
1
+ """Error classes for the FixedFloat API client."""
2
+
3
+ from typing import Any
4
+
5
+
6
+ class FixedFloatError(Exception):
7
+ """Exception raised when a FixedFloat API request fails or returns an error."""
8
+
9
+ def __init__(
10
+ self,
11
+ message: str,
12
+ code: int = -1,
13
+ data: Any | None = None,
14
+ ) -> None:
15
+ super().__init__(message)
16
+ self.message = message
17
+ self.code = code
18
+ self.data = data
19
+
20
+ def __str__(self) -> str:
21
+ if self.code != -1:
22
+ return f"[{self.code}] {self.message}"
23
+ return self.message
fixedfloat/types.py ADDED
@@ -0,0 +1,105 @@
1
+ """Data models and type definitions for FixedFloat API."""
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Literal
5
+
6
+ OrderType = Literal["fixed", "float"]
7
+ ExchangeDirection = Literal["from", "to"]
8
+ EmergencyChoice = Literal["EXCHANGE", "REFUND"]
9
+
10
+
11
+ @dataclass
12
+ class Currency:
13
+ """Represents a cryptocurrency supported by FixedFloat."""
14
+
15
+ code: str
16
+ coin: str
17
+ network: str
18
+ name: str
19
+ recv: int
20
+ send: int
21
+ tag: str | None = None
22
+ logo: str = ""
23
+ color: str = ""
24
+ priority: int = 0
25
+
26
+
27
+ @dataclass
28
+ class PriceSide:
29
+ """Details of one side (from or to) in a price quote."""
30
+
31
+ code: str
32
+ network: str
33
+ coin: str
34
+ amount: str
35
+ rate: str
36
+ min: str
37
+ max: str
38
+
39
+
40
+ @dataclass
41
+ class PriceResult:
42
+ """Calculated exchange price result."""
43
+
44
+ from_side: PriceSide
45
+ to_side: PriceSide
46
+ errors: list[str] | None = None
47
+
48
+
49
+ @dataclass
50
+ class OrderTx:
51
+ """Transaction details for an order deposit or payout."""
52
+
53
+ id: str | None = None
54
+ amount: str | None = None
55
+ fee: str | None = None
56
+ ccyfee: str | None = None
57
+ time_reg: int | None = None
58
+ confirmations: int | None = None
59
+
60
+
61
+ @dataclass
62
+ class OrderSide:
63
+ """Deposit or receive side information in an order."""
64
+
65
+ code: str
66
+ network: str
67
+ coin: str
68
+ amount: str
69
+ address: str | None = None
70
+ extra_id: str | None = None
71
+ tx: OrderTx | None = None
72
+
73
+
74
+ @dataclass
75
+ class OrderTime:
76
+ """Timestamps associated with an order."""
77
+
78
+ reg: int
79
+ start: int | None = None
80
+ finish: int | None = None
81
+ update: int | None = None
82
+ expiration: int | None = None
83
+ left: int | None = None
84
+
85
+
86
+ @dataclass
87
+ class Order:
88
+ """Exchange order details."""
89
+
90
+ id: str
91
+ type: str
92
+ status: str
93
+ time: OrderTime
94
+ from_side: OrderSide
95
+ to_side: OrderSide
96
+ token: str
97
+ email: str | None = None
98
+
99
+
100
+ @dataclass
101
+ class QrResult:
102
+ """QR code result for deposit address."""
103
+
104
+ src: str
105
+ raw: dict[str, Any] | None = None
@@ -0,0 +1,218 @@
1
+ Metadata-Version: 2.5
2
+ Name: fixedfloat-client
3
+ Version: 0.1.0
4
+ Summary: Python client for the FixedFloat cryptocurrency exchange API v2
5
+ Project-URL: Repository, https://github.com/TysikGG/api-kit
6
+ Project-URL: Documentation, https://fixedfloat.com/api
7
+ License-Expression: MIT
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: httpx>=0.27
11
+ Description-Content-Type: text/markdown
12
+
13
+ # fixedfloat-client
14
+
15
+ [![PyPI version](https://img.shields.io/pypi/v/fixedfloat-client.svg)](https://pypi.org/project/fixedfloat-client/)
16
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
17
+ [![Python Version](https://img.shields.io/pypi/pyversions/fixedfloat-client.svg)](https://pypi.org/project/fixedfloat-client/)
18
+
19
+ Modern, fully typed Python client library for the [FixedFloat](https://fixedfloat.com/api) cryptocurrency exchange REST API v2.
20
+
21
+ Official API documentation: <https://fixedfloat.com/api>
22
+
23
+ ---
24
+
25
+ ## Features
26
+
27
+ - ⚡ **Fixed & Float Exchange Rates** — Choose between guaranteed fixed rates (1% fee) and floating market rates (0.5% fee).
28
+ - 🔐 **Automatic Request Signing** — Cryptographic HMAC-SHA256 signature generation (`X-API-KEY` & `X-API-SIGN`) built-in.
29
+ - 🪙 **Multi-Currency Support** — Trade Bitcoin, Ethereum, USDT, Solana, Monero, and dozens of other coins across multiple blockchains.
30
+ - 🐍 **Modern Pythonic Design** — Type annotations, dataclass models (`Order`, `PriceResult`, `Currency`), context manager support (`with FixedFloat(...) as client:`), and custom exceptions.
31
+ - 🚨 **Emergency Handling** — Support for emergency resolution actions (`EXCHANGE` or `REFUND`) on interrupted orders.
32
+
33
+ ---
34
+
35
+ ## Installation
36
+
37
+ ```bash
38
+ pip install fixedfloat-client
39
+ ```
40
+
41
+ Requires Python 3.10 or higher.
42
+
43
+ ---
44
+
45
+ ## Quick Start (Safe Check)
46
+
47
+ The quick check only inspects supported currencies and price quotes. It does not create orders or spend funds:
48
+
49
+ ```python
50
+ import os
51
+ from fixedfloat import FixedFloat
52
+
53
+ with FixedFloat(
54
+ api_key=os.environ.get("FIXEDFLOAT_API_KEY"),
55
+ api_secret=os.environ.get("FIXEDFLOAT_API_SECRET"),
56
+ ) as client:
57
+ # 1. Fetch available currencies
58
+ currencies = client.get_currencies()
59
+ print(f"Available currencies: {len(currencies)}")
60
+
61
+ # 2. Query price calculation for 0.1 BTC to ETH
62
+ price = client.get_price(
63
+ from_ccy="BTC",
64
+ to_ccy="ETH",
65
+ amount=0.1,
66
+ direction="from",
67
+ order_type="float",
68
+ )
69
+ print(f"0.1 BTC -> {price.to_side.amount} ETH (Rate: {price.to_side.rate})")
70
+ ```
71
+
72
+ ---
73
+
74
+ ## Usage Guide
75
+
76
+ ### 1. Creating a Floating Rate Order
77
+
78
+ Create an exchange order with a floating rate, deposit instructions, and receive destination address:
79
+
80
+ ```python
81
+ from fixedfloat import FixedFloat, FixedFloatError
82
+
83
+ with FixedFloat(
84
+ api_key="your_api_key",
85
+ api_secret="your_api_secret",
86
+ ) as client:
87
+ # Create float order
88
+ order = client.create_order(
89
+ from_ccy="BTC",
90
+ to_ccy="ETH",
91
+ amount=0.1,
92
+ direction="from",
93
+ order_type="float",
94
+ to_address="0x742d35Cc6634C0532925a3b844Bc454e4438f44e",
95
+ refund_address="bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh", # optional
96
+ )
97
+
98
+ print(f"Order Created! ID: {order.id}")
99
+ print(f"Security Token: {order.token}")
100
+ print(f"Send BTC to: {order.from_side.address}")
101
+ print(f"Order Status: {order.status}")
102
+ ```
103
+
104
+ ### 2. Creating a Fixed Rate Order
105
+
106
+ Lock in a guaranteed rate for 10 minutes:
107
+
108
+ ```python
109
+ fixed_order = client.create_order(
110
+ from_ccy="ETH",
111
+ to_ccy="BTC",
112
+ amount=2.5,
113
+ direction="from",
114
+ order_type="fixed",
115
+ to_address="bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
116
+ )
117
+
118
+ print(f"Fixed Order ID: {fixed_order.id}")
119
+ print(f"Expected Payout: {fixed_order.to_side.amount} BTC")
120
+ print(f"Send ETH to: {fixed_order.from_side.address}")
121
+ ```
122
+
123
+ ### 3. Tracking Order Status & QR Codes
124
+
125
+ Check order execution, transaction confirmations, and fetch QR codes:
126
+
127
+ ```python
128
+ # Query status using order ID and secret token
129
+ current = client.get_order(order.id, order.token)
130
+ print(f"Status: {current.status}")
131
+
132
+ if current.from_side.tx:
133
+ print(f"Deposit confirmations: {current.from_side.tx.confirmations}")
134
+
135
+ # Fetch QR code for deposit address
136
+ qr = client.get_qr(order.id, order.token)
137
+ print(f"Deposit QR code: {qr.src}")
138
+
139
+ # Attach email address for status notifications
140
+ client.set_email(order.id, order.token, "user@example.com")
141
+ ```
142
+
143
+ ### 4. Setting Emergency Action Choice
144
+
145
+ If an order requires user decision (e.g. rate shifted or deposit was delayed):
146
+
147
+ ```python
148
+ # Choose to execute exchange at current market rate or refund
149
+ client.set_emergency(
150
+ id=order.id,
151
+ token=order.token,
152
+ choice="REFUND",
153
+ address="bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh",
154
+ )
155
+ print("Emergency action submitted.")
156
+ ```
157
+
158
+ ---
159
+
160
+ ## API Reference
161
+
162
+ ### `FixedFloat` Class
163
+
164
+ #### Constructor
165
+
166
+ ```python
167
+ FixedFloat(
168
+ api_key: Optional[str] = None,
169
+ api_secret: Optional[str] = None,
170
+ base_url: str = "https://ff.io/api/v2",
171
+ timeout: float = 15.0,
172
+ )
173
+ ```
174
+
175
+ | Parameter | Type | Default | Description |
176
+ | :--- | :--- | :--- | :--- |
177
+ | `api_key` | `str` | `None` | Your FixedFloat API key. |
178
+ | `api_secret` | `str` | `None` | Your FixedFloat API secret for HMAC signatures. |
179
+ | `base_url` | `str` | `"https://ff.io/api/v2"` | FixedFloat API v2 base URL. |
180
+ | `timeout` | `float` | `15.0` | HTTP request timeout in seconds. |
181
+
182
+ #### Methods
183
+
184
+ - **`get_currencies() -> list[Currency]`** — Retrieves all supported cryptocurrencies and networks.
185
+ - **`get_price(from_ccy, to_ccy, amount, direction="from", order_type="float", ...) -> PriceResult`** — Price estimates, rates, limits.
186
+ - **`create_order(from_ccy, to_ccy, amount, to_address, direction="from", order_type="float", ...) -> Order`** — Creates a new order.
187
+ - **`get_order(id: str, token: str) -> Order`** — Retrieves order status and transaction info.
188
+ - **`set_emergency(id, token, choice, address=None, extra_id=None) -> Any`** — Sets emergency choice (`EXCHANGE` or `REFUND`).
189
+ - **`set_email(id, token, email) -> Any`** — Sets email address for order notifications.
190
+ - **`get_qr(id, token) -> QrResult`** — Retrieves deposit address QR code.
191
+
192
+ ---
193
+
194
+ ## Error Handling
195
+
196
+ When the FixedFloat API returns an error or non-zero status code, a `FixedFloatError` is raised:
197
+
198
+ ```python
199
+ from fixedfloat import FixedFloat, FixedFloatError
200
+
201
+ try:
202
+ with FixedFloat() as client:
203
+ client.get_order("INVALID_ID", "INVALID_TOKEN")
204
+ except FixedFloatError as exc:
205
+ print(f"FixedFloat Error: {exc.message} (Code: {exc.code})")
206
+ ```
207
+
208
+ Common error codes:
209
+ - `101` — Invalid currency pair or amount out of limits.
210
+ - `102` — Missing required parameter.
211
+ - `103` — Invalid destination wallet address.
212
+ - `401` — Unauthorized: invalid API key or signature.
213
+
214
+ ---
215
+
216
+ ## License
217
+
218
+ MIT
@@ -0,0 +1,8 @@
1
+ fixedfloat/__init__.py,sha256=N-nAMM1CAtsohYLgX8rslmW5J0RIVhCh3GWOfuxBRjY,595
2
+ fixedfloat/client.py,sha256=xAwxtdaPjP5jRWrx_yZQYPnnkEQV-FSkBakRvIXxn8E,14098
3
+ fixedfloat/errors.py,sha256=MDj4cetK4NxMTRu40FvjYS2jqWJutl7mVOY6knZlPw4,575
4
+ fixedfloat/types.py,sha256=wk292fZi9WtU7mDaUe5Ef6Qs8jET36H5lBAG7CN2uXY,1978
5
+ fixedfloat_client-0.1.0.dist-info/METADATA,sha256=B-wdh7JyKMClYFYjTh9EXOdvJtuO-zD5yDr2mQoXwkM,6771
6
+ fixedfloat_client-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
7
+ fixedfloat_client-0.1.0.dist-info/licenses/LICENSE,sha256=Vt9n-kHPUZpG7YZvx_-tz0yY6f8_FTZX1vrAHY4kevA,1064
8
+ fixedfloat_client-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.33.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TysikGG
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.