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.
Files changed (39) hide show
  1. cryptochief/__init__.py +336 -0
  2. cryptochief/_models.py +93 -0
  3. cryptochief/_version.py +3 -0
  4. cryptochief/amount.py +97 -0
  5. cryptochief/assets.py +32 -0
  6. cryptochief/chains.py +115 -0
  7. cryptochief/client.py +189 -0
  8. cryptochief/contract/__init__.py +69 -0
  9. cryptochief/contract/base58.py +40 -0
  10. cryptochief/contract/borsh.py +141 -0
  11. cryptochief/contract/evm_abi.py +321 -0
  12. cryptochief/contract/keccak.py +16 -0
  13. cryptochief/contract/tron_address.py +60 -0
  14. cryptochief/errors.py +111 -0
  15. cryptochief/pagination.py +32 -0
  16. cryptochief/poll.py +56 -0
  17. cryptochief/rsa.py +70 -0
  18. cryptochief/services/__init__.py +1 -0
  19. cryptochief/services/base.py +24 -0
  20. cryptochief/services/blockchain.py +76 -0
  21. cryptochief/services/currencies.py +55 -0
  22. cryptochief/services/payins.py +145 -0
  23. cryptochief/services/payouts.py +175 -0
  24. cryptochief/services/static_deposits.py +77 -0
  25. cryptochief/services/sweeps.py +85 -0
  26. cryptochief/services/transactions.py +470 -0
  27. cryptochief/services/wallets.py +86 -0
  28. cryptochief/services/withdrawals.py +51 -0
  29. cryptochief/sign.py +112 -0
  30. cryptochief/ton/__init__.py +19 -0
  31. cryptochief/ton/address.py +109 -0
  32. cryptochief/ton/messages.py +105 -0
  33. cryptochief/ton/rpc.py +159 -0
  34. cryptochief/transport.py +48 -0
  35. cryptochief/webhook.py +191 -0
  36. cryptochief_crypto_processing_python-0.1.0.dist-info/METADATA +346 -0
  37. cryptochief_crypto_processing_python-0.1.0.dist-info/RECORD +39 -0
  38. cryptochief_crypto_processing_python-0.1.0.dist-info/WHEEL +4 -0
  39. cryptochief_crypto_processing_python-0.1.0.dist-info/licenses/LICENSE +21 -0
cryptochief/client.py ADDED
@@ -0,0 +1,189 @@
1
+ """The asynchronous Crypto Chief client and its low-level signed transport."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import json
7
+ from typing import Any, Mapping, Optional, Union
8
+
9
+ import httpx
10
+
11
+ from ._version import __version__
12
+ from .errors import CryptoChiefError, is_retryable
13
+ from .rsa import RsaKeyNotConfiguredError, decrypt_rsa_oaep, load_rsa_private_key_pem
14
+ from .services.blockchain import BlockchainService
15
+ from .services.currencies import CurrenciesService
16
+ from .services.payins import PayInsService
17
+ from .services.payouts import PayoutsService
18
+ from .services.static_deposits import StaticDepositsService
19
+ from .services.sweeps import SweepsService
20
+ from .services.transactions import TransactionsService
21
+ from .services.wallets import WalletsService
22
+ from .services.withdrawals import WithdrawalsService
23
+ from .sign import sign_value
24
+ from .ton.rpc import TonRpc
25
+ from .transport import backoff_delay, network_error, parse_api_error
26
+
27
+ #: SDK version, reported in the default ``User-Agent``.
28
+ VERSION = __version__
29
+
30
+ #: Production processing API endpoint. Test-mode projects share this host.
31
+ DEFAULT_BASE_URL = "https://api-processing.crypto-chief.com"
32
+
33
+ _MAX_RAW_IN_ERROR = 512
34
+
35
+
36
+ class CryptoChiefClient:
37
+ """Entry point to the Crypto Chief processing API.
38
+
39
+ Construct once and reuse - the client is stateless beyond its configuration.
40
+ It owns an :class:`httpx.AsyncClient`, so close it when done (or use it as an
41
+ async context manager)::
42
+
43
+ async with CryptoChiefClient(merchant_id="M", api_key="K") as client:
44
+ est = await client.payouts.estimate(EstimatePayoutRequest(
45
+ network=Chain.ETH_SEPOLIA, coin="ETH", amount="0.0001",
46
+ to_address="0x...",
47
+ ))
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ *,
53
+ merchant_id: str,
54
+ api_key: str,
55
+ base_url: str = DEFAULT_BASE_URL,
56
+ timeout: float = 60.0,
57
+ retries: int = 3,
58
+ retry_backoff: Optional[Mapping[str, float]] = None,
59
+ user_agent: Optional[str] = None,
60
+ http_client: Optional[httpx.AsyncClient] = None,
61
+ transport: Optional[httpx.AsyncBaseTransport] = None,
62
+ rsa_private_key: Optional[Union[str, bytes, Any]] = None,
63
+ ton_rpc_base_url: Optional[str] = None,
64
+ ) -> None:
65
+ if not merchant_id:
66
+ raise CryptoChiefError("cryptochief: merchant_id is required")
67
+ if not api_key:
68
+ raise CryptoChiefError("cryptochief: api_key is required")
69
+
70
+ self.merchant_id = merchant_id
71
+ self._api_key = api_key
72
+ self.base_url = base_url.rstrip("/")
73
+ self._timeout = timeout
74
+ self._retries = retries
75
+ backoff = retry_backoff or {}
76
+ self._base_ms = backoff.get("base_ms", 200)
77
+ self._max_ms = backoff.get("max_ms", 5000)
78
+ self._user_agent = user_agent or f"cryptochief-python/{VERSION}"
79
+
80
+ self._owns_http = http_client is None
81
+ self._http = http_client or httpx.AsyncClient(timeout=timeout, transport=transport)
82
+
83
+ self._rsa_input = rsa_private_key
84
+ self._rsa_key: Any = None
85
+ self._rsa_error: Optional[CryptoChiefError] = None
86
+
87
+ self._ton_rpc_base_url = ton_rpc_base_url
88
+ self._ton_rpc: Optional[TonRpc] = None
89
+
90
+ self.payouts = PayoutsService(self)
91
+ self.transactions = TransactionsService(self)
92
+ self.pay_ins = PayInsService(self)
93
+ self.wallets = WalletsService(self)
94
+ self.sweeps = SweepsService(self)
95
+ self.withdrawals = WithdrawalsService(self)
96
+ self.static_deposits = StaticDepositsService(self)
97
+ self.blockchain = BlockchainService(self)
98
+ self.currencies = CurrenciesService(self)
99
+
100
+ async def request(self, path: str, body: Any = None) -> Any:
101
+ """Low-level signed POST against an API path (e.g. ``/v1/payout/estimate``).
102
+
103
+ Canonicalizes + signs the body, sends it, retries transient failures, and
104
+ returns the parsed JSON. Service methods are thin wrappers over this;
105
+ reach for it directly only to hit an endpoint the SDK doesn't model yet.
106
+ """
107
+ canonical, signature = sign_value(body, self._api_key)
108
+ url = self.base_url + path
109
+ headers = {
110
+ "Content-Type": "application/json",
111
+ "Accept": "application/json",
112
+ "Merchant": self.merchant_id,
113
+ "Signature": signature,
114
+ "User-Agent": self._user_agent,
115
+ }
116
+ body_bytes = canonical.encode("utf-8")
117
+ attempts = self._retries + 1
118
+ last_err: Optional[Exception] = None
119
+
120
+ for attempt in range(attempts):
121
+ if attempt > 0:
122
+ await asyncio.sleep(backoff_delay(attempt, self._base_ms, self._max_ms))
123
+ try:
124
+ resp = await self._http.post(url, content=body_bytes, headers=headers)
125
+ except httpx.HTTPError as err:
126
+ last_err = network_error(str(err))
127
+ if not is_retryable(last_err):
128
+ raise last_err
129
+ continue
130
+
131
+ text = resp.text
132
+ status = resp.status_code
133
+ if 200 <= status < 300:
134
+ if not text:
135
+ return None
136
+ try:
137
+ return json.loads(text)
138
+ except ValueError as err:
139
+ raise CryptoChiefError(
140
+ f"cryptochief: decode {path} response: {err} "
141
+ f"(raw={text[:_MAX_RAW_IN_ERROR]})"
142
+ ) from err
143
+
144
+ api_err = parse_api_error(status, text)
145
+ if status >= 500:
146
+ last_err = api_err
147
+ continue
148
+ raise api_err
149
+
150
+ raise last_err or CryptoChiefError("cryptochief: retry budget exhausted")
151
+
152
+ async def aclose(self) -> None:
153
+ """Close the underlying HTTP client (only if this client created it)."""
154
+ if self._owns_http:
155
+ await self._http.aclose()
156
+
157
+ async def __aenter__(self) -> "CryptoChiefClient":
158
+ return self
159
+
160
+ async def __aexit__(self, *exc: object) -> None:
161
+ await self.aclose()
162
+
163
+ def rsa_decrypt(self, encrypted: str) -> str:
164
+ """Decrypt a wallet ``private_key_encrypted`` field. Used by ``wallets``."""
165
+ if self._rsa_error:
166
+ raise self._rsa_error
167
+ if self._rsa_key is None:
168
+ if self._rsa_input is None:
169
+ raise RsaKeyNotConfiguredError()
170
+ try:
171
+ if isinstance(self._rsa_input, (str, bytes, bytearray)):
172
+ self._rsa_key = load_rsa_private_key_pem(self._rsa_input)
173
+ else:
174
+ self._rsa_key = self._rsa_input # already a private-key object
175
+ except CryptoChiefError as err:
176
+ self._rsa_error = err
177
+ raise
178
+ return decrypt_rsa_oaep(self._rsa_key, encrypted)
179
+
180
+ def ton_rpc(self) -> TonRpc:
181
+ """Lazily built TON RPC helper, sharing the merchant credential + HTTP client."""
182
+ if self._ton_rpc is None:
183
+ self._ton_rpc = TonRpc(
184
+ merchant_id=self.merchant_id,
185
+ http=self._http,
186
+ base_url=self._ton_rpc_base_url,
187
+ user_agent=self._user_agent,
188
+ )
189
+ return self._ton_rpc
@@ -0,0 +1,69 @@
1
+ """Contract-call encoders: EVM/TRON ABI, Solana Borsh/Anchor, base58, TRON addresses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from .base58 import base58_decode, base58_encode
6
+ from .borsh import (
7
+ BorshValue,
8
+ anchor_discriminator,
9
+ borsh_bool,
10
+ borsh_bytes,
11
+ borsh_fixed_bytes,
12
+ borsh_i8,
13
+ borsh_i16,
14
+ borsh_i32,
15
+ borsh_i64,
16
+ borsh_option,
17
+ borsh_pubkey,
18
+ borsh_string,
19
+ borsh_struct,
20
+ borsh_u8,
21
+ borsh_u16,
22
+ borsh_u32,
23
+ borsh_u64,
24
+ borsh_u128,
25
+ borsh_vec,
26
+ decode_solana_pubkey,
27
+ encode_anchor_instruction,
28
+ )
29
+ from .evm_abi import (
30
+ canonical_signature,
31
+ encode_evm_call,
32
+ encode_evm_call_hex,
33
+ evm_selector,
34
+ )
35
+ from .keccak import keccak_256
36
+ from .tron_address import hex_to_tron, tron_to_hex
37
+
38
+ __all__ = [
39
+ "base58_decode",
40
+ "base58_encode",
41
+ "BorshValue",
42
+ "anchor_discriminator",
43
+ "borsh_bool",
44
+ "borsh_bytes",
45
+ "borsh_fixed_bytes",
46
+ "borsh_i8",
47
+ "borsh_i16",
48
+ "borsh_i32",
49
+ "borsh_i64",
50
+ "borsh_option",
51
+ "borsh_pubkey",
52
+ "borsh_string",
53
+ "borsh_struct",
54
+ "borsh_u8",
55
+ "borsh_u16",
56
+ "borsh_u32",
57
+ "borsh_u64",
58
+ "borsh_u128",
59
+ "borsh_vec",
60
+ "decode_solana_pubkey",
61
+ "encode_anchor_instruction",
62
+ "canonical_signature",
63
+ "encode_evm_call",
64
+ "encode_evm_call_hex",
65
+ "evm_selector",
66
+ "keccak_256",
67
+ "hex_to_tron",
68
+ "tron_to_hex",
69
+ ]
@@ -0,0 +1,40 @@
1
+ """Base58 (Bitcoin / Tron / Solana alphabet).
2
+
3
+ Shared by the TRON address codec and Solana pubkey decoding. Pure ``int``
4
+ arithmetic - no external dependency.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from ..errors import CryptoChiefError
10
+
11
+ _ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
12
+ _DECODE = {c: i for i, c in enumerate(_ALPHABET)}
13
+
14
+
15
+ def base58_encode(data: bytes) -> str:
16
+ zeros = 0
17
+ while zeros < len(data) and data[zeros] == 0:
18
+ zeros += 1
19
+ num = int.from_bytes(data, "big")
20
+ out = ""
21
+ while num > 0:
22
+ num, rem = divmod(num, 58)
23
+ out = _ALPHABET[rem] + out
24
+ return _ALPHABET[0] * zeros + out
25
+
26
+
27
+ def base58_decode(s: str) -> bytes:
28
+ if s == "":
29
+ raise CryptoChiefError("cryptochief: base58: empty input")
30
+ zeros = 0
31
+ while zeros < len(s) and s[zeros] == _ALPHABET[0]:
32
+ zeros += 1
33
+ num = 0
34
+ for ch in s:
35
+ v = _DECODE.get(ch, -1)
36
+ if v < 0:
37
+ raise CryptoChiefError(f"cryptochief: base58: invalid char {ch!r}")
38
+ num = num * 58 + v
39
+ body = num.to_bytes((num.bit_length() + 7) // 8, "big") if num > 0 else b""
40
+ return b"\x00" * zeros + body
@@ -0,0 +1,141 @@
1
+ """Borsh encoding + Anchor instruction building for Solana.
2
+
3
+ Anchor instruction data is ``[8-byte discriminator][Borsh-encoded args]``. Borsh
4
+ has no on-wire type tags, so the caller must describe each argument's type
5
+ explicitly - the ``borsh_*`` constructors below force that. Each returns a
6
+ :class:`BorshValue`; pass them to :func:`encode_anchor_instruction`.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import hashlib
12
+ from typing import List, Optional, Union
13
+
14
+ from ..errors import CryptoChiefError
15
+ from .base58 import base58_decode
16
+
17
+
18
+ class BorshError(CryptoChiefError):
19
+ def __init__(self, message: str) -> None:
20
+ super().__init__(f"cryptochief/anchor: {message}")
21
+
22
+
23
+ class BorshValue:
24
+ """A value paired with its Borsh encoding, ready to concatenate."""
25
+
26
+ __slots__ = ("_data",)
27
+
28
+ def __init__(self, data: bytes) -> None:
29
+ self._data = data
30
+
31
+ def encode(self) -> bytes:
32
+ return self._data
33
+
34
+
35
+ def _le(value: int, width: int) -> bytes:
36
+ return (value & ((1 << (8 * width)) - 1)).to_bytes(width, "little")
37
+
38
+
39
+ # Unsigned little-endian integers.
40
+ def borsh_u8(n: int) -> BorshValue:
41
+ return BorshValue(_le(n, 1))
42
+
43
+
44
+ def borsh_u16(n: int) -> BorshValue:
45
+ return BorshValue(_le(n, 2))
46
+
47
+
48
+ def borsh_u32(n: int) -> BorshValue:
49
+ return BorshValue(_le(n, 4))
50
+
51
+
52
+ def borsh_u64(n: int) -> BorshValue:
53
+ return BorshValue(_le(n, 8))
54
+
55
+
56
+ # Signed little-endian integers (two's complement - same wire bytes as unsigned).
57
+ borsh_i8 = borsh_u8
58
+ borsh_i16 = borsh_u16
59
+ borsh_i32 = borsh_u32
60
+ borsh_i64 = borsh_u64
61
+
62
+
63
+ def borsh_u128(n: int) -> BorshValue:
64
+ """128-bit unsigned little-endian. Must be non-negative and < 2^128."""
65
+ if n < 0:
66
+ raise BorshError("u128 negative")
67
+ if n >= (1 << 128):
68
+ raise BorshError("u128 overflow")
69
+ return BorshValue(_le(n, 16))
70
+
71
+
72
+ def borsh_bool(b: bool) -> BorshValue:
73
+ """1-byte boolean (0x00 / 0x01)."""
74
+ return BorshValue(b"\x01" if b else b"\x00")
75
+
76
+
77
+ def borsh_string(s: str) -> BorshValue:
78
+ """UTF-8 string: 4-byte LE length prefix + bytes."""
79
+ data = s.encode("utf-8")
80
+ return BorshValue(_le(len(data), 4) + data)
81
+
82
+
83
+ def borsh_bytes(b: bytes) -> BorshValue:
84
+ """Raw byte slice: 4-byte LE length prefix + bytes (same wire form as a string)."""
85
+ b = bytes(b)
86
+ return BorshValue(_le(len(b), 4) + b)
87
+
88
+
89
+ def borsh_fixed_bytes(b: bytes, n: int) -> BorshValue:
90
+ """Fixed-length bytes with NO length prefix (Anchor's ``[u8; N]``)."""
91
+ b = bytes(b)
92
+ if len(b) != n:
93
+ raise BorshError(f"borsh_fixed_bytes: expected {n} bytes, got {len(b)}")
94
+ return BorshValue(b)
95
+
96
+
97
+ def borsh_pubkey(pk: Union[str, bytes]) -> BorshValue:
98
+ """A Solana 32-byte pubkey (base58 string or raw 32 bytes)."""
99
+ return BorshValue(decode_solana_pubkey(pk))
100
+
101
+
102
+ def borsh_option(inner: Optional[BorshValue]) -> BorshValue:
103
+ """Nullable value: ``None`` -> 0x00; otherwise 0x01 + inner encoding."""
104
+ if inner is None:
105
+ return BorshValue(b"\x00")
106
+ return BorshValue(b"\x01" + inner.encode())
107
+
108
+
109
+ def borsh_vec(items: List[BorshValue]) -> BorshValue:
110
+ """Homogeneous ``Vec<T>``: 4-byte LE length + elements."""
111
+ body = b"".join(it.encode() for it in items)
112
+ return BorshValue(_le(len(items), 4) + body)
113
+
114
+
115
+ def borsh_struct(*fields: BorshValue) -> BorshValue:
116
+ """Heterogeneous struct / tuple: fields in order, no length prefix."""
117
+ return BorshValue(b"".join(f.encode() for f in fields))
118
+
119
+
120
+ def anchor_discriminator(method: str) -> bytes:
121
+ """The 8-byte Anchor instruction discriminator: ``sha256("global:" + method)[:8]``."""
122
+ return hashlib.sha256(f"global:{method}".encode("utf-8")).digest()[:8]
123
+
124
+
125
+ def encode_anchor_instruction(method: str, *args: BorshValue) -> bytes:
126
+ """Raw Anchor instruction data: 8-byte discriminator + Borsh-encoded args."""
127
+ parts = [anchor_discriminator(method)]
128
+ parts.extend(a.encode() for a in args)
129
+ return b"".join(parts)
130
+
131
+
132
+ def decode_solana_pubkey(pk: Union[str, bytes]) -> bytes:
133
+ """Decode a Solana pubkey (base58 string or raw 32 bytes) to its 32-byte form."""
134
+ if isinstance(pk, (bytes, bytearray)):
135
+ if len(pk) != 32:
136
+ raise BorshError(f"solana pubkey: want 32 bytes, got {len(pk)}")
137
+ return bytes(pk)
138
+ raw = base58_decode(pk)
139
+ if len(raw) != 32:
140
+ raise BorshError(f"solana pubkey: decoded length {len(raw)}, want 32")
141
+ return raw