exolix-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.
exolix/__init__.py ADDED
@@ -0,0 +1,28 @@
1
+ """Exolix cryptocurrency exchange API (v2) client."""
2
+
3
+ from .client import EXOLIX_API_KEY_HEADER, Exolix
4
+ from .errors import ExolixError
5
+ from .types import (
6
+ Currency,
7
+ Network,
8
+ PaginatedCurrencies,
9
+ PaginatedNetworks,
10
+ PaginatedTransactions,
11
+ Rate,
12
+ RateType,
13
+ Transaction,
14
+ )
15
+
16
+ __all__ = [
17
+ "EXOLIX_API_KEY_HEADER",
18
+ "Currency",
19
+ "Exolix",
20
+ "ExolixError",
21
+ "Network",
22
+ "PaginatedCurrencies",
23
+ "PaginatedNetworks",
24
+ "PaginatedTransactions",
25
+ "Rate",
26
+ "RateType",
27
+ "Transaction",
28
+ ]
exolix/client.py ADDED
@@ -0,0 +1,377 @@
1
+ from typing import Any
2
+ from urllib.parse import quote
3
+
4
+ import httpx
5
+
6
+ from .errors import ExolixError
7
+ from .types import (
8
+ Network,
9
+ PaginatedCurrencies,
10
+ PaginatedNetworks,
11
+ PaginatedTransactions,
12
+ Rate,
13
+ RateType,
14
+ SortOrder,
15
+ Transaction,
16
+ )
17
+
18
+ # Header that carries the affiliate API key.
19
+ #
20
+ # Partner attribution is the API key itself. The default affiliate share is
21
+ # 0.3%. There is no referral parameter.
22
+ EXOLIX_API_KEY_HEADER = "Authorization"
23
+
24
+ DEFAULT_BASE_URL = "https://exolix.com/api/v2"
25
+
26
+
27
+ def _defined(values: dict[str, Any]) -> dict[str, Any]:
28
+ params: dict[str, Any] = {}
29
+ for key, value in values.items():
30
+ if value is None or value == "":
31
+ continue
32
+ if isinstance(value, bool):
33
+ params[key] = str(value).lower()
34
+ elif isinstance(value, list):
35
+ if not value:
36
+ continue
37
+ params[key] = ",".join(str(item) for item in value)
38
+ else:
39
+ params[key] = value
40
+ return params
41
+
42
+
43
+ def _amount(value: float | str | None) -> str | None:
44
+ if value is None:
45
+ return None
46
+ return str(value)
47
+
48
+
49
+ def _error_parts(data: Any, fallback: str) -> tuple[str, str | None]:
50
+ if isinstance(data, str) and data.strip():
51
+ return data.strip(), None
52
+ if isinstance(data, dict):
53
+ code_source = data.get("error")
54
+ code = None if code_source is None else str(code_source)
55
+ message_source = data.get("message", data.get("error"))
56
+ if isinstance(message_source, list):
57
+ message_source = ", ".join(str(item) for item in message_source)
58
+ message = fallback if message_source in (None, "") else str(message_source)
59
+ return message, code
60
+ return fallback, None
61
+
62
+
63
+ class Exolix:
64
+ """Client for the Exolix cryptocurrency exchange API (v2).
65
+
66
+ Attributes:
67
+ api_key: Affiliate API key. Sent only in the Authorization header.
68
+ base_url: Base URL of the API.
69
+ """
70
+
71
+ def __init__(
72
+ self,
73
+ api_key: str | None = None,
74
+ base_url: str | None = None,
75
+ timeout: float = 30.0,
76
+ user_agent: str | None = None,
77
+ ) -> None:
78
+ """Initializes the Exolix client.
79
+
80
+ Args:
81
+ api_key: Affiliate API key. Not read from the environment.
82
+ Attribution is this key (default affiliate share 0.3%).
83
+ base_url: API base URL. Defaults to https://exolix.com/api/v2.
84
+ timeout: Timeout in seconds for HTTP requests. Defaults to 30.0.
85
+ user_agent: Custom User-Agent header string.
86
+
87
+ Examples:
88
+ >>> from exolix import Exolix
89
+ >>> client = Exolix(api_key="your-api-key")
90
+ >>> currencies = client.get_currencies(page=1, size=10)
91
+ >>> print(currencies["count"])
92
+ """
93
+ self.api_key = api_key
94
+ self.base_url = (base_url or DEFAULT_BASE_URL).rstrip("/")
95
+ ua = user_agent or "exolix-client/0.1.0"
96
+
97
+ headers: dict[str, str] = {
98
+ "User-Agent": ua,
99
+ "Accept": "application/json",
100
+ "Content-Type": "application/json",
101
+ }
102
+ if self.api_key:
103
+ headers[EXOLIX_API_KEY_HEADER] = self.api_key
104
+
105
+ self._client = httpx.Client(
106
+ base_url=self.base_url,
107
+ timeout=timeout,
108
+ headers=headers,
109
+ )
110
+
111
+ def close(self) -> None:
112
+ """Closes the underlying HTTP client session."""
113
+ self._client.close()
114
+
115
+ def __enter__(self) -> "Exolix":
116
+ return self
117
+
118
+ def __exit__(self, exc_type: Any, exc_val: Any, exc_tb: Any) -> None:
119
+ self.close()
120
+
121
+ def _request(
122
+ self,
123
+ method: str,
124
+ path: str,
125
+ params: dict[str, Any] | None = None,
126
+ json_data: dict[str, Any] | None = None,
127
+ ) -> Any:
128
+ try:
129
+ response = self._client.request(method, path, params=params, json=json_data)
130
+ response.raise_for_status()
131
+ return response.json()
132
+ except httpx.HTTPStatusError as exc:
133
+ try:
134
+ data_err = exc.response.json()
135
+ except ValueError:
136
+ data_err = exc.response.text or None
137
+ message, code = _error_parts(data_err, "Exolix API error")
138
+ raise ExolixError(
139
+ message=message,
140
+ status_code=exc.response.status_code,
141
+ code=code,
142
+ response_body=data_err,
143
+ ) from exc
144
+ except httpx.RequestError as exc:
145
+ raise ExolixError(f"HTTP request failed: {exc}") from exc
146
+
147
+ def get_currencies(
148
+ self,
149
+ page: int | None = None,
150
+ size: int | None = None,
151
+ search: str | None = None,
152
+ with_networks: bool | None = None,
153
+ ) -> PaginatedCurrencies:
154
+ """Lists currencies. Set with_networks to include each coin's networks.
155
+
156
+ Args:
157
+ page: Page number.
158
+ size: Page size.
159
+ search: Currency code or name filter.
160
+ with_networks: Include networks on each currency.
161
+
162
+ Returns:
163
+ Page of currencies and the total count.
164
+
165
+ Examples:
166
+ >>> page = client.get_currencies(search="BTC", with_networks=True)
167
+ """
168
+ params = _defined(
169
+ {"page": page, "size": size, "search": search, "withNetworks": with_networks}
170
+ )
171
+ return self._request("GET", "/currencies", params=params or None)
172
+
173
+ def get_currency_networks(self, code: str) -> list[Network]:
174
+ """Lists networks for one currency code.
175
+
176
+ Args:
177
+ code: Currency code, for example ``ETH``.
178
+
179
+ Returns:
180
+ Networks for that currency.
181
+
182
+ Examples:
183
+ >>> networks = client.get_currency_networks("ETH")
184
+ """
185
+ return self._request("GET", f"/currencies/{quote(code, safe='')}/networks")
186
+
187
+ def get_networks(
188
+ self,
189
+ page: int | None = None,
190
+ size: int | None = None,
191
+ search: str | None = None,
192
+ ) -> PaginatedNetworks:
193
+ """Lists every network in the catalog.
194
+
195
+ Args:
196
+ page: Page number.
197
+ size: Page size.
198
+ search: Network name filter.
199
+
200
+ Returns:
201
+ Page of networks and the total count.
202
+
203
+ Examples:
204
+ >>> networks = client.get_networks(page=1, size=50)
205
+ """
206
+ params = _defined({"page": page, "size": size, "search": search})
207
+ return self._request("GET", "/currencies/networks", params=params or None)
208
+
209
+ def get_rate(
210
+ self,
211
+ coin_from: str,
212
+ coin_to: str,
213
+ rate_type: RateType,
214
+ *,
215
+ network_from: str | None = None,
216
+ network_to: str | None = None,
217
+ amount: float | str | None = None,
218
+ withdrawal_amount: float | str | None = None,
219
+ ) -> Rate:
220
+ """Quotes a fixed or floating rate.
221
+
222
+ Pass amount (what you send) or withdrawal_amount (what you want to
223
+ receive). Partner attribution is the API key, not a query field.
224
+
225
+ Args:
226
+ coin_from: Deposit currency code.
227
+ coin_to: Payout currency code.
228
+ rate_type: ``float`` or ``fixed``.
229
+ network_from: Deposit network.
230
+ network_to: Payout network.
231
+ amount: Deposit amount.
232
+ withdrawal_amount: Desired payout amount.
233
+
234
+ Returns:
235
+ Quote with amounts, rate, and limits.
236
+
237
+ Examples:
238
+ >>> quote = client.get_rate("BTC", "ETH", "float", amount="0.1")
239
+ """
240
+ params = _defined(
241
+ {
242
+ "coinFrom": coin_from,
243
+ "networkFrom": network_from,
244
+ "coinTo": coin_to,
245
+ "networkTo": network_to,
246
+ "amount": _amount(amount),
247
+ "withdrawalAmount": _amount(withdrawal_amount),
248
+ "rateType": rate_type,
249
+ }
250
+ )
251
+ return self._request("GET", "/rate", params=params)
252
+
253
+ def create_transaction(
254
+ self,
255
+ coin_from: str,
256
+ coin_to: str,
257
+ network_from: str,
258
+ network_to: str,
259
+ withdrawal_address: str,
260
+ *,
261
+ amount: float | str | None = None,
262
+ withdrawal_amount: float | str | None = None,
263
+ withdrawal_extra_id: str | None = None,
264
+ rate_type: RateType | None = None,
265
+ refund_address: str | None = None,
266
+ refund_extra_id: str | None = None,
267
+ slippage: float | str | None = None,
268
+ ) -> Transaction:
269
+ """Creates an exchange and returns the deposit address.
270
+
271
+ This opens a real swap. Call it only when a transaction should be created.
272
+
273
+ Args:
274
+ coin_from: Deposit currency code.
275
+ coin_to: Payout currency code.
276
+ network_from: Deposit network.
277
+ network_to: Payout network.
278
+ withdrawal_address: Address that receives the payout.
279
+ amount: Deposit amount.
280
+ withdrawal_amount: Desired payout amount.
281
+ withdrawal_extra_id: Memo or extra id for the payout address.
282
+ rate_type: ``float`` or ``fixed``. Defaults to float on the API.
283
+ refund_address: Address used if the swap is refunded.
284
+ refund_extra_id: Memo or extra id for the refund address.
285
+ slippage: Allowed rate movement, percent. Requires a refund address.
286
+
287
+ Returns:
288
+ Created transaction, including the deposit address.
289
+
290
+ Examples:
291
+ >>> tx = client.create_transaction(
292
+ ... "ETH",
293
+ ... "USDT",
294
+ ... "ETH",
295
+ ... "ETH",
296
+ ... "0x0E29D1E501f90649Ad5982E900c455006e4522FC",
297
+ ... amount=0.5,
298
+ ... rate_type="float",
299
+ ... )
300
+ """
301
+ payload = _defined(
302
+ {
303
+ "coinFrom": coin_from,
304
+ "networkFrom": network_from,
305
+ "coinTo": coin_to,
306
+ "networkTo": network_to,
307
+ "amount": amount,
308
+ "withdrawalAmount": withdrawal_amount,
309
+ "withdrawalAddress": withdrawal_address,
310
+ "withdrawalExtraId": withdrawal_extra_id,
311
+ "rateType": rate_type,
312
+ "refundAddress": refund_address,
313
+ "refundExtraId": refund_extra_id,
314
+ "slippage": slippage,
315
+ }
316
+ )
317
+ return self._request("POST", "/transactions", json_data=payload)
318
+
319
+ def get_transaction(self, transaction_id: str) -> Transaction:
320
+ """Fetches one transaction by id.
321
+
322
+ Args:
323
+ transaction_id: Transaction id returned by create.
324
+
325
+ Returns:
326
+ Transaction status and addresses.
327
+
328
+ Examples:
329
+ >>> tx = client.get_transaction("ab93061434dd0s")
330
+ """
331
+ return self._request("GET", f"/transactions/{quote(transaction_id, safe='')}")
332
+
333
+ def get_transactions(
334
+ self,
335
+ page: int | None = None,
336
+ size: int | None = None,
337
+ search: str | None = None,
338
+ sort: str | None = None,
339
+ order: SortOrder | None = None,
340
+ date_from: str | None = None,
341
+ date_to: str | None = None,
342
+ statuses: str | list[str] | None = None,
343
+ ) -> PaginatedTransactions:
344
+ """Lists transaction history for the affiliate API key.
345
+
346
+ Exolix enables this route per key. A rejected request means history is
347
+ not activated for that key.
348
+
349
+ Args:
350
+ page: Page number.
351
+ size: Page size.
352
+ search: Transaction id filter.
353
+ sort: Field to sort by.
354
+ order: ``asc`` or ``desc``.
355
+ date_from: Inclusive start datetime.
356
+ date_to: Inclusive end datetime.
357
+ statuses: One status or a list. A list is sent comma-separated.
358
+
359
+ Returns:
360
+ Page of transactions and the total count.
361
+
362
+ Examples:
363
+ >>> history = client.get_transactions(page=1, size=25, order="desc")
364
+ """
365
+ params = _defined(
366
+ {
367
+ "page": page,
368
+ "size": size,
369
+ "search": search,
370
+ "sort": sort,
371
+ "order": order,
372
+ "dateFrom": date_from,
373
+ "dateTo": date_to,
374
+ "statuses": statuses,
375
+ }
376
+ )
377
+ return self._request("GET", "/transactions", params=params or None)
exolix/errors.py ADDED
@@ -0,0 +1,23 @@
1
+ from typing import Any
2
+
3
+
4
+ class ExolixError(Exception):
5
+ """Exception raised when an Exolix API operation fails."""
6
+
7
+ def __init__(
8
+ self,
9
+ message: str,
10
+ status_code: int | None = None,
11
+ code: str | None = None,
12
+ response_body: Any = None,
13
+ ) -> None:
14
+ super().__init__(message)
15
+ self.message = message
16
+ self.status_code = status_code
17
+ self.code = code
18
+ self.response_body = response_body
19
+
20
+ def __str__(self) -> str:
21
+ if self.status_code is not None:
22
+ return f"[{self.status_code}] {self.message}"
23
+ return self.message
exolix/types.py ADDED
@@ -0,0 +1,103 @@
1
+ from typing import Literal, TypedDict
2
+
3
+ RateType = Literal["float", "fixed"]
4
+ SortOrder = Literal["asc", "desc"]
5
+ TransactionStatus = Literal[
6
+ "wait",
7
+ "confirmation",
8
+ "confirmed",
9
+ "exchanging",
10
+ "sending",
11
+ "success",
12
+ "overdue",
13
+ "refund",
14
+ "refunded",
15
+ ]
16
+
17
+
18
+ class Network(TypedDict, total=False):
19
+ network: str
20
+ name: str
21
+ shortName: str | None
22
+ notes: str | None
23
+ addressRegex: str | None
24
+ isDefault: bool
25
+ blockExplorer: str | None
26
+ memoNeeded: bool
27
+ memoName: str | None
28
+ memoRegex: str | None
29
+ precision: int
30
+ decimal: int | None
31
+ contract: str | None
32
+ icon: str | None
33
+
34
+
35
+ class Currency(TypedDict, total=False):
36
+ code: str
37
+ name: str
38
+ icon: str
39
+ notes: str | None
40
+ networks: list[Network]
41
+
42
+
43
+ class Rate(TypedDict, total=False):
44
+ fromAmount: float
45
+ toAmount: float
46
+ rate: float
47
+ message: str | None
48
+ minAmount: float
49
+ withdrawMin: float
50
+ maxAmount: float
51
+
52
+
53
+ class TransactionCoin(TypedDict, total=False):
54
+ coinCode: str
55
+ coinName: str
56
+ network: str
57
+ networkName: str
58
+ networkShortName: str | None
59
+ icon: str | None
60
+ memoName: str | None
61
+ contract: str | None
62
+
63
+
64
+ class TransactionHash(TypedDict, total=False):
65
+ hash: str | None
66
+ link: str | None
67
+
68
+
69
+ class Transaction(TypedDict, total=False):
70
+ id: str
71
+ amount: float
72
+ amountTo: float
73
+ coinFrom: TransactionCoin
74
+ coinTo: TransactionCoin
75
+ comment: str | None
76
+ createdAt: str
77
+ depositAddress: str
78
+ depositExtraId: str | None
79
+ withdrawalAddress: str
80
+ withdrawalExtraId: str | None
81
+ hashIn: TransactionHash
82
+ hashOut: TransactionHash
83
+ rate: float
84
+ rateType: str
85
+ refundAddress: str | None
86
+ refundExtraId: str | None
87
+ status: str
88
+ source: str
89
+
90
+
91
+ class PaginatedCurrencies(TypedDict):
92
+ data: list[Currency]
93
+ count: int
94
+
95
+
96
+ class PaginatedNetworks(TypedDict):
97
+ data: list[Network]
98
+ count: int
99
+
100
+
101
+ class PaginatedTransactions(TypedDict):
102
+ data: list[Transaction]
103
+ count: int
@@ -0,0 +1,206 @@
1
+ Metadata-Version: 2.5
2
+ Name: exolix-client
3
+ Version: 0.1.0
4
+ Summary: Python client for the Exolix cryptocurrency exchange API (v2)
5
+ Project-URL: Repository, https://github.com/TysikGG/api-kit
6
+ Project-URL: Documentation, https://exolix.com/developers
7
+ License-Expression: ISC
8
+ License-File: LICENSE
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: httpx>=0.27
11
+ Description-Content-Type: text/markdown
12
+
13
+ # exolix-client
14
+
15
+ [![PyPI version](https://img.shields.io/pypi/v/exolix-client.svg)](https://pypi.org/project/exolix-client/)
16
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](LICENSE)
17
+ [![Python Version](https://img.shields.io/pypi/pyversions/exolix-client.svg)](https://pypi.org/project/exolix-client/)
18
+
19
+ Modern, fully typed Python client library for the [Exolix](https://exolix.com) instant cryptocurrency exchange API (v2).
20
+
21
+ Official API documentation: <https://exolix.com/developers>
22
+
23
+ The PyPI package is `exolix-client`. Import it as `exolix`.
24
+
25
+ ---
26
+
27
+ ## Features
28
+
29
+ - ⚡ **Fixed & Floating Rates** — Quote a swap by deposit `amount` or by desired `withdrawal_amount`, at a floating or fixed rate.
30
+ - 🪙 **Currencies & Networks** — List coins, networks for one coin, and the full network catalog.
31
+ - 🐍 **Modern Pythonic Design** — Type annotations, context manager support (`with Exolix(...) as client:`), and a custom exception.
32
+ - 🔄 **Transactions** — Create a swap, fetch it by id, and list affiliate transaction history.
33
+
34
+ ---
35
+
36
+ ## Installation
37
+
38
+ ```bash
39
+ pip install exolix-client
40
+ ```
41
+
42
+ Requires Python 3.10 or higher.
43
+
44
+ ---
45
+
46
+ ## Authentication
47
+
48
+ Partner attribution is the affiliate API key. The default affiliate share is 0.3%. The client sends the key in one header and does not add a referral parameter:
49
+
50
+ ```python
51
+ from exolix import EXOLIX_API_KEY_HEADER
52
+
53
+ print(EXOLIX_API_KEY_HEADER) # "Authorization"
54
+ ```
55
+
56
+ Pass the key to the constructor. The client does not read `EXOLIX_API_KEY` from the environment. Public reads work without a key. Transaction history requires a key that Exolix has enabled for that route.
57
+
58
+ ---
59
+
60
+ ## Quick Start (Safe Check)
61
+
62
+ The quick check only queries currencies and a rate. It does not create a transaction:
63
+
64
+ ```python
65
+ import os
66
+ from exolix import Exolix
67
+
68
+ with Exolix(api_key=os.environ["EXOLIX_API_KEY"]) as client:
69
+ currencies = client.get_currencies(page=1, size=5, with_networks=True)
70
+ print(f"Available currencies: {currencies['count']}")
71
+
72
+ quote = client.get_rate(
73
+ "BTC",
74
+ "ETH",
75
+ "float",
76
+ network_from="BTC",
77
+ network_to="ETH",
78
+ amount="0.1",
79
+ )
80
+ print(f"0.1 BTC -> {quote['toAmount']} ETH (rate {quote['rate']})")
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Usage Guide
86
+
87
+ ### 1. Creating a Floating Rate Swap
88
+
89
+ ```python
90
+ from exolix import Exolix, ExolixError
91
+
92
+ with Exolix(api_key="your_api_key") as client:
93
+ quote = client.get_rate(
94
+ "ETH",
95
+ "USDT",
96
+ "float",
97
+ network_from="ETH",
98
+ network_to="ETH",
99
+ amount="0.5",
100
+ )
101
+ print(f"Min {quote['minAmount']}, you receive {quote['toAmount']} USDT")
102
+
103
+ tx = client.create_transaction(
104
+ "ETH",
105
+ "USDT",
106
+ "ETH",
107
+ "ETH",
108
+ "0x0E29D1E501f90649Ad5982E900c455006e4522FC",
109
+ amount=0.5,
110
+ rate_type="float",
111
+ )
112
+ print(f"Send ETH to {tx['depositAddress']} ({tx['id']})")
113
+ ```
114
+
115
+ `create_transaction` opens a real swap. Quote with `get_rate` first.
116
+
117
+ ### 2. Fixed Rate by Withdrawal Amount
118
+
119
+ ```python
120
+ quote = client.get_rate(
121
+ "ETH",
122
+ "USDT",
123
+ "fixed",
124
+ network_from="ETH",
125
+ network_to="ETH",
126
+ withdrawal_amount="50",
127
+ )
128
+ print(f"Send {quote['fromAmount']} ETH to receive {quote['toAmount']} USDT")
129
+ ```
130
+
131
+ ### 3. Tracking Status and History
132
+
133
+ ```python
134
+ tx = client.get_transaction("ab93061434dd0s")
135
+ print(tx["status"]) # wait, confirmation, confirmed, exchanging, sending, success, ...
136
+
137
+ history = client.get_transactions(page=1, size=25, order="desc")
138
+ print(history["count"])
139
+ ```
140
+
141
+ History is authenticated. Exolix enables it per API key.
142
+
143
+ ---
144
+
145
+ ## API Reference
146
+
147
+ ### `Exolix` Class
148
+
149
+ #### Constructor
150
+
151
+ ```python
152
+ Exolix(
153
+ api_key: str | None = None,
154
+ base_url: str | None = None,
155
+ timeout: float = 30.0,
156
+ user_agent: str | None = None,
157
+ )
158
+ ```
159
+
160
+ | Option | Type | Default | Description |
161
+ | :--- | :--- | :--- | :--- |
162
+ | `api_key` | `str` | unset | Affiliate API key. Sent as `Authorization`. |
163
+ | `base_url` | `str` | `"https://exolix.com/api/v2"` | API base URL. |
164
+ | `timeout` | `float` | `30.0` | Request timeout in seconds. |
165
+ | `user_agent` | `str` | `"exolix-client/0.1.0"` | User-Agent header. |
166
+
167
+ #### Methods
168
+
169
+ - **`get_currencies(...)`** — `GET /currencies`
170
+ - **`get_currency_networks(code)`** — `GET /currencies/{code}/networks`
171
+ - **`get_networks(...)`** — `GET /currencies/networks`
172
+ - **`get_rate(...)`** — `GET /rate` (`rate_type` `float` or `fixed`, `amount` or `withdrawal_amount`)
173
+ - **`create_transaction(...)`** — `POST /transactions`
174
+ - **`get_transaction(transaction_id)`** — `GET /transactions/{id}`
175
+ - **`get_transactions(...)`** — `GET /transactions` (authenticated history)
176
+
177
+ ---
178
+
179
+ ## Error Handling
180
+
181
+ ```python
182
+ from exolix import Exolix, ExolixError
183
+
184
+ try:
185
+ with Exolix(api_key="invalid_key") as client:
186
+ client.get_transactions()
187
+ except ExolixError as error:
188
+ print(f"{error.code}: {error.message} (HTTP {error.status_code})")
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Live Tests
194
+
195
+ Unit tests mock HTTP and always run. The live suite calls the real API and runs only when `EXOLIX_API_KEY` is set. It is read-only: currencies, networks, a floating rate, a fixed rate, and transaction history. It never creates a transaction.
196
+
197
+ ```bash
198
+ uv run pytest -m "not live"
199
+ EXOLIX_API_KEY=your_key uv run pytest -m live
200
+ ```
201
+
202
+ ---
203
+
204
+ ## License
205
+
206
+ ISC
@@ -0,0 +1,8 @@
1
+ exolix/__init__.py,sha256=_Ih3H6gLxTzRxfBlSn6siMIooicgxGPS-bhWKl4uh5s,539
2
+ exolix/client.py,sha256=_jwlJ_HXltbYbmIV0qr_Vc5_pQBlIwTzO0-Oas6lph8,12346
3
+ exolix/errors.py,sha256=cDe2uBKoCCBpIeQBk6gDA9UpucvNtiFv1OmP4V6LOq0,626
4
+ exolix/types.py,sha256=nvAZe2HkPb17LS1Vefq7aIMUvC1utJbYLibDJFTmITE,2018
5
+ exolix_client-0.1.0.dist-info/METADATA,sha256=H3NccXFjsCZ_jAiUtj8HKidzTjbbWJNA-weWPrE8bnY,5678
6
+ exolix_client-0.1.0.dist-info/WHEEL,sha256=2iZrJkDIkeDh0k9SikKMqFuRP-ua6mb4Ir1AsER4BEw,87
7
+ exolix_client-0.1.0.dist-info/licenses/LICENSE,sha256=8S0uYmn_aeljwFudfBoV91HcIo79yab9s0yhMVsOHJ0,751
8
+ exolix_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,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 api-kit contributors
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10
+ WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11
+ MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12
+ ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13
+ WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14
+ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15
+ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.