cryptochief-crypto-processing-python 0.5.0__tar.gz → 0.6.0__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.
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/PKG-INFO +36 -4
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/README.md +35 -3
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/_version.py +1 -1
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/errors.py +18 -4
- cryptochief_crypto_processing_python-0.6.0/src/cryptochief/services/wallets.py +222 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/transport.py +27 -7
- cryptochief_crypto_processing_python-0.5.0/src/cryptochief/services/wallets.py +0 -86
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/.gitignore +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/LICENSE +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/pyproject.toml +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/__init__.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/_models.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/amount.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/assets.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/chains.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/client.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/contract/__init__.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/contract/base58.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/contract/borsh.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/contract/evm_abi.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/contract/keccak.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/contract/tron_address.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/pagination.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/poll.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/py.typed +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/rsa.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/sentinels.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/__init__.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/base.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/blockchain.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/credits.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/currencies.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/payins.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/payouts.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/static_deposits.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/sweeps.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/transactions.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/services/withdrawals.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/sign.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/ton/__init__.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/ton/address.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/ton/messages.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/ton/rpc.py +0 -0
- {cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/src/cryptochief/webhook.py +0 -0
{cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: cryptochief-crypto-processing-python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Official async Python SDK for the Crypto Chief crypto payment gateway and crypto processing API. Accept crypto payments, send single and mass crypto payouts, sign on-chain transactions and smart-contract calls, manage wallets, convert fiat to crypto, and verify webhooks across Ethereum, BNB Smart Chain, Polygon, Tron, TON, Solana, Bitcoin, XRP and 20+ blockchains. USDT and USDC stablecoin support with int-precise amounts and asyncio/httpx.
|
|
5
5
|
Project-URL: Homepage, https://crypto-chief.com/processing/
|
|
6
6
|
Project-URL: Documentation, https://docs-sdk.crypto-chief.com/processing/python
|
|
@@ -120,7 +120,7 @@ Both credentials come from the Dashboard -> Project.
|
|
|
120
120
|
| Solana programs | `client.transactions` | `sign_anchor_call`, `sign_solana_call` |
|
|
121
121
|
| TON contract calls (Jetton / NFT / text) | `client.transactions` | `jetton_transfer`, `nft_transfer`, `send_ton_comment`, `sign_ton_call` |
|
|
122
122
|
| Accept incoming payments | `client.pay_ins` | `create`, `select_asset`, `reset_asset`, `cancel`, `info`, `history`, `wait_for` |
|
|
123
|
-
| Wallet management + RSA decrypt | `client.wallets` | `generate`, `list`, `info`, `freeze`, `decrypt_private_key` |
|
|
123
|
+
| Wallet management + RSA decrypt | `client.wallets` | `generate`, `list`, `info`, `freeze`, `rebind_master`, `set_callback_url`, `set_label`, `decrypt_private_key` |
|
|
124
124
|
| Treasury sweeps | `client.sweeps` | `force`, `history`, `wallet_history`, `settings`, `update_settings` |
|
|
125
125
|
| Withdrawals (read-only) | `client.withdrawals` | `info`, `history` |
|
|
126
126
|
| Static-deposit history | `client.static_deposits` | `info`, `history` |
|
|
@@ -302,8 +302,11 @@ the sender IPs in `WEBHOOK_SENDER_IPS` at your edge for defense in depth.
|
|
|
302
302
|
## Errors
|
|
303
303
|
|
|
304
304
|
Everything the SDK raises derives from `CryptoChiefError`. API failures are
|
|
305
|
-
`APIError` with a stable `.code` (
|
|
306
|
-
|
|
305
|
+
`APIError` with a stable `.code` (plus `.message`, `.http_status` and the
|
|
306
|
+
untouched `.raw` body); branch on `ErrorCode` rather than parsing messages. Both
|
|
307
|
+
envelope shapes the gateway sends - its own refusals, which carry the code in
|
|
308
|
+
`error`, and refusals relayed from upstream as `SERVICE_ERROR` with the code in
|
|
309
|
+
`msg` - resolve to `.code`. 5xx and network errors are retried automatically;
|
|
307
310
|
4xx is raised immediately.
|
|
308
311
|
|
|
309
312
|
```python
|
|
@@ -371,6 +374,35 @@ priv = client.wallets.decrypt_private_key(wallet.private_key_encrypted)
|
|
|
371
374
|
`completed_at` filled in. Earlier platform versions reported `completed` at
|
|
372
375
|
broadcast, so a sweep could read as settled while its transaction was still
|
|
373
376
|
unconfirmed.
|
|
377
|
+
- **My deposits are settling on the wrong master wallet.**
|
|
378
|
+
`client.wallets.rebind_master(address, master_wallet_address)` re-points a
|
|
379
|
+
transit or static wallet at another master of the project - the link is
|
|
380
|
+
otherwise decided at creation, falling back to the project's *oldest* master
|
|
381
|
+
of that chain family when none was named. It moves no money: it changes where
|
|
382
|
+
the **next** sweep settles, including sweeps already queued, and anything
|
|
383
|
+
already swept sits on the previous master and has to be sent from there as an
|
|
384
|
+
ordinary payout. It is idempotent, so re-running the same list is safe.
|
|
385
|
+
- **A static address is announcing deposits to the wrong URL.** Deposits go to
|
|
386
|
+
the callback the *address* carries, fixed when it was minted - so an address
|
|
387
|
+
you did not create through your own integration, or one minted before your
|
|
388
|
+
endpoint moved, keeps notifying somewhere else.
|
|
389
|
+
`client.wallets.set_callback_url(address, url)` corrects it, from the next
|
|
390
|
+
deposit on (one already announced is not re-announced). Pass `""` to clear it
|
|
391
|
+
and stop the announcements - the SDK sends the empty string rather than
|
|
392
|
+
dropping it the way it drops unset optional fields, and the wallet then reads
|
|
393
|
+
back `callback_url=None`. Static wallets only.
|
|
394
|
+
- **How do I name a wallet?** Pass `label` on
|
|
395
|
+
`client.wallets.generate(GenerateWalletRequest(..., label="EU shop"))`. It
|
|
396
|
+
applies to every wallet type, is up to 255 characters, and is yours alone -
|
|
397
|
+
nothing on chain and nothing in routing depends on it.
|
|
398
|
+
- **How do I rename a wallet I already have?**
|
|
399
|
+
`client.wallets.set_label(address, "EU shop")` - every wallet type, master
|
|
400
|
+
and transit included, unlike the deposit callback. Pass `""` to clear the
|
|
401
|
+
name: as with `set_callback_url`, the empty string is sent rather than
|
|
402
|
+
dropped, and the wallet then reads back `label=None`. The name comes back on
|
|
403
|
+
every response that describes a wallet - generation, `info`, `list`, and the
|
|
404
|
+
answers of `rebind_master` / `set_callback_url` / `set_label` itself - as
|
|
405
|
+
`wallet.label`, `None` when the wallet is unnamed.
|
|
374
406
|
- **How do I keep test payments off real chains?** Set `environment` on
|
|
375
407
|
`CreatePayInRequest` to `Environment.TESTNET` or `Environment.MAINNET`. It
|
|
376
408
|
constrains the asset the platform picks when you have not named a concrete
|
{cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/README.md
RENAMED
|
@@ -81,7 +81,7 @@ Both credentials come from the Dashboard -> Project.
|
|
|
81
81
|
| Solana programs | `client.transactions` | `sign_anchor_call`, `sign_solana_call` |
|
|
82
82
|
| TON contract calls (Jetton / NFT / text) | `client.transactions` | `jetton_transfer`, `nft_transfer`, `send_ton_comment`, `sign_ton_call` |
|
|
83
83
|
| Accept incoming payments | `client.pay_ins` | `create`, `select_asset`, `reset_asset`, `cancel`, `info`, `history`, `wait_for` |
|
|
84
|
-
| Wallet management + RSA decrypt | `client.wallets` | `generate`, `list`, `info`, `freeze`, `decrypt_private_key` |
|
|
84
|
+
| Wallet management + RSA decrypt | `client.wallets` | `generate`, `list`, `info`, `freeze`, `rebind_master`, `set_callback_url`, `set_label`, `decrypt_private_key` |
|
|
85
85
|
| Treasury sweeps | `client.sweeps` | `force`, `history`, `wallet_history`, `settings`, `update_settings` |
|
|
86
86
|
| Withdrawals (read-only) | `client.withdrawals` | `info`, `history` |
|
|
87
87
|
| Static-deposit history | `client.static_deposits` | `info`, `history` |
|
|
@@ -263,8 +263,11 @@ the sender IPs in `WEBHOOK_SENDER_IPS` at your edge for defense in depth.
|
|
|
263
263
|
## Errors
|
|
264
264
|
|
|
265
265
|
Everything the SDK raises derives from `CryptoChiefError`. API failures are
|
|
266
|
-
`APIError` with a stable `.code` (
|
|
267
|
-
|
|
266
|
+
`APIError` with a stable `.code` (plus `.message`, `.http_status` and the
|
|
267
|
+
untouched `.raw` body); branch on `ErrorCode` rather than parsing messages. Both
|
|
268
|
+
envelope shapes the gateway sends - its own refusals, which carry the code in
|
|
269
|
+
`error`, and refusals relayed from upstream as `SERVICE_ERROR` with the code in
|
|
270
|
+
`msg` - resolve to `.code`. 5xx and network errors are retried automatically;
|
|
268
271
|
4xx is raised immediately.
|
|
269
272
|
|
|
270
273
|
```python
|
|
@@ -332,6 +335,35 @@ priv = client.wallets.decrypt_private_key(wallet.private_key_encrypted)
|
|
|
332
335
|
`completed_at` filled in. Earlier platform versions reported `completed` at
|
|
333
336
|
broadcast, so a sweep could read as settled while its transaction was still
|
|
334
337
|
unconfirmed.
|
|
338
|
+
- **My deposits are settling on the wrong master wallet.**
|
|
339
|
+
`client.wallets.rebind_master(address, master_wallet_address)` re-points a
|
|
340
|
+
transit or static wallet at another master of the project - the link is
|
|
341
|
+
otherwise decided at creation, falling back to the project's *oldest* master
|
|
342
|
+
of that chain family when none was named. It moves no money: it changes where
|
|
343
|
+
the **next** sweep settles, including sweeps already queued, and anything
|
|
344
|
+
already swept sits on the previous master and has to be sent from there as an
|
|
345
|
+
ordinary payout. It is idempotent, so re-running the same list is safe.
|
|
346
|
+
- **A static address is announcing deposits to the wrong URL.** Deposits go to
|
|
347
|
+
the callback the *address* carries, fixed when it was minted - so an address
|
|
348
|
+
you did not create through your own integration, or one minted before your
|
|
349
|
+
endpoint moved, keeps notifying somewhere else.
|
|
350
|
+
`client.wallets.set_callback_url(address, url)` corrects it, from the next
|
|
351
|
+
deposit on (one already announced is not re-announced). Pass `""` to clear it
|
|
352
|
+
and stop the announcements - the SDK sends the empty string rather than
|
|
353
|
+
dropping it the way it drops unset optional fields, and the wallet then reads
|
|
354
|
+
back `callback_url=None`. Static wallets only.
|
|
355
|
+
- **How do I name a wallet?** Pass `label` on
|
|
356
|
+
`client.wallets.generate(GenerateWalletRequest(..., label="EU shop"))`. It
|
|
357
|
+
applies to every wallet type, is up to 255 characters, and is yours alone -
|
|
358
|
+
nothing on chain and nothing in routing depends on it.
|
|
359
|
+
- **How do I rename a wallet I already have?**
|
|
360
|
+
`client.wallets.set_label(address, "EU shop")` - every wallet type, master
|
|
361
|
+
and transit included, unlike the deposit callback. Pass `""` to clear the
|
|
362
|
+
name: as with `set_callback_url`, the empty string is sent rather than
|
|
363
|
+
dropped, and the wallet then reads back `label=None`. The name comes back on
|
|
364
|
+
every response that describes a wallet - generation, `info`, `list`, and the
|
|
365
|
+
answers of `rebind_master` / `set_callback_url` / `set_label` itself - as
|
|
366
|
+
`wallet.label`, `None` when the wallet is unnamed.
|
|
335
367
|
- **How do I keep test payments off real chains?** Set `environment` on
|
|
336
368
|
`CreatePayInRequest` to `Environment.TESTNET` or `Environment.MAINNET`. It
|
|
337
369
|
constrains the asset the platform picks when you have not named a concrete
|
|
@@ -19,19 +19,27 @@ class CryptoChiefError(Exception):
|
|
|
19
19
|
class APIError(CryptoChiefError):
|
|
20
20
|
"""A typed Crypto Chief error response.
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
:attr:`code` is the machine-readable identifier to branch on, whichever
|
|
23
|
+
envelope shape the refusal arrived in: the gateway's own refusals carry the
|
|
24
|
+
code in ``error`` and an English sentence in ``msg``
|
|
25
|
+
(``{"error": "LABEL_TOO_LONG", "msg": "label is longer than 255 characters"}``),
|
|
26
|
+
while a relayed upstream refusal carries the generic ``SERVICE_ERROR``
|
|
27
|
+
marker in ``error`` and the code in ``msg``
|
|
28
|
+
(``{"error": "SERVICE_ERROR", "msg": "wallet_not_found"}``). Both resolve to
|
|
29
|
+
:attr:`code`::
|
|
26
30
|
|
|
27
31
|
try:
|
|
28
32
|
await client.payouts.execute(req)
|
|
29
33
|
except APIError as e:
|
|
30
34
|
if e.code == ErrorCode.INSUFFICIENT_FUNDS:
|
|
31
35
|
... # top up and retry
|
|
36
|
+
|
|
37
|
+
:attr:`message` is the human-readable half - the sentence when the gateway
|
|
38
|
+
sent one - and :attr:`raw` is the untouched response body.
|
|
32
39
|
"""
|
|
33
40
|
|
|
34
41
|
code: str
|
|
42
|
+
message: str
|
|
35
43
|
http_status: int
|
|
36
44
|
raw: Optional[str]
|
|
37
45
|
|
|
@@ -46,6 +54,7 @@ class APIError(CryptoChiefError):
|
|
|
46
54
|
# Normalize an ErrorCode member to its wire string ("NETWORK_ERROR"),
|
|
47
55
|
# not its enum repr ("ErrorCode.NETWORK_ERROR").
|
|
48
56
|
self.code = code.value if isinstance(code, Enum) else str(code)
|
|
57
|
+
self.message = message or ""
|
|
49
58
|
self.http_status = http_status
|
|
50
59
|
self.raw = raw
|
|
51
60
|
super().__init__(self._format(http_status, self.code, message))
|
|
@@ -75,6 +84,11 @@ class ErrorCode(str, Enum):
|
|
|
75
84
|
ORDER_NOT_LIVE = "ORDER_NOT_LIVE"
|
|
76
85
|
ASSET_ALREADY_SELECTED = "ASSET_ALREADY_SELECTED"
|
|
77
86
|
INVALID_PARAMS = "INVALID_PARAMS"
|
|
87
|
+
#: A wallet label over 255 characters.
|
|
88
|
+
LABEL_TOO_LONG = "LABEL_TOO_LONG"
|
|
89
|
+
#: The gateway's marker for a refusal relayed from an upstream service; the
|
|
90
|
+
#: machine code then travels in ``msg`` and is what :attr:`APIError.code`
|
|
91
|
+
#: reports, so this member is rarely what you compare against.
|
|
78
92
|
SERVICE_ERROR = "SERVICE_ERROR"
|
|
79
93
|
UNAUTHORIZED = "UNAUTHORIZED"
|
|
80
94
|
URL_CALLBACK_REQUIRED = "URL_CALLBACK_REQUIRED"
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
"""Wallet management + local RSA private-key decryption."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from enum import Enum
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
from .._models import from_dict
|
|
10
|
+
from ..errors import CryptoChiefError
|
|
11
|
+
from .base import BaseService
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class WalletType(str, Enum):
|
|
15
|
+
MASTER = "master"
|
|
16
|
+
TRANSIT = "transit"
|
|
17
|
+
STATIC = "static"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(kw_only=True)
|
|
21
|
+
class GenerateWalletRequest:
|
|
22
|
+
wallet_type: str
|
|
23
|
+
chain_family: str
|
|
24
|
+
master_wallet_address: Optional[str] = None # transit/static wallets only
|
|
25
|
+
callback_url: Optional[str] = None # static wallets only - per-deposit webhook URL
|
|
26
|
+
#: A name for the wallet, for people reading a list of them. Applies to
|
|
27
|
+
#: every wallet type - it names the wallet, it is not a property of its
|
|
28
|
+
#: role - and is yours alone: nothing on chain and nothing in routing
|
|
29
|
+
#: depends on it. Up to 255 characters, longer answers ``LABEL_TOO_LONG``.
|
|
30
|
+
#: Leave it ``None`` to omit it; the endpoint rejects unknown fields, and an
|
|
31
|
+
#: empty string is a name rather than the absence of one.
|
|
32
|
+
#: :meth:`WalletsService.set_label` renames the wallet afterwards.
|
|
33
|
+
label: Optional[str] = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass(kw_only=True)
|
|
37
|
+
class WalletCoinBalance:
|
|
38
|
+
address: Optional[str] = None
|
|
39
|
+
chain: Optional[str] = None
|
|
40
|
+
coin: Optional[str] = None
|
|
41
|
+
contract: Optional[str] = None
|
|
42
|
+
decimals: int = 0
|
|
43
|
+
value: Optional[str] = None
|
|
44
|
+
human_value: Optional[str] = None
|
|
45
|
+
amount_usd: Optional[str] = None
|
|
46
|
+
timestamp: Optional[int] = None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass(kw_only=True)
|
|
50
|
+
class Wallet:
|
|
51
|
+
address: str = ""
|
|
52
|
+
chain_family: Optional[str] = None
|
|
53
|
+
type: Optional[str] = None
|
|
54
|
+
wallet_type: Optional[str] = None
|
|
55
|
+
frozen: Optional[bool] = None
|
|
56
|
+
#: The master this wallet sweeps into, ``None`` when it has none - a master
|
|
57
|
+
#: wallet has no master of its own. The API always sends the key and sends
|
|
58
|
+
#: ``null`` rather than an empty string, so ``None`` here means "no master",
|
|
59
|
+
#: not "not reported". :meth:`WalletsService.rebind_master` changes it.
|
|
60
|
+
master_wallet_address: Optional[str] = None
|
|
61
|
+
#: Where deposits to this address are announced, ``None`` when nowhere. Only
|
|
62
|
+
#: a static wallet has one: a master or transit always reads ``None``.
|
|
63
|
+
#: :meth:`WalletsService.set_callback_url` changes it.
|
|
64
|
+
callback_url: Optional[str] = None
|
|
65
|
+
#: The wallet's name, ``None`` when it has none. Every wallet type can carry
|
|
66
|
+
#: one, and every response that describes a wallet reports it. The API
|
|
67
|
+
#: always sends the key and sends ``null`` rather than an empty string, so
|
|
68
|
+
#: ``None`` here means "unnamed" - a cleared label reads back as ``None``,
|
|
69
|
+
#: never as ``""``. :meth:`WalletsService.set_label` changes it.
|
|
70
|
+
label: Optional[str] = None
|
|
71
|
+
#: Base64 RSA-OAEP/SHA-256 ciphertext - decrypt with ``decrypt_private_key``.
|
|
72
|
+
private_key_encrypted: Optional[str] = None
|
|
73
|
+
created_at: Optional[str] = None
|
|
74
|
+
coins: Optional[List[WalletCoinBalance]] = None
|
|
75
|
+
total_balance_usd: Optional[str] = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass(kw_only=True)
|
|
79
|
+
class ListWalletsResponse:
|
|
80
|
+
items: Optional[List[Wallet]] = None
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class WalletsService(BaseService):
|
|
84
|
+
async def generate(self, req: GenerateWalletRequest) -> Wallet:
|
|
85
|
+
"""Provision a new wallet on the requested chain family."""
|
|
86
|
+
return from_dict(Wallet, await self._post("/v1/wallets/generate", req))
|
|
87
|
+
|
|
88
|
+
async def list(self) -> ListWalletsResponse:
|
|
89
|
+
"""Every wallet on the project."""
|
|
90
|
+
return from_dict(ListWalletsResponse, await self._post("/v1/wallets/list", {}))
|
|
91
|
+
|
|
92
|
+
async def info(self, address: str) -> Wallet:
|
|
93
|
+
"""Details and current balances of one wallet."""
|
|
94
|
+
return from_dict(Wallet, await self._post("/v1/wallets/info", {"address": address}))
|
|
95
|
+
|
|
96
|
+
async def freeze(self, address: str) -> Wallet:
|
|
97
|
+
"""Toggle the frozen flag - the response's ``frozen`` field is the new state."""
|
|
98
|
+
return from_dict(Wallet, await self._post("/v1/wallets/freeze", {"address": address}))
|
|
99
|
+
|
|
100
|
+
async def rebind_master(self, address: str, master_wallet_address: str) -> Wallet:
|
|
101
|
+
"""Re-point a transit or static wallet at another master of the project.
|
|
102
|
+
|
|
103
|
+
The master link is decided when the wallet is created - at the master
|
|
104
|
+
named on that request, or, when none was named, at the project's *oldest*
|
|
105
|
+
master of that chain family, which on a project with more than one master
|
|
106
|
+
is rarely the one you meant. This is the way back.
|
|
107
|
+
|
|
108
|
+
It moves no money. It changes where the *next* sweep settles, including
|
|
109
|
+
sweeps already queued, because the destination is resolved when the sweep
|
|
110
|
+
runs; anything already swept sits on the previous master and has to be
|
|
111
|
+
sent from there as an ordinary payout.
|
|
112
|
+
|
|
113
|
+
Idempotent - a wallet already bound to that master answers 200 unchanged,
|
|
114
|
+
so re-running the same list is safe. A master wallet cannot be
|
|
115
|
+
re-pointed at all (``only transit and static wallets have a master``);
|
|
116
|
+
naming something that is not a master as the TARGET is a different
|
|
117
|
+
refusal (``not_a_master_wallet``). The target master must be the same
|
|
118
|
+
chain family (``chain_family_mismatch``) and not frozen
|
|
119
|
+
(``master_wallet_frozen``), since sweeping into a frozen master would
|
|
120
|
+
strand the funds there.
|
|
121
|
+
|
|
122
|
+
The gateway relays every upstream refusal as
|
|
123
|
+
``{"error": "SERVICE_ERROR", "msg": "<token>"}``, and the SDK reports
|
|
124
|
+
that token as ``APIError.code`` - so branch on the code, not on the
|
|
125
|
+
message text. These tokens are per-endpoint and are not
|
|
126
|
+
:class:`~cryptochief.ErrorCode` members. Memo/tag-based families share one deposit
|
|
127
|
+
account across orders and are excluded
|
|
128
|
+
(``shared_transit_cannot_be_rebound``). Both addresses resolve against
|
|
129
|
+
the authenticated project, so one that is not yours answers
|
|
130
|
+
``wallet_not_found`` / ``master_wallet_not_found`` rather than revealing
|
|
131
|
+
that it exists elsewhere.
|
|
132
|
+
|
|
133
|
+
Returns the wallet as it now stands.
|
|
134
|
+
"""
|
|
135
|
+
return from_dict(
|
|
136
|
+
Wallet,
|
|
137
|
+
await self._post(
|
|
138
|
+
"/v1/wallets/rebind-master",
|
|
139
|
+
{"address": address, "master_wallet_address": master_wallet_address},
|
|
140
|
+
),
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
async def set_callback_url(self, address: str, callback_url: str) -> Wallet:
|
|
144
|
+
"""Set or clear a static wallet's deposit webhook after creation.
|
|
145
|
+
|
|
146
|
+
Deposits are announced to the callback URL the *address* carries, which
|
|
147
|
+
is fixed when the address is minted - so an address you did not create
|
|
148
|
+
through your own integration, or one minted before your endpoint moved,
|
|
149
|
+
keeps announcing its deposits somewhere else, or nowhere. This corrects
|
|
150
|
+
it, from the next deposit on: one already announced is not re-announced
|
|
151
|
+
to the new URL.
|
|
152
|
+
|
|
153
|
+
Pass ``""`` to clear it and stop announcing deposits for the address.
|
|
154
|
+
That is a real instruction rather than a missing field, so the SDK sends
|
|
155
|
+
the empty string instead of dropping it the way it drops unset optional
|
|
156
|
+
fields; the wallet then reads back ``callback_url=None``. ``None`` is
|
|
157
|
+
not that instruction and is refused here rather than silently leaving
|
|
158
|
+
the field off the body.
|
|
159
|
+
|
|
160
|
+
Static wallets only - a master or transit has no per-deposit callback
|
|
161
|
+
and answers 400. The address resolves against the authenticated project,
|
|
162
|
+
so one that is not yours answers ``wallet_not_found``.
|
|
163
|
+
|
|
164
|
+
Returns the wallet as it now stands.
|
|
165
|
+
"""
|
|
166
|
+
if callback_url is None:
|
|
167
|
+
raise CryptoChiefError(
|
|
168
|
+
'cryptochief: set_callback_url: callback_url is required; pass "" to clear it'
|
|
169
|
+
)
|
|
170
|
+
return from_dict(
|
|
171
|
+
Wallet,
|
|
172
|
+
await self._post(
|
|
173
|
+
"/v1/wallets/callback-url",
|
|
174
|
+
{"address": address, "callback_url": callback_url},
|
|
175
|
+
),
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
async def set_label(self, address: str, label: str) -> Wallet:
|
|
179
|
+
"""Set or clear a wallet's label - the name it is read by.
|
|
180
|
+
|
|
181
|
+
A label is yours alone: nothing on chain and nothing in routing depends
|
|
182
|
+
on it. It is also the only thing telling one freshly minted address
|
|
183
|
+
apart from the next in a list, so a wallet created before the label was
|
|
184
|
+
supported, or minted somewhere other than your own integration, is worth
|
|
185
|
+
naming after the fact. This is how.
|
|
186
|
+
|
|
187
|
+
Every wallet type can be renamed - master, transit and static alike,
|
|
188
|
+
because a label names the wallet rather than describing its role. That
|
|
189
|
+
is unlike :meth:`set_callback_url`, which only a static wallet has.
|
|
190
|
+
|
|
191
|
+
Pass ``""`` to clear the name and leave the wallet unnamed. That is a
|
|
192
|
+
real instruction rather than a missing field, so the SDK sends the empty
|
|
193
|
+
string instead of dropping it the way it drops unset optional fields;
|
|
194
|
+
the wallet then reads back ``label=None``. ``None`` is not that
|
|
195
|
+
instruction and is refused here rather than silently leaving the field
|
|
196
|
+
off the body.
|
|
197
|
+
|
|
198
|
+
Up to 255 characters, longer answers ``LABEL_TOO_LONG``. The address
|
|
199
|
+
resolves against the authenticated project, so one that is not yours
|
|
200
|
+
answers ``wallet_not_found`` rather than revealing that it exists
|
|
201
|
+
elsewhere.
|
|
202
|
+
|
|
203
|
+
Returns the wallet as it now stands.
|
|
204
|
+
"""
|
|
205
|
+
if label is None:
|
|
206
|
+
raise CryptoChiefError(
|
|
207
|
+
'cryptochief: set_label: label is required; pass "" to clear it'
|
|
208
|
+
)
|
|
209
|
+
return from_dict(
|
|
210
|
+
Wallet,
|
|
211
|
+
await self._post("/v1/wallets/label", {"address": address, "label": label}),
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def decrypt_private_key(self, encrypted: str) -> str:
|
|
215
|
+
"""Decrypt a generated wallet's ``private_key_encrypted`` field locally.
|
|
216
|
+
|
|
217
|
+
Uses the RSA private key configured on the client (``rsa_private_key``
|
|
218
|
+
option) and returns the chain-native hex private key. Raises
|
|
219
|
+
:class:`RsaKeyNotConfiguredError` if no key was configured. Synchronous -
|
|
220
|
+
never touches the network.
|
|
221
|
+
"""
|
|
222
|
+
return self._client.rsa_decrypt(encrypted)
|
|
@@ -8,11 +8,26 @@ import random
|
|
|
8
8
|
from .errors import APIError, ErrorCode
|
|
9
9
|
|
|
10
10
|
|
|
11
|
+
def _field(env: dict, key: str) -> str:
|
|
12
|
+
"""Read ``key`` from an error envelope as a trimmed string (``""`` if absent)."""
|
|
13
|
+
value = env.get(key)
|
|
14
|
+
return value.strip() if isinstance(value, str) else ""
|
|
15
|
+
|
|
16
|
+
|
|
11
17
|
def parse_api_error(status: int, body: str) -> APIError:
|
|
12
18
|
"""Parse a non-2xx response body into an :class:`APIError` with a stable code.
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
``msg``
|
|
20
|
+
Refusals arrive in two envelope shapes. When the gateway itself refuses, the
|
|
21
|
+
machine code is in ``error`` and ``msg`` holds an English sentence
|
|
22
|
+
(``{"error": "LABEL_TOO_LONG", "msg": "label is longer than 255 characters"}``).
|
|
23
|
+
When it relays an upstream refusal, ``error`` is the generic
|
|
24
|
+
``SERVICE_ERROR`` marker and the machine code is in ``msg``
|
|
25
|
+
(``{"error": "SERVICE_ERROR", "msg": "wallet_not_found"}``).
|
|
26
|
+
|
|
27
|
+
So the code is ``error`` unless that is ``SERVICE_ERROR``, in which case it
|
|
28
|
+
is ``msg``; an empty result falls back to ``error`` and then
|
|
29
|
+
``HTTP_<status>``. The human-readable message prefers ``msg`` and falls back
|
|
30
|
+
to ``error``.
|
|
16
31
|
"""
|
|
17
32
|
env: dict = {}
|
|
18
33
|
try:
|
|
@@ -21,11 +36,16 @@ def parse_api_error(status: int, body: str) -> APIError:
|
|
|
21
36
|
env = parsed
|
|
22
37
|
except ValueError:
|
|
23
38
|
pass # non-JSON error body -> fall back to HTTP_<status>
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return APIError(
|
|
39
|
+
|
|
40
|
+
error = _field(env, "error")
|
|
41
|
+
msg = _field(env, "msg")
|
|
42
|
+
code = error if error and error != ErrorCode.SERVICE_ERROR else (msg or error)
|
|
43
|
+
return APIError(
|
|
44
|
+
code or f"HTTP_{status}",
|
|
45
|
+
http_status=status,
|
|
46
|
+
message=msg or error,
|
|
47
|
+
raw=body,
|
|
48
|
+
)
|
|
29
49
|
|
|
30
50
|
|
|
31
51
|
def backoff_delay(attempt: int, base_ms: float, max_ms: float) -> float:
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
"""Wallet management + local RSA private-key decryption."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from dataclasses import dataclass
|
|
6
|
-
from enum import Enum
|
|
7
|
-
from typing import List, Optional
|
|
8
|
-
|
|
9
|
-
from .._models import from_dict
|
|
10
|
-
from .base import BaseService
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class WalletType(str, Enum):
|
|
14
|
-
MASTER = "master"
|
|
15
|
-
TRANSIT = "transit"
|
|
16
|
-
STATIC = "static"
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
@dataclass(kw_only=True)
|
|
20
|
-
class GenerateWalletRequest:
|
|
21
|
-
wallet_type: str
|
|
22
|
-
chain_family: str
|
|
23
|
-
master_wallet_address: Optional[str] = None # transit/static wallets only
|
|
24
|
-
callback_url: Optional[str] = None # static wallets only - per-deposit webhook URL
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
@dataclass(kw_only=True)
|
|
28
|
-
class WalletCoinBalance:
|
|
29
|
-
address: Optional[str] = None
|
|
30
|
-
chain: Optional[str] = None
|
|
31
|
-
coin: Optional[str] = None
|
|
32
|
-
contract: Optional[str] = None
|
|
33
|
-
decimals: int = 0
|
|
34
|
-
value: Optional[str] = None
|
|
35
|
-
human_value: Optional[str] = None
|
|
36
|
-
amount_usd: Optional[str] = None
|
|
37
|
-
timestamp: Optional[int] = None
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@dataclass(kw_only=True)
|
|
41
|
-
class Wallet:
|
|
42
|
-
address: str = ""
|
|
43
|
-
chain_family: Optional[str] = None
|
|
44
|
-
type: Optional[str] = None
|
|
45
|
-
wallet_type: Optional[str] = None
|
|
46
|
-
frozen: Optional[bool] = None
|
|
47
|
-
master_wallet_address: Optional[str] = None
|
|
48
|
-
callback_url: Optional[str] = None
|
|
49
|
-
#: Base64 RSA-OAEP/SHA-256 ciphertext - decrypt with ``decrypt_private_key``.
|
|
50
|
-
private_key_encrypted: Optional[str] = None
|
|
51
|
-
created_at: Optional[str] = None
|
|
52
|
-
coins: Optional[List[WalletCoinBalance]] = None
|
|
53
|
-
total_balance_usd: Optional[str] = None
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
@dataclass(kw_only=True)
|
|
57
|
-
class ListWalletsResponse:
|
|
58
|
-
items: Optional[List[Wallet]] = None
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
class WalletsService(BaseService):
|
|
62
|
-
async def generate(self, req: GenerateWalletRequest) -> Wallet:
|
|
63
|
-
"""Provision a new wallet on the requested chain family."""
|
|
64
|
-
return from_dict(Wallet, await self._post("/v1/wallets/generate", req))
|
|
65
|
-
|
|
66
|
-
async def list(self) -> ListWalletsResponse:
|
|
67
|
-
"""Every wallet on the project."""
|
|
68
|
-
return from_dict(ListWalletsResponse, await self._post("/v1/wallets/list", {}))
|
|
69
|
-
|
|
70
|
-
async def info(self, address: str) -> Wallet:
|
|
71
|
-
"""Details and current balances of one wallet."""
|
|
72
|
-
return from_dict(Wallet, await self._post("/v1/wallets/info", {"address": address}))
|
|
73
|
-
|
|
74
|
-
async def freeze(self, address: str) -> Wallet:
|
|
75
|
-
"""Toggle the frozen flag - the response's ``frozen`` field is the new state."""
|
|
76
|
-
return from_dict(Wallet, await self._post("/v1/wallets/freeze", {"address": address}))
|
|
77
|
-
|
|
78
|
-
def decrypt_private_key(self, encrypted: str) -> str:
|
|
79
|
-
"""Decrypt a generated wallet's ``private_key_encrypted`` field locally.
|
|
80
|
-
|
|
81
|
-
Uses the RSA private key configured on the client (``rsa_private_key``
|
|
82
|
-
option) and returns the chain-native hex private key. Raises
|
|
83
|
-
:class:`RsaKeyNotConfiguredError` if no key was configured. Synchronous -
|
|
84
|
-
never touches the network.
|
|
85
|
-
"""
|
|
86
|
-
return self._client.rsa_decrypt(encrypted)
|
{cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/.gitignore
RENAMED
|
File without changes
|
{cryptochief_crypto_processing_python-0.5.0 → cryptochief_crypto_processing_python-0.6.0}/LICENSE
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|