cryptochief-crypto-processing-python 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.
- cryptochief/__init__.py +336 -0
- cryptochief/_models.py +93 -0
- cryptochief/_version.py +3 -0
- cryptochief/amount.py +97 -0
- cryptochief/assets.py +32 -0
- cryptochief/chains.py +115 -0
- cryptochief/client.py +189 -0
- cryptochief/contract/__init__.py +69 -0
- cryptochief/contract/base58.py +40 -0
- cryptochief/contract/borsh.py +141 -0
- cryptochief/contract/evm_abi.py +321 -0
- cryptochief/contract/keccak.py +16 -0
- cryptochief/contract/tron_address.py +60 -0
- cryptochief/errors.py +111 -0
- cryptochief/pagination.py +32 -0
- cryptochief/poll.py +56 -0
- cryptochief/rsa.py +70 -0
- cryptochief/services/__init__.py +1 -0
- cryptochief/services/base.py +24 -0
- cryptochief/services/blockchain.py +76 -0
- cryptochief/services/currencies.py +55 -0
- cryptochief/services/payins.py +145 -0
- cryptochief/services/payouts.py +175 -0
- cryptochief/services/static_deposits.py +77 -0
- cryptochief/services/sweeps.py +85 -0
- cryptochief/services/transactions.py +470 -0
- cryptochief/services/wallets.py +86 -0
- cryptochief/services/withdrawals.py +51 -0
- cryptochief/sign.py +112 -0
- cryptochief/ton/__init__.py +19 -0
- cryptochief/ton/address.py +109 -0
- cryptochief/ton/messages.py +105 -0
- cryptochief/ton/rpc.py +159 -0
- cryptochief/transport.py +48 -0
- cryptochief/webhook.py +191 -0
- cryptochief_crypto_processing_python-0.1.0.dist-info/METADATA +346 -0
- cryptochief_crypto_processing_python-0.1.0.dist-info/RECORD +39 -0
- cryptochief_crypto_processing_python-0.1.0.dist-info/WHEEL +4 -0
- cryptochief_crypto_processing_python-0.1.0.dist-info/licenses/LICENSE +21 -0
cryptochief/__init__.py
ADDED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""Crypto Chief Python SDK - the official async client for the
|
|
2
|
+
`Crypto Chief <https://crypto-chief.com/processing/>`_ crypto processing API.
|
|
3
|
+
|
|
4
|
+
Accept crypto payments, send single and mass payouts, sign on-chain
|
|
5
|
+
transactions and smart-contract calls, manage wallets, convert fiat to crypto,
|
|
6
|
+
and verify webhooks across Ethereum, Tron, TON, Solana, Bitcoin, XRP and 20+
|
|
7
|
+
more blockchains.
|
|
8
|
+
|
|
9
|
+
>>> import asyncio
|
|
10
|
+
>>> from cryptochief import CryptoChiefClient, Chain
|
|
11
|
+
>>> from cryptochief import EstimatePayoutRequest
|
|
12
|
+
>>>
|
|
13
|
+
>>> async def main():
|
|
14
|
+
... async with CryptoChiefClient(merchant_id="M", api_key="K") as client:
|
|
15
|
+
... est = await client.payouts.estimate(EstimatePayoutRequest(
|
|
16
|
+
... network=Chain.ETH_SEPOLIA, coin="ETH", amount="0.0001",
|
|
17
|
+
... to_address="0x...",
|
|
18
|
+
... ))
|
|
19
|
+
... print(est.amount_to_receive)
|
|
20
|
+
>>> asyncio.run(main()) # doctest: +SKIP
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
from ._version import __version__
|
|
26
|
+
from .amount import InvalidAmountError, base_to_human, human_to_base, nano_ton
|
|
27
|
+
from .assets import Asset, AssetsPolicy
|
|
28
|
+
from .chains import Chain, ChainFamily, chain_family, supports_contract_calls
|
|
29
|
+
from .client import DEFAULT_BASE_URL, VERSION, CryptoChiefClient
|
|
30
|
+
from .contract import (
|
|
31
|
+
BorshValue,
|
|
32
|
+
anchor_discriminator,
|
|
33
|
+
base58_decode,
|
|
34
|
+
base58_encode,
|
|
35
|
+
borsh_bool,
|
|
36
|
+
borsh_bytes,
|
|
37
|
+
borsh_fixed_bytes,
|
|
38
|
+
borsh_i8,
|
|
39
|
+
borsh_i16,
|
|
40
|
+
borsh_i32,
|
|
41
|
+
borsh_i64,
|
|
42
|
+
borsh_option,
|
|
43
|
+
borsh_pubkey,
|
|
44
|
+
borsh_string,
|
|
45
|
+
borsh_struct,
|
|
46
|
+
borsh_u8,
|
|
47
|
+
borsh_u16,
|
|
48
|
+
borsh_u32,
|
|
49
|
+
borsh_u64,
|
|
50
|
+
borsh_u128,
|
|
51
|
+
borsh_vec,
|
|
52
|
+
canonical_signature,
|
|
53
|
+
decode_solana_pubkey,
|
|
54
|
+
encode_anchor_instruction,
|
|
55
|
+
encode_evm_call,
|
|
56
|
+
encode_evm_call_hex,
|
|
57
|
+
evm_selector,
|
|
58
|
+
hex_to_tron,
|
|
59
|
+
keccak_256,
|
|
60
|
+
tron_to_hex,
|
|
61
|
+
)
|
|
62
|
+
from .errors import APIError, CryptoChiefError, ErrorCode, is_api_error, is_retryable
|
|
63
|
+
from .pagination import HistoryMeta, HistoryQuery
|
|
64
|
+
from .poll import PollTimeoutError, wait_for_terminal
|
|
65
|
+
from .rsa import (
|
|
66
|
+
RsaKeyNotConfiguredError,
|
|
67
|
+
decrypt_rsa_oaep,
|
|
68
|
+
load_rsa_private_key_file,
|
|
69
|
+
load_rsa_private_key_pem,
|
|
70
|
+
)
|
|
71
|
+
from .services.blockchain import (
|
|
72
|
+
AvailableContract,
|
|
73
|
+
AvailableContractsResponse,
|
|
74
|
+
BlockchainService,
|
|
75
|
+
TxStatusRow,
|
|
76
|
+
WalletBalanceRow,
|
|
77
|
+
)
|
|
78
|
+
from .services.currencies import ConvertRequest, ConvertResponse, CurrenciesService
|
|
79
|
+
from .services.payins import (
|
|
80
|
+
CoinOption,
|
|
81
|
+
CreatePayInRequest,
|
|
82
|
+
PayIn,
|
|
83
|
+
PayInHistoryResponse,
|
|
84
|
+
PayInMode,
|
|
85
|
+
PayInsService,
|
|
86
|
+
PayInStatus,
|
|
87
|
+
SelectAssetRequest,
|
|
88
|
+
is_payin_terminal,
|
|
89
|
+
)
|
|
90
|
+
from .services.payouts import (
|
|
91
|
+
BatchItemResult,
|
|
92
|
+
BatchPayoutRequest,
|
|
93
|
+
BatchPayoutResponse,
|
|
94
|
+
EstimatePayoutRequest,
|
|
95
|
+
EstimatePayoutResponse,
|
|
96
|
+
ExecutePayoutRequest,
|
|
97
|
+
PayoutFeeInfo,
|
|
98
|
+
PayoutHistoryResponse,
|
|
99
|
+
PayoutInfo,
|
|
100
|
+
PayoutsService,
|
|
101
|
+
PayoutSource,
|
|
102
|
+
PayoutStatus,
|
|
103
|
+
is_payout_terminal,
|
|
104
|
+
)
|
|
105
|
+
from .services.static_deposits import (
|
|
106
|
+
StaticDeposit,
|
|
107
|
+
StaticDepositHistoryQuery,
|
|
108
|
+
StaticDepositHistoryResponse,
|
|
109
|
+
StaticDepositsService,
|
|
110
|
+
StaticDepositStatus,
|
|
111
|
+
)
|
|
112
|
+
from .services.sweeps import (
|
|
113
|
+
ForceSweepResponse,
|
|
114
|
+
Sweep,
|
|
115
|
+
SweepHistoryQuery,
|
|
116
|
+
SweepHistoryResponse,
|
|
117
|
+
SweepMode,
|
|
118
|
+
SweepsService,
|
|
119
|
+
)
|
|
120
|
+
from .services.transactions import (
|
|
121
|
+
AnchorCallRequest,
|
|
122
|
+
ContractCall,
|
|
123
|
+
Erc20TransferRequest,
|
|
124
|
+
EvmCallRequest,
|
|
125
|
+
ExecuteTransactionRequest,
|
|
126
|
+
JettonTransferRequest,
|
|
127
|
+
NftTransferRequest,
|
|
128
|
+
SignTransactionRequest,
|
|
129
|
+
SignTransactionResponse,
|
|
130
|
+
SolanaAccount,
|
|
131
|
+
SolanaCallRequest,
|
|
132
|
+
TonCallRequest,
|
|
133
|
+
TonCommentRequest,
|
|
134
|
+
TransactionHistoryResponse,
|
|
135
|
+
TransactionInfo,
|
|
136
|
+
TransactionsService,
|
|
137
|
+
TxStatus,
|
|
138
|
+
TxType,
|
|
139
|
+
is_transaction_terminal,
|
|
140
|
+
)
|
|
141
|
+
from .services.wallets import (
|
|
142
|
+
GenerateWalletRequest,
|
|
143
|
+
ListWalletsResponse,
|
|
144
|
+
Wallet,
|
|
145
|
+
WalletCoinBalance,
|
|
146
|
+
WalletsService,
|
|
147
|
+
WalletType,
|
|
148
|
+
)
|
|
149
|
+
from .services.withdrawals import Withdrawal, WithdrawalHistoryResponse, WithdrawalsService
|
|
150
|
+
from .sign import canonical_json, sign, sign_value
|
|
151
|
+
from .ton import (
|
|
152
|
+
TonAddress,
|
|
153
|
+
crc16_xmodem,
|
|
154
|
+
parse_ton_address,
|
|
155
|
+
ton_address_to_raw,
|
|
156
|
+
ton_address_to_string,
|
|
157
|
+
)
|
|
158
|
+
from .webhook import (
|
|
159
|
+
WEBHOOK_HEADER,
|
|
160
|
+
WEBHOOK_SENDER_IPS,
|
|
161
|
+
PayInWebhookEvent,
|
|
162
|
+
PayoutWebhookEvent,
|
|
163
|
+
StaticDepositWebhookEvent,
|
|
164
|
+
TransactionWebhookEvent,
|
|
165
|
+
WebhookSignatureError,
|
|
166
|
+
coerce_webhook_event,
|
|
167
|
+
parse_webhook_event,
|
|
168
|
+
verify_webhook_signature,
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
__all__ = [
|
|
172
|
+
"__version__",
|
|
173
|
+
# Client
|
|
174
|
+
"CryptoChiefClient",
|
|
175
|
+
"VERSION",
|
|
176
|
+
"DEFAULT_BASE_URL",
|
|
177
|
+
# Errors
|
|
178
|
+
"CryptoChiefError",
|
|
179
|
+
"APIError",
|
|
180
|
+
"ErrorCode",
|
|
181
|
+
"is_api_error",
|
|
182
|
+
"is_retryable",
|
|
183
|
+
# Signing
|
|
184
|
+
"canonical_json",
|
|
185
|
+
"sign",
|
|
186
|
+
"sign_value",
|
|
187
|
+
# Amounts
|
|
188
|
+
"human_to_base",
|
|
189
|
+
"base_to_human",
|
|
190
|
+
"nano_ton",
|
|
191
|
+
"InvalidAmountError",
|
|
192
|
+
# Chains / assets / pagination
|
|
193
|
+
"Chain",
|
|
194
|
+
"ChainFamily",
|
|
195
|
+
"chain_family",
|
|
196
|
+
"supports_contract_calls",
|
|
197
|
+
"Asset",
|
|
198
|
+
"AssetsPolicy",
|
|
199
|
+
"HistoryQuery",
|
|
200
|
+
"HistoryMeta",
|
|
201
|
+
# Polling
|
|
202
|
+
"wait_for_terminal",
|
|
203
|
+
"PollTimeoutError",
|
|
204
|
+
# RSA
|
|
205
|
+
"load_rsa_private_key_pem",
|
|
206
|
+
"load_rsa_private_key_file",
|
|
207
|
+
"decrypt_rsa_oaep",
|
|
208
|
+
"RsaKeyNotConfiguredError",
|
|
209
|
+
# Webhooks
|
|
210
|
+
"verify_webhook_signature",
|
|
211
|
+
"parse_webhook_event",
|
|
212
|
+
"coerce_webhook_event",
|
|
213
|
+
"WebhookSignatureError",
|
|
214
|
+
"WEBHOOK_HEADER",
|
|
215
|
+
"WEBHOOK_SENDER_IPS",
|
|
216
|
+
"PayoutWebhookEvent",
|
|
217
|
+
"TransactionWebhookEvent",
|
|
218
|
+
"PayInWebhookEvent",
|
|
219
|
+
"StaticDepositWebhookEvent",
|
|
220
|
+
# Services
|
|
221
|
+
"PayoutsService",
|
|
222
|
+
"TransactionsService",
|
|
223
|
+
"PayInsService",
|
|
224
|
+
"WalletsService",
|
|
225
|
+
"SweepsService",
|
|
226
|
+
"WithdrawalsService",
|
|
227
|
+
"StaticDepositsService",
|
|
228
|
+
"BlockchainService",
|
|
229
|
+
"CurrenciesService",
|
|
230
|
+
# Payout types
|
|
231
|
+
"EstimatePayoutRequest",
|
|
232
|
+
"ExecutePayoutRequest",
|
|
233
|
+
"EstimatePayoutResponse",
|
|
234
|
+
"PayoutInfo",
|
|
235
|
+
"PayoutFeeInfo",
|
|
236
|
+
"PayoutSource",
|
|
237
|
+
"PayoutHistoryResponse",
|
|
238
|
+
"BatchPayoutRequest",
|
|
239
|
+
"BatchPayoutResponse",
|
|
240
|
+
"BatchItemResult",
|
|
241
|
+
"PayoutStatus",
|
|
242
|
+
"is_payout_terminal",
|
|
243
|
+
# Transaction types
|
|
244
|
+
"SignTransactionRequest",
|
|
245
|
+
"SignTransactionResponse",
|
|
246
|
+
"ExecuteTransactionRequest",
|
|
247
|
+
"TransactionInfo",
|
|
248
|
+
"TransactionHistoryResponse",
|
|
249
|
+
"ContractCall",
|
|
250
|
+
"SolanaAccount",
|
|
251
|
+
"EvmCallRequest",
|
|
252
|
+
"Erc20TransferRequest",
|
|
253
|
+
"AnchorCallRequest",
|
|
254
|
+
"SolanaCallRequest",
|
|
255
|
+
"TonCallRequest",
|
|
256
|
+
"JettonTransferRequest",
|
|
257
|
+
"NftTransferRequest",
|
|
258
|
+
"TonCommentRequest",
|
|
259
|
+
"TxType",
|
|
260
|
+
"TxStatus",
|
|
261
|
+
"is_transaction_terminal",
|
|
262
|
+
# Pay-in types
|
|
263
|
+
"CreatePayInRequest",
|
|
264
|
+
"SelectAssetRequest",
|
|
265
|
+
"PayIn",
|
|
266
|
+
"CoinOption",
|
|
267
|
+
"PayInHistoryResponse",
|
|
268
|
+
"PayInMode",
|
|
269
|
+
"PayInStatus",
|
|
270
|
+
"is_payin_terminal",
|
|
271
|
+
# Wallet types
|
|
272
|
+
"GenerateWalletRequest",
|
|
273
|
+
"Wallet",
|
|
274
|
+
"WalletCoinBalance",
|
|
275
|
+
"ListWalletsResponse",
|
|
276
|
+
"WalletType",
|
|
277
|
+
# Sweep types
|
|
278
|
+
"Sweep",
|
|
279
|
+
"SweepHistoryQuery",
|
|
280
|
+
"SweepHistoryResponse",
|
|
281
|
+
"ForceSweepResponse",
|
|
282
|
+
"SweepMode",
|
|
283
|
+
# Withdrawal types
|
|
284
|
+
"Withdrawal",
|
|
285
|
+
"WithdrawalHistoryResponse",
|
|
286
|
+
# Static deposit types
|
|
287
|
+
"StaticDeposit",
|
|
288
|
+
"StaticDepositHistoryQuery",
|
|
289
|
+
"StaticDepositHistoryResponse",
|
|
290
|
+
"StaticDepositStatus",
|
|
291
|
+
# Blockchain types
|
|
292
|
+
"AvailableContract",
|
|
293
|
+
"AvailableContractsResponse",
|
|
294
|
+
"WalletBalanceRow",
|
|
295
|
+
"TxStatusRow",
|
|
296
|
+
# Currency types
|
|
297
|
+
"ConvertRequest",
|
|
298
|
+
"ConvertResponse",
|
|
299
|
+
# Contract encoders
|
|
300
|
+
"encode_evm_call",
|
|
301
|
+
"encode_evm_call_hex",
|
|
302
|
+
"evm_selector",
|
|
303
|
+
"canonical_signature",
|
|
304
|
+
"keccak_256",
|
|
305
|
+
"BorshValue",
|
|
306
|
+
"borsh_u8",
|
|
307
|
+
"borsh_u16",
|
|
308
|
+
"borsh_u32",
|
|
309
|
+
"borsh_u64",
|
|
310
|
+
"borsh_i8",
|
|
311
|
+
"borsh_i16",
|
|
312
|
+
"borsh_i32",
|
|
313
|
+
"borsh_i64",
|
|
314
|
+
"borsh_u128",
|
|
315
|
+
"borsh_bool",
|
|
316
|
+
"borsh_string",
|
|
317
|
+
"borsh_bytes",
|
|
318
|
+
"borsh_fixed_bytes",
|
|
319
|
+
"borsh_pubkey",
|
|
320
|
+
"borsh_option",
|
|
321
|
+
"borsh_vec",
|
|
322
|
+
"borsh_struct",
|
|
323
|
+
"anchor_discriminator",
|
|
324
|
+
"encode_anchor_instruction",
|
|
325
|
+
"decode_solana_pubkey",
|
|
326
|
+
"tron_to_hex",
|
|
327
|
+
"hex_to_tron",
|
|
328
|
+
"base58_encode",
|
|
329
|
+
"base58_decode",
|
|
330
|
+
# TON address utilities (offline)
|
|
331
|
+
"parse_ton_address",
|
|
332
|
+
"ton_address_to_string",
|
|
333
|
+
"ton_address_to_raw",
|
|
334
|
+
"crc16_xmodem",
|
|
335
|
+
"TonAddress",
|
|
336
|
+
]
|
cryptochief/_models.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Dataclass <-> wire helpers.
|
|
2
|
+
|
|
3
|
+
The public API is modeled with dataclasses whose field names already match the
|
|
4
|
+
snake_case wire format, so there is no case conversion to do - requests serialize
|
|
5
|
+
straight to the body and responses parse straight back.
|
|
6
|
+
|
|
7
|
+
:func:`to_payload` turns a request (dataclass / dict / list / enum / scalar) into
|
|
8
|
+
a JSON-ready value, dropping ``None`` fields. :func:`from_dict` builds a typed
|
|
9
|
+
dataclass from a response dict, recursing into nested dataclass and list fields
|
|
10
|
+
and tolerating unknown keys (forward-compatible with new server fields).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import dataclasses
|
|
16
|
+
import enum
|
|
17
|
+
from typing import Any, Mapping, Optional, Type, TypeVar, Union, get_args, get_origin, get_type_hints
|
|
18
|
+
|
|
19
|
+
T = TypeVar("T")
|
|
20
|
+
|
|
21
|
+
_hints_cache: dict[type, dict[str, Any]] = {}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _type_hints(cls: type) -> dict[str, Any]:
|
|
25
|
+
cached = _hints_cache.get(cls)
|
|
26
|
+
if cached is None:
|
|
27
|
+
cached = get_type_hints(cls)
|
|
28
|
+
_hints_cache[cls] = cached
|
|
29
|
+
return cached
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def to_payload(value: Any) -> Any:
|
|
33
|
+
"""Recursively convert a request value to a JSON-ready form, dropping ``None``."""
|
|
34
|
+
if value is None:
|
|
35
|
+
return None
|
|
36
|
+
if isinstance(value, bool):
|
|
37
|
+
return value
|
|
38
|
+
if isinstance(value, enum.Enum):
|
|
39
|
+
return value.value
|
|
40
|
+
if dataclasses.is_dataclass(value) and not isinstance(value, type):
|
|
41
|
+
out: dict[str, Any] = {}
|
|
42
|
+
for f in dataclasses.fields(value):
|
|
43
|
+
v = getattr(value, f.name)
|
|
44
|
+
if v is None:
|
|
45
|
+
continue
|
|
46
|
+
out[f.name] = to_payload(v)
|
|
47
|
+
return out
|
|
48
|
+
if isinstance(value, dict):
|
|
49
|
+
return {k: to_payload(v) for k, v in value.items() if v is not None}
|
|
50
|
+
if isinstance(value, (list, tuple)):
|
|
51
|
+
return [to_payload(v) for v in value]
|
|
52
|
+
return value
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _unwrap_optional(tp: Any) -> Any:
|
|
56
|
+
if get_origin(tp) is Union:
|
|
57
|
+
args = [a for a in get_args(tp) if a is not type(None)]
|
|
58
|
+
if len(args) == 1:
|
|
59
|
+
return args[0]
|
|
60
|
+
return args[0] if args else Any
|
|
61
|
+
return tp
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _coerce(tp: Any, value: Any) -> Any:
|
|
65
|
+
if value is None:
|
|
66
|
+
return None
|
|
67
|
+
tp = _unwrap_optional(tp)
|
|
68
|
+
origin = get_origin(tp)
|
|
69
|
+
if origin in (list, tuple):
|
|
70
|
+
args = get_args(tp)
|
|
71
|
+
elem = args[0] if args else Any
|
|
72
|
+
return [_coerce(elem, v) for v in value]
|
|
73
|
+
if isinstance(tp, type) and dataclasses.is_dataclass(tp):
|
|
74
|
+
return from_dict(tp, value)
|
|
75
|
+
return value
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def from_dict(cls: Type[T], data: Optional[Mapping[str, Any]]) -> T:
|
|
79
|
+
"""Build a dataclass of type ``cls`` from a response mapping.
|
|
80
|
+
|
|
81
|
+
Recurses into nested dataclass / list fields and ignores keys the dataclass
|
|
82
|
+
does not declare (forward-compatible with new server fields). A ``None`` or
|
|
83
|
+
empty body yields an all-defaults instance - every response model is
|
|
84
|
+
constructible with no arguments.
|
|
85
|
+
"""
|
|
86
|
+
if not isinstance(data, Mapping):
|
|
87
|
+
data = {}
|
|
88
|
+
hints = _type_hints(cls)
|
|
89
|
+
kwargs: dict[str, Any] = {}
|
|
90
|
+
for f in dataclasses.fields(cls): # type: ignore[arg-type] # cls is a dataclass type
|
|
91
|
+
if f.name in data:
|
|
92
|
+
kwargs[f.name] = _coerce(hints.get(f.name, Any), data[f.name])
|
|
93
|
+
return cls(**kwargs)
|
cryptochief/_version.py
ADDED
cryptochief/amount.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""Amount conversion helpers backed by Python's arbitrary-precision ``int``.
|
|
2
|
+
|
|
3
|
+
**Never use ``float`` for crypto amounts**: ``0.1 + 0.2 != 0.3`` and large token
|
|
4
|
+
values lose precision. These helpers parse decimal strings exactly and return
|
|
5
|
+
``int`` base units.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from .errors import CryptoChiefError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class InvalidAmountError(CryptoChiefError):
|
|
14
|
+
"""Raised by :func:`human_to_base` when its input is not a plain decimal."""
|
|
15
|
+
|
|
16
|
+
def __init__(self, message: str) -> None:
|
|
17
|
+
super().__init__(f"cryptochief: invalid amount: {message}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _is_ascii_digits(s: str) -> bool:
|
|
21
|
+
return len(s) > 0 and all("0" <= c <= "9" for c in s)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def human_to_base(human: str, decimals: int) -> int:
|
|
25
|
+
"""Convert a decimal human amount (e.g. ``"0.0001"``) to its base-unit ``int``.
|
|
26
|
+
|
|
27
|
+
Precise to the last digit. Negative amounts and scientific notation are
|
|
28
|
+
rejected. Sub-base-unit precision is truncated, since it is meaningless
|
|
29
|
+
on-chain.
|
|
30
|
+
|
|
31
|
+
>>> human_to_base("1.5", 18)
|
|
32
|
+
1500000000000000000
|
|
33
|
+
>>> human_to_base("0.0001", 8)
|
|
34
|
+
10000
|
|
35
|
+
"""
|
|
36
|
+
s = human.strip()
|
|
37
|
+
if s == "":
|
|
38
|
+
raise InvalidAmountError("empty")
|
|
39
|
+
if not isinstance(decimals, int) or isinstance(decimals, bool) or decimals < 0:
|
|
40
|
+
raise InvalidAmountError(f"negative or non-integer decimals {decimals!r}")
|
|
41
|
+
if "e" in s or "E" in s:
|
|
42
|
+
raise InvalidAmountError(f"scientific notation not allowed: {human!r}")
|
|
43
|
+
if s.startswith("-"):
|
|
44
|
+
raise InvalidAmountError(f"negative not allowed: {human!r}")
|
|
45
|
+
|
|
46
|
+
dot = s.find(".")
|
|
47
|
+
if dot < 0:
|
|
48
|
+
if not _is_ascii_digits(s):
|
|
49
|
+
raise InvalidAmountError(repr(human))
|
|
50
|
+
int_part, frac_part = s, ""
|
|
51
|
+
else:
|
|
52
|
+
int_part = s[:dot] or "0"
|
|
53
|
+
frac_part = s[dot + 1 :]
|
|
54
|
+
if frac_part == "":
|
|
55
|
+
raise InvalidAmountError(repr(human))
|
|
56
|
+
if not _is_ascii_digits(int_part) or not _is_ascii_digits(frac_part):
|
|
57
|
+
raise InvalidAmountError(repr(human))
|
|
58
|
+
|
|
59
|
+
if len(frac_part) > decimals:
|
|
60
|
+
frac_part = frac_part[:decimals]
|
|
61
|
+
else:
|
|
62
|
+
frac_part = frac_part.ljust(decimals, "0")
|
|
63
|
+
|
|
64
|
+
combined = (int_part + frac_part).lstrip("0") or "0"
|
|
65
|
+
return int(combined)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def base_to_human(base: int, decimals: int) -> str:
|
|
69
|
+
"""Inverse of :func:`human_to_base`: a base-unit ``int`` to a decimal string.
|
|
70
|
+
|
|
71
|
+
Trailing zeroes are trimmed.
|
|
72
|
+
|
|
73
|
+
>>> base_to_human(1500000000000000000, 18)
|
|
74
|
+
'1.5'
|
|
75
|
+
>>> base_to_human(0, 18)
|
|
76
|
+
'0'
|
|
77
|
+
"""
|
|
78
|
+
if decimals < 0:
|
|
79
|
+
decimals = 0
|
|
80
|
+
abs_s = str(-base if base < 0 else base)
|
|
81
|
+
if decimals == 0:
|
|
82
|
+
return abs_s
|
|
83
|
+
if len(abs_s) <= decimals:
|
|
84
|
+
abs_s = "0" * (decimals - len(abs_s) + 1) + abs_s
|
|
85
|
+
cut = len(abs_s) - decimals
|
|
86
|
+
int_part = abs_s[:cut]
|
|
87
|
+
frac_part = abs_s[cut:].rstrip("0")
|
|
88
|
+
return int_part if frac_part == "" else f"{int_part}.{frac_part}"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def nano_ton(human: str) -> int:
|
|
92
|
+
"""Convert a human TON amount (``"0.05"``) into base-unit nanoTON (``50000000``).
|
|
93
|
+
|
|
94
|
+
Equivalent to ``human_to_base(human, 9)`` - the form the TON helpers'
|
|
95
|
+
``attached_ton`` / ``forward_ton_amount`` fields expect.
|
|
96
|
+
"""
|
|
97
|
+
return human_to_base(human, 9)
|
cryptochief/assets.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Asset selection policies used by payouts and FIAT-mode pay-ins."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import List, Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(kw_only=True)
|
|
10
|
+
class Asset:
|
|
11
|
+
"""A specific coin on a specific network.
|
|
12
|
+
|
|
13
|
+
``network`` takes a chain code (e.g. ``Chain.ETH_MAINNET``) or the wildcard
|
|
14
|
+
``"ANY"``; ``coin`` is the symbol (e.g. ``"USDT"``). Either field may be
|
|
15
|
+
omitted to mean "any".
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
network: Optional[str] = None
|
|
19
|
+
coin: Optional[str] = None
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass(kw_only=True)
|
|
23
|
+
class AssetsPolicy:
|
|
24
|
+
"""An allow / exclude filter over :class:`Asset` entries.
|
|
25
|
+
|
|
26
|
+
Omitting both lists means "no restriction". Used for payout auto-convert
|
|
27
|
+
source selection and to restrict which coins a FIAT-mode pay-in customer may
|
|
28
|
+
pick.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
allow: Optional[List[Asset]] = None
|
|
32
|
+
exclude: Optional[List[Asset]] = None
|
cryptochief/chains.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"""Chain codes and protocol families.
|
|
2
|
+
|
|
3
|
+
:class:`Chain` is the value of the ``network`` / ``chain`` / ``network_code``
|
|
4
|
+
fields across the API; :class:`ChainFamily` (the ``chain_family`` field) groups
|
|
5
|
+
chains by underlying protocol and drives capability checks such as "does this
|
|
6
|
+
chain accept contract calls?".
|
|
7
|
+
|
|
8
|
+
Both are ``str`` enums: a member compares equal to its wire string, and any
|
|
9
|
+
plain string is accepted wherever a chain is expected, so new chains work before
|
|
10
|
+
this SDK is updated.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from enum import Enum
|
|
16
|
+
from typing import Optional
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Chain(str, Enum):
|
|
20
|
+
"""Chain codes the API currently supports."""
|
|
21
|
+
|
|
22
|
+
ETH_MAINNET = "ETH_MAINNET"
|
|
23
|
+
ETH_SEPOLIA = "ETH_SEPOLIA"
|
|
24
|
+
BSC_MAINNET = "BSC_MAINNET"
|
|
25
|
+
BSC_TESTNET = "BSC_TESTNET"
|
|
26
|
+
POLYGON_MAINNET = "POLYGON_MAINNET"
|
|
27
|
+
POLYGON_AMOY = "POLYGON_AMOY"
|
|
28
|
+
ARBITRUM_ONE = "ARBITRUM_ONE"
|
|
29
|
+
ARBITRUM_SEPOLIA = "ARBITRUM_SEPOLIA"
|
|
30
|
+
OPTIMISM_MAINNET = "OPTIMISM_MAINNET"
|
|
31
|
+
OPTIMISM_SEPOLIA = "OPTIMISM_SEPOLIA"
|
|
32
|
+
AVAX_MAINNET = "AVAX_MAINNET"
|
|
33
|
+
AVAX_TESTNET = "AVAX_TESTNET"
|
|
34
|
+
|
|
35
|
+
BTC_MAINNET = "BTC_MAINNET"
|
|
36
|
+
BTC_TESTNET_4 = "BTC_TESTNET_4"
|
|
37
|
+
LITECOIN_MAINNET = "LITECOIN_MAINNET"
|
|
38
|
+
BITCOIN_CASH_MAINNET = "BITCOIN_CASH_MAINNET"
|
|
39
|
+
DOGECOIN_MAINNET = "DOGECOIN_MAINNET"
|
|
40
|
+
|
|
41
|
+
TRON_MAINNET = "TRON_MAINNET"
|
|
42
|
+
TRON_NILE = "TRON_NILE"
|
|
43
|
+
|
|
44
|
+
SOLANA_MAINNET = "SOLANA_MAINNET"
|
|
45
|
+
SOLANA_DEVNET = "SOLANA_DEVNET"
|
|
46
|
+
|
|
47
|
+
TON_MAINNET = "TON_MAINNET"
|
|
48
|
+
TON_TESTNET = "TON_TESTNET"
|
|
49
|
+
|
|
50
|
+
XRP_MAINNET = "XRP_MAINNET"
|
|
51
|
+
XRP_TESTNET = "XRP_TESTNET"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class ChainFamily(str, Enum):
|
|
55
|
+
"""Protocol families (the ``chain_family`` field in API responses)."""
|
|
56
|
+
|
|
57
|
+
EVM = "EVM"
|
|
58
|
+
TRON = "TRON"
|
|
59
|
+
SOLANA = "SOLANA"
|
|
60
|
+
XRP_LEDGER = "XRP_LEDGER"
|
|
61
|
+
TON = "TON"
|
|
62
|
+
BTC_UTXO = "BTC_UTXO"
|
|
63
|
+
BTC_UTXO_TESTNET = "BTC_UTXO_TESTNET"
|
|
64
|
+
DOGECOIN_UTXO = "DOGECOIN_UTXO"
|
|
65
|
+
BTC_CASH_UTXO = "BTC_CASH_UTXO"
|
|
66
|
+
LITECOIN_UTXO = "LITECOIN_UTXO"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
_CHAIN_TO_FAMILY = {
|
|
70
|
+
Chain.ETH_MAINNET: ChainFamily.EVM,
|
|
71
|
+
Chain.ETH_SEPOLIA: ChainFamily.EVM,
|
|
72
|
+
Chain.BSC_MAINNET: ChainFamily.EVM,
|
|
73
|
+
Chain.BSC_TESTNET: ChainFamily.EVM,
|
|
74
|
+
Chain.POLYGON_MAINNET: ChainFamily.EVM,
|
|
75
|
+
Chain.POLYGON_AMOY: ChainFamily.EVM,
|
|
76
|
+
Chain.ARBITRUM_ONE: ChainFamily.EVM,
|
|
77
|
+
Chain.ARBITRUM_SEPOLIA: ChainFamily.EVM,
|
|
78
|
+
Chain.OPTIMISM_MAINNET: ChainFamily.EVM,
|
|
79
|
+
Chain.OPTIMISM_SEPOLIA: ChainFamily.EVM,
|
|
80
|
+
Chain.AVAX_MAINNET: ChainFamily.EVM,
|
|
81
|
+
Chain.AVAX_TESTNET: ChainFamily.EVM,
|
|
82
|
+
Chain.BTC_MAINNET: ChainFamily.BTC_UTXO,
|
|
83
|
+
Chain.BTC_TESTNET_4: ChainFamily.BTC_UTXO_TESTNET,
|
|
84
|
+
Chain.LITECOIN_MAINNET: ChainFamily.LITECOIN_UTXO,
|
|
85
|
+
Chain.BITCOIN_CASH_MAINNET: ChainFamily.BTC_CASH_UTXO,
|
|
86
|
+
Chain.DOGECOIN_MAINNET: ChainFamily.DOGECOIN_UTXO,
|
|
87
|
+
Chain.TRON_MAINNET: ChainFamily.TRON,
|
|
88
|
+
Chain.TRON_NILE: ChainFamily.TRON,
|
|
89
|
+
Chain.SOLANA_MAINNET: ChainFamily.SOLANA,
|
|
90
|
+
Chain.SOLANA_DEVNET: ChainFamily.SOLANA,
|
|
91
|
+
Chain.TON_MAINNET: ChainFamily.TON,
|
|
92
|
+
Chain.TON_TESTNET: ChainFamily.TON,
|
|
93
|
+
Chain.XRP_MAINNET: ChainFamily.XRP_LEDGER,
|
|
94
|
+
Chain.XRP_TESTNET: ChainFamily.XRP_LEDGER,
|
|
95
|
+
}
|
|
96
|
+
# Allow plain-string lookups too (e.g. the value off a response).
|
|
97
|
+
_CHAIN_TO_FAMILY_STR = {k.value: v for k, v in _CHAIN_TO_FAMILY.items()}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def chain_family(chain: str) -> Optional[ChainFamily]:
|
|
101
|
+
"""Return the protocol family for a chain, or ``None`` if unrecognized."""
|
|
102
|
+
return _CHAIN_TO_FAMILY_STR.get(str(chain))
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def supports_contract_calls(family: str) -> bool:
|
|
106
|
+
"""Whether a chain family accepts the ``contract`` transaction type.
|
|
107
|
+
|
|
108
|
+
Only EVM, TRON, Solana, and TON do.
|
|
109
|
+
"""
|
|
110
|
+
return family in (
|
|
111
|
+
ChainFamily.EVM,
|
|
112
|
+
ChainFamily.TRON,
|
|
113
|
+
ChainFamily.SOLANA,
|
|
114
|
+
ChainFamily.TON,
|
|
115
|
+
)
|