cryptochief-crypto-processing-python 0.1.0__tar.gz → 0.4.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.
Files changed (43) hide show
  1. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/PKG-INFO +38 -4
  2. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/README.md +35 -1
  3. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/pyproject.toml +1 -1
  4. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/__init__.py +23 -0
  5. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/_version.py +1 -1
  6. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/client.py +2 -0
  7. cryptochief_crypto_processing_python-0.4.0/src/cryptochief/sentinels.py +38 -0
  8. cryptochief_crypto_processing_python-0.4.0/src/cryptochief/services/credits.py +70 -0
  9. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/services/payins.py +32 -0
  10. cryptochief_crypto_processing_python-0.4.0/src/cryptochief/services/sweeps.py +282 -0
  11. cryptochief_crypto_processing_python-0.1.0/src/cryptochief/services/sweeps.py +0 -85
  12. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/.gitignore +0 -0
  13. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/LICENSE +0 -0
  14. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/_models.py +0 -0
  15. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/amount.py +0 -0
  16. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/assets.py +0 -0
  17. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/chains.py +0 -0
  18. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/contract/__init__.py +0 -0
  19. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/contract/base58.py +0 -0
  20. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/contract/borsh.py +0 -0
  21. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/contract/evm_abi.py +0 -0
  22. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/contract/keccak.py +0 -0
  23. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/contract/tron_address.py +0 -0
  24. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/errors.py +0 -0
  25. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/pagination.py +0 -0
  26. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/poll.py +0 -0
  27. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/rsa.py +0 -0
  28. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/services/__init__.py +0 -0
  29. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/services/base.py +0 -0
  30. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/services/blockchain.py +0 -0
  31. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/services/currencies.py +0 -0
  32. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/services/payouts.py +0 -0
  33. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/services/static_deposits.py +0 -0
  34. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/services/transactions.py +0 -0
  35. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/services/wallets.py +0 -0
  36. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/services/withdrawals.py +0 -0
  37. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/sign.py +0 -0
  38. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/ton/__init__.py +0 -0
  39. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/ton/address.py +0 -0
  40. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/ton/messages.py +0 -0
  41. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/ton/rpc.py +0 -0
  42. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/transport.py +0 -0
  43. {cryptochief_crypto_processing_python-0.1.0 → cryptochief_crypto_processing_python-0.4.0}/src/cryptochief/webhook.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: cryptochief-crypto-processing-python
3
- Version: 0.1.0
3
+ Version: 0.4.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
@@ -34,7 +34,7 @@ Provides-Extra: dev
34
34
  Requires-Dist: mypy>=1.8; extra == 'dev'
35
35
  Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
36
36
  Requires-Dist: pytest>=8; extra == 'dev'
37
- Requires-Dist: ruff>=0.4; extra == 'dev'
37
+ Requires-Dist: ruff<0.16,>=0.4; extra == 'dev'
38
38
  Description-Content-Type: text/markdown
39
39
 
40
40
  # Crypto Chief Python SDK - Crypto Processing API Client
@@ -121,11 +121,12 @@ Both credentials come from the Dashboard -> Project.
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
123
  | Wallet management + RSA decrypt | `client.wallets` | `generate`, `list`, `info`, `freeze`, `decrypt_private_key` |
124
- | Treasury sweeps | `client.sweeps` | `force`, `history`, `wallet_history` |
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` |
127
127
  | On-chain queries | `client.blockchain` | `contracts_available`, `wallet_balance`, `transaction_status` |
128
128
  | Fiat <-> crypto rate quote | `client.currencies` | `fiat_to_crypto`, `crypto_to_fiat` |
129
+ | Credits (billing) balance check and top-up - free of charge | `client.credits` | `balance`, `topup` |
129
130
 
130
131
  ## Accept a crypto payment (pay-in)
131
132
 
@@ -334,6 +335,39 @@ priv = client.wallets.decrypt_private_key(wallet.private_key_encrypted)
334
335
  - **How do I do a crypto swap?** A swap is a payout with `auto_convert=True`.
335
336
  - **How do I call a smart contract?** `client.transactions.sign_evm_call` /
336
337
  `sign_anchor_call` / `jetton_transfer`, then `transactions.execute`.
338
+ - **How do I control when a deposit wallet is swept?**
339
+ `client.sweeps.settings(...)` reads the policy in force for one wallet and
340
+ `client.sweeps.update_settings(...)` changes it - sweep on arrival
341
+ (`SweepPolicyMode.MOMENTUM`), sweep once the balance reaches an amount
342
+ (`SweepPolicyMode.THRESHOLD` plus `threshold_amount_usd`), or never on its own
343
+ (`SweepPolicyMode.OFF`, force still works). The read comes back in three
344
+ layers - what will happen, what this wallet overrides, and what it inherits
345
+ from the project - so a value of your own is distinguishable from an inherited
346
+ one:
347
+
348
+ ```python
349
+ s = await client.sweeps.update_settings(
350
+ deposit_address,
351
+ type_work=SweepPolicyMode.THRESHOLD,
352
+ threshold_amount_usd="250",
353
+ )
354
+ # s.effective is the resolved policy; s.effective.source names the layer it came from.
355
+ ```
356
+
357
+ Inheritance is per field: overriding the mode leaves the fee mode inherited.
358
+ To stop overriding a field, pass `CLEAR` - `None` already means "leave this
359
+ field alone", so it cannot also mean "reset it".
360
+ - **How do I know a sweep actually settled?** Check `status`.
361
+ `SweepStatus.BROADCASTED` means the transaction is out and not yet confirmed;
362
+ `SweepStatus.COMPLETED` means confirmed, with `sweep_confirmations` and
363
+ `completed_at` filled in. Earlier platform versions reported `completed` at
364
+ broadcast, so a sweep could read as settled while its transaction was still
365
+ unconfirmed.
366
+ - **How do I keep test payments off real chains?** Set `environment` on
367
+ `CreatePayInRequest` to `Environment.TESTNET` or `Environment.MAINNET`. It
368
+ constrains the asset the platform picks when you have not named a concrete
369
+ network - fiat mode and `ANY` - so an unconstrained pick cannot put a real
370
+ payment on a test chain. Omit it to use the project's default.
337
371
 
338
372
  ## Documentation
339
373
 
@@ -82,11 +82,12 @@ Both credentials come from the Dashboard -> Project.
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
84
  | Wallet management + RSA decrypt | `client.wallets` | `generate`, `list`, `info`, `freeze`, `decrypt_private_key` |
85
- | Treasury sweeps | `client.sweeps` | `force`, `history`, `wallet_history` |
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` |
88
88
  | On-chain queries | `client.blockchain` | `contracts_available`, `wallet_balance`, `transaction_status` |
89
89
  | Fiat <-> crypto rate quote | `client.currencies` | `fiat_to_crypto`, `crypto_to_fiat` |
90
+ | Credits (billing) balance check and top-up - free of charge | `client.credits` | `balance`, `topup` |
90
91
 
91
92
  ## Accept a crypto payment (pay-in)
92
93
 
@@ -295,6 +296,39 @@ priv = client.wallets.decrypt_private_key(wallet.private_key_encrypted)
295
296
  - **How do I do a crypto swap?** A swap is a payout with `auto_convert=True`.
296
297
  - **How do I call a smart contract?** `client.transactions.sign_evm_call` /
297
298
  `sign_anchor_call` / `jetton_transfer`, then `transactions.execute`.
299
+ - **How do I control when a deposit wallet is swept?**
300
+ `client.sweeps.settings(...)` reads the policy in force for one wallet and
301
+ `client.sweeps.update_settings(...)` changes it - sweep on arrival
302
+ (`SweepPolicyMode.MOMENTUM`), sweep once the balance reaches an amount
303
+ (`SweepPolicyMode.THRESHOLD` plus `threshold_amount_usd`), or never on its own
304
+ (`SweepPolicyMode.OFF`, force still works). The read comes back in three
305
+ layers - what will happen, what this wallet overrides, and what it inherits
306
+ from the project - so a value of your own is distinguishable from an inherited
307
+ one:
308
+
309
+ ```python
310
+ s = await client.sweeps.update_settings(
311
+ deposit_address,
312
+ type_work=SweepPolicyMode.THRESHOLD,
313
+ threshold_amount_usd="250",
314
+ )
315
+ # s.effective is the resolved policy; s.effective.source names the layer it came from.
316
+ ```
317
+
318
+ Inheritance is per field: overriding the mode leaves the fee mode inherited.
319
+ To stop overriding a field, pass `CLEAR` - `None` already means "leave this
320
+ field alone", so it cannot also mean "reset it".
321
+ - **How do I know a sweep actually settled?** Check `status`.
322
+ `SweepStatus.BROADCASTED` means the transaction is out and not yet confirmed;
323
+ `SweepStatus.COMPLETED` means confirmed, with `sweep_confirmations` and
324
+ `completed_at` filled in. Earlier platform versions reported `completed` at
325
+ broadcast, so a sweep could read as settled while its transaction was still
326
+ unconfirmed.
327
+ - **How do I keep test payments off real chains?** Set `environment` on
328
+ `CreatePayInRequest` to `Environment.TESTNET` or `Environment.MAINNET`. It
329
+ constrains the asset the platform picks when you have not named a concrete
330
+ network - fiat mode and `ANY` - so an unconstrained pick cannot put a real
331
+ payment on a test chain. Omit it to use the project's default.
298
332
 
299
333
  ## Documentation
300
334
 
@@ -82,7 +82,7 @@ dev = [
82
82
  "pytest>=8",
83
83
  "pytest-asyncio>=0.23",
84
84
  "mypy>=1.8",
85
- "ruff>=0.4",
85
+ "ruff>=0.4,<0.16",
86
86
  ]
87
87
 
88
88
  [project.urls]
@@ -75,10 +75,12 @@ from .services.blockchain import (
75
75
  TxStatusRow,
76
76
  WalletBalanceRow,
77
77
  )
78
+ from .services.credits import CreditsBalance, CreditsService, CreditsTopup
78
79
  from .services.currencies import ConvertRequest, ConvertResponse, CurrenciesService
79
80
  from .services.payins import (
80
81
  CoinOption,
81
82
  CreatePayInRequest,
83
+ Environment,
82
84
  PayIn,
83
85
  PayInHistoryResponse,
84
86
  PayInMode,
@@ -109,13 +111,20 @@ from .services.static_deposits import (
109
111
  StaticDepositsService,
110
112
  StaticDepositStatus,
111
113
  )
114
+ from .sentinels import CLEAR, Clear
112
115
  from .services.sweeps import (
113
116
  ForceSweepResponse,
114
117
  Sweep,
118
+ SweepFeeMode,
115
119
  SweepHistoryQuery,
116
120
  SweepHistoryResponse,
117
121
  SweepMode,
122
+ SweepOverride,
123
+ SweepPolicy,
124
+ SweepPolicyMode,
125
+ SweepSettings,
118
126
  SweepsService,
127
+ SweepStatus,
119
128
  )
120
129
  from .services.transactions import (
121
130
  AnchorCallRequest,
@@ -170,6 +179,10 @@ from .webhook import (
170
179
 
171
180
  __all__ = [
172
181
  "__version__",
182
+ # Sentinels
183
+ "CLEAR",
184
+ "Clear",
185
+ "Environment",
173
186
  # Client
174
187
  "CryptoChiefClient",
175
188
  "VERSION",
@@ -227,6 +240,7 @@ __all__ = [
227
240
  "StaticDepositsService",
228
241
  "BlockchainService",
229
242
  "CurrenciesService",
243
+ "CreditsService",
230
244
  # Payout types
231
245
  "EstimatePayoutRequest",
232
246
  "ExecutePayoutRequest",
@@ -276,10 +290,16 @@ __all__ = [
276
290
  "WalletType",
277
291
  # Sweep types
278
292
  "Sweep",
293
+ "SweepFeeMode",
279
294
  "SweepHistoryQuery",
280
295
  "SweepHistoryResponse",
281
296
  "ForceSweepResponse",
282
297
  "SweepMode",
298
+ "SweepOverride",
299
+ "SweepPolicy",
300
+ "SweepPolicyMode",
301
+ "SweepSettings",
302
+ "SweepStatus",
283
303
  # Withdrawal types
284
304
  "Withdrawal",
285
305
  "WithdrawalHistoryResponse",
@@ -296,6 +316,9 @@ __all__ = [
296
316
  # Currency types
297
317
  "ConvertRequest",
298
318
  "ConvertResponse",
319
+ # Credits types
320
+ "CreditsBalance",
321
+ "CreditsTopup",
299
322
  # Contract encoders
300
323
  "encode_evm_call",
301
324
  "encode_evm_call_hex",
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for the package version."""
2
2
 
3
- __version__ = "0.1.0"
3
+ __version__ = "0.4.0"
@@ -12,6 +12,7 @@ from ._version import __version__
12
12
  from .errors import CryptoChiefError, is_retryable
13
13
  from .rsa import RsaKeyNotConfiguredError, decrypt_rsa_oaep, load_rsa_private_key_pem
14
14
  from .services.blockchain import BlockchainService
15
+ from .services.credits import CreditsService
15
16
  from .services.currencies import CurrenciesService
16
17
  from .services.payins import PayInsService
17
18
  from .services.payouts import PayoutsService
@@ -96,6 +97,7 @@ class CryptoChiefClient:
96
97
  self.static_deposits = StaticDepositsService(self)
97
98
  self.blockchain = BlockchainService(self)
98
99
  self.currencies = CurrenciesService(self)
100
+ self.credits = CreditsService(self)
99
101
 
100
102
  async def request(self, path: str, body: Any = None) -> Any:
101
103
  """Low-level signed POST against an API path (e.g. ``/v1/payout/estimate``).
@@ -0,0 +1,38 @@
1
+ """Sentinels for values that ``None`` cannot express.
2
+
3
+ Python has one "absent" value and some APIs need two. Where an argument
4
+ distinguishes "not supplied" from "supplied as nothing", ``None`` takes the
5
+ first meaning and a sentinel from this module takes the second.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+
11
+ class Clear:
12
+ """Stop overriding a field and go back to inheriting it.
13
+
14
+ Used with :meth:`cryptochief.SweepsService.update_settings`, where the API
15
+ expresses "inherit this again" by naming a field and sending no value for
16
+ it. ``None`` already means "leave this field alone", so it cannot also mean
17
+ "reset it".
18
+
19
+ Use the :data:`CLEAR` singleton rather than constructing this.
20
+ """
21
+
22
+ _instance: "Clear | None" = None
23
+
24
+ def __new__(cls) -> "Clear":
25
+ if cls._instance is None:
26
+ cls._instance = super().__new__(cls)
27
+ return cls._instance
28
+
29
+ def __repr__(self) -> str:
30
+ return "CLEAR"
31
+
32
+ def __bool__(self) -> bool:
33
+ # Truthy: `if value:` on a CLEAR must not read as "nothing was passed".
34
+ return True
35
+
36
+
37
+ #: The singleton :class:`Clear`.
38
+ CLEAR = Clear()
@@ -0,0 +1,70 @@
1
+ """Merchant credits (billing): balance check and top-up.
2
+
3
+ Both calls are billing-exempt (free of charge) - integrations can poll the
4
+ balance or open a top-up invoice without spending a paid call. Rate-limited to
5
+ 60 req/min per project; the balance answers even at zero or negative balance.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from dataclasses import dataclass
11
+
12
+ from .._models import from_dict
13
+ from .base import BaseService
14
+
15
+
16
+ @dataclass(kw_only=True)
17
+ class CreditsBalance:
18
+ #: Current balance in credits (10_000_000 credits = 1 USD).
19
+ credits_balance: int = 0
20
+ #: Pre-formatted USD with 2 decimals - can be negative, e.g. ``"-1.52"``.
21
+ usd_balance: str = ""
22
+ is_postpaid: bool = False
23
+ #: Effective debt limit in credits (postpaid only, 0 for prepaid).
24
+ debt_limit_credits: int = 0
25
+ #: Whether gas-paying ops (``/v1/transaction/execute`` etc.) would pass the gate.
26
+ can_execute_gas_operations: bool = False
27
+ #: Minimum credits required for gas-paying operations.
28
+ gas_ops_min_credits: int = 0
29
+ timestamp: str = "" # RFC3339
30
+
31
+
32
+ @dataclass(kw_only=True)
33
+ class CreditsTopup:
34
+ #: Billing invoice id.
35
+ invoice_id: int = 0
36
+ #: Hosted payment page (QR code, network selection, live status).
37
+ payment_link: str = ""
38
+ amount: str = ""
39
+ currency: str = ""
40
+ status: str = "" # "pending" on creation
41
+ order_uuid: str = ""
42
+ #: Unix seconds; 0 when the server does not set an expiry.
43
+ expired_at: int = 0
44
+
45
+
46
+ class CreditsService(BaseService):
47
+ async def balance(self) -> CreditsBalance:
48
+ """Current credits balance - free of charge, safe to poll before paid calls."""
49
+ return from_dict(CreditsBalance, await self._post("/v1/credits/balance", {}))
50
+
51
+ async def topup(
52
+ self,
53
+ *,
54
+ amount: str,
55
+ currency: str,
56
+ url_success: str | None = None,
57
+ url_error: str | None = None,
58
+ ) -> CreditsTopup:
59
+ """Open a credits top-up invoice - free of charge.
60
+
61
+ ``amount`` is a positive decimal up to 100000 (USD-pegged), ``currency``
62
+ is ``"USDT"`` or ``"USDC"``. Send the payer to ``payment_link``; the
63
+ optional urls are absolute http(s) redirects after payment.
64
+ """
65
+ body: dict[str, str] = {"amount": amount, "currency": currency}
66
+ if url_success is not None:
67
+ body["url_success"] = url_success
68
+ if url_error is not None:
69
+ body["url_error"] = url_error
70
+ return from_dict(CreditsTopup, await self._post("/v1/credits/topup", body))
@@ -38,12 +38,40 @@ def is_payin_terminal(status: str) -> bool:
38
38
  return status in _PAYIN_TERMINAL
39
39
 
40
40
 
41
+ class Environment(str, Enum):
42
+ """The two environments an order can belong to.
43
+
44
+ A project may be allowed one or both; asking for testnet on a project that
45
+ does not permit it is refused with ``TESTNET_NOT_ALLOWED`` rather than
46
+ quietly served on mainnet, and a value that is neither is
47
+ ``ENVIRONMENT_INVALID`` rather than a silent fallback.
48
+ """
49
+
50
+ MAINNET = "mainnet"
51
+ TESTNET = "testnet"
52
+
53
+
41
54
  @dataclass(kw_only=True)
42
55
  class CreatePayInRequest:
43
56
  order_id: str
44
57
  user_id: str
45
58
  mode: str
46
59
  to_address: Optional[str] = None
60
+ #: Pin the transit deposit wallet of THIS order to the given master wallet of
61
+ #: the project - the address the funds are swept to. The order's
62
+ #: asset/network chain family must match the master wallet's; a foreign or
63
+ #: mismatched address is rejected with 400. Omit for the project-default
64
+ #: behaviour.
65
+ master_wallet_address: Optional[str] = None
66
+ #: Constrain the asset the platform PICKS for this order to the real chains
67
+ #: or the test ones - ``Environment.MAINNET`` or ``Environment.TESTNET``.
68
+ #: Omit to use the project's own default.
69
+ #:
70
+ #: It changes nothing when ``asset`` names a concrete network - that is the
71
+ #: caller's choice. It matters in fiat mode and when the network is ``ANY``,
72
+ #: where the platform selects the asset and an unconstrained pick could put
73
+ #: a real payment on a test network.
74
+ environment: Optional[str] = None
47
75
  lifetime_sec: Optional[int] = None
48
76
  url_callback: Optional[str] = None
49
77
  url_success: Optional[str] = None
@@ -105,6 +133,10 @@ class SelectAssetRequest:
105
133
  uuid: str
106
134
  coin: str
107
135
  network: str
136
+ #: Pin the order's transit deposit wallet to the given project master
137
+ #: wallet; see :class:`CreatePayInRequest`. A value here overrides one
138
+ #: supplied at order create.
139
+ master_wallet_address: Optional[str] = None
108
140
 
109
141
 
110
142
  class PayInsService(BaseService):
@@ -0,0 +1,282 @@
1
+ """Treasury sweeps (transit -> master)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import Any, List, Optional, Union
8
+
9
+ from .._models import from_dict
10
+ from ..sentinels import Clear
11
+ from ..pagination import HistoryMeta
12
+ from .base import BaseService
13
+
14
+
15
+ class SweepMode(str, Enum):
16
+ AUTO = "auto"
17
+ FORCE = "force"
18
+
19
+
20
+ @dataclass(kw_only=True)
21
+ class SweepHistoryQuery:
22
+ mode: Optional[str] = None
23
+ page: Optional[int] = None
24
+ page_size: Optional[int] = None
25
+
26
+
27
+ class SweepStatus(str, Enum):
28
+ """A sweep is broadcast first and confirmed after.
29
+
30
+ ``BROADCASTED`` means the transaction is out and not yet confirmed;
31
+ ``COMPLETED`` means the chain confirmed it. The platform used to report
32
+ ``completed`` at broadcast, so a sweep could read as settled while its
33
+ transaction was still unconfirmed or had been dropped.
34
+
35
+ ``SKIPPED`` is a sweep the platform decided against - almost always a
36
+ balance below the wallet's threshold. A normal outcome, not a failure.
37
+ """
38
+
39
+ PENDING = "pending"
40
+ WAITING_GAS = "waiting_gas"
41
+ BROADCASTED = "broadcasted"
42
+ COMPLETED = "completed"
43
+ FAILED = "failed"
44
+ SKIPPED = "skipped"
45
+
46
+
47
+ class SweepPolicyMode(str, Enum):
48
+ """Auto-sweep modes.
49
+
50
+ ``OFF`` is never swept on its own (:meth:`SweepsService.force` still works),
51
+ ``MOMENTUM`` sweeps as soon as funds arrive, and ``THRESHOLD`` sweeps once
52
+ the balance reaches ``threshold_amount_usd``. A held balance is re-checked
53
+ periodically, so a wallet that crosses the threshold through price movement
54
+ alone is still swept.
55
+ """
56
+
57
+ OFF = "turned_off"
58
+ MOMENTUM = "momentum"
59
+ THRESHOLD = "threshold"
60
+
61
+
62
+ class SweepFeeMode(str, Enum):
63
+ """Who pays the gas for a sweep.
64
+
65
+ ``CLIENT`` takes it from the swept wallet, ``SERVICE`` from the platform's
66
+ service wallet, and ``MIX`` funds the gas from the service wallet and
67
+ reclaims the cost from the sweep.
68
+ """
69
+
70
+ CLIENT = "client"
71
+ SERVICE = "service"
72
+ MIX = "mix"
73
+
74
+
75
+ @dataclass(kw_only=True)
76
+ class Sweep:
77
+ task_id: str = ""
78
+ status: str = ""
79
+ sweep_tx_hash: Optional[str] = None
80
+ gas_pump_tx_hash: Optional[str] = None
81
+ wallet_address: Optional[str] = None
82
+ chain: Optional[str] = None
83
+ chain_family: Optional[str] = None
84
+ asset_symbol: Optional[str] = None
85
+ asset_type: Optional[str] = None
86
+ amount_human: Optional[str] = None
87
+ #: What triggered this sweep: momentum, threshold or force.
88
+ type_work: Optional[str] = None
89
+
90
+ #: Confirmations seen on the sweep transaction, and when it reached the
91
+ #: network's confirmation target. Read them with ``status``:
92
+ #: ``completed_at`` is absent while the sweep is still in flight.
93
+ sweep_confirmations: Optional[int] = None
94
+ completed_at: Optional[str] = None
95
+
96
+ #: Fees. ``total_fee_usd`` is the whole cost of the sweep; the gas-pump half
97
+ #: is the funding transfer that pays for it on chains needing one. The
98
+ #: ``real_*`` figures are what the chain actually charged, filled in once the
99
+ #: transaction settles; the others are the estimate made up front.
100
+ total_fee_usd: Optional[str] = None
101
+ gas_pump_source: Optional[str] = None
102
+ gas_pump_fee_human: Optional[str] = None
103
+ gas_pump_fee_usd: Optional[str] = None
104
+ sweep_fee_human: Optional[str] = None
105
+ sweep_fee_usd: Optional[str] = None
106
+ real_gas_pump_fee_human: Optional[str] = None
107
+ real_gas_pump_fee_usd: Optional[str] = None
108
+ real_sweep_fee_human: Optional[str] = None
109
+ real_sweep_fee_usd: Optional[str] = None
110
+
111
+ created_at: Optional[str] = None
112
+
113
+ #: Deprecated: never populated. The API reports fees under the names above;
114
+ #: these were guesses at a shape it does not send.
115
+ gas_fee_human: Optional[str] = None
116
+ gas_fee_fiat: Optional[str] = None
117
+ service_fee_fiat: Optional[str] = None
118
+ #: Deprecated: never populated - sweeps carry ``created_at`` and
119
+ #: ``completed_at``.
120
+ updated_at: Optional[str] = None
121
+
122
+
123
+ @dataclass(kw_only=True)
124
+ class SweepPolicy:
125
+ """A resolved set of sweep rules."""
126
+
127
+ type_work: str = ""
128
+ #: Meaningful only when ``type_work`` is ``threshold``.
129
+ threshold_amount_usd: Optional[str] = None
130
+ fee_mode: str = ""
131
+ #: Which layer the mode came from: ``wallet_network``, ``wallet``,
132
+ #: ``project`` or ``default``. Present on the effective policy, where the
133
+ #: question arises.
134
+ source: Optional[str] = None
135
+
136
+
137
+ @dataclass(kw_only=True)
138
+ class SweepOverride:
139
+ """What one wallet decides for itself.
140
+
141
+ A field of ``None`` is not overridden - it is inherited, which no ordinary
142
+ value can express.
143
+ """
144
+
145
+ #: Empty covers the address on every network it exists on; set, it covers
146
+ #: that one network and takes precedence over the address-wide override.
147
+ network_code: Optional[str] = None
148
+ type_work: Optional[str] = None
149
+ threshold_amount_usd: Optional[str] = None
150
+ fee_mode: Optional[str] = None
151
+ #: Who wrote it: ``merchant`` or ``operator``.
152
+ source: Optional[str] = None
153
+ #: An operator pinned this policy. While it is set, a merchant write answers
154
+ #: ``SWEEP_SETTINGS_LOCKED`` and changes nothing.
155
+ locked: bool = False
156
+
157
+
158
+ @dataclass(kw_only=True)
159
+ class SweepSettings:
160
+ """Three layers, on purpose.
161
+
162
+ ``effective`` is what will actually happen, ``override`` is what this wallet
163
+ decides for itself (``None`` if it decides nothing), and ``project_default``
164
+ is what it falls back to. Only the three together answer "is this value mine
165
+ or inherited" - the difference between changing it here and changing it on
166
+ the project. Inheritance is per field: a wallet can override the mode and
167
+ keep inheriting the fee mode.
168
+ """
169
+
170
+ wallet_address: Optional[str] = None
171
+ network_code: Optional[str] = None
172
+ effective: Optional[SweepPolicy] = None
173
+ override: Optional[SweepOverride] = None
174
+ project_default: Optional[SweepPolicy] = None
175
+
176
+
177
+ @dataclass(kw_only=True)
178
+ class SweepHistoryResponse:
179
+ items: Optional[List[Sweep]] = None
180
+ meta: Optional[HistoryMeta] = None
181
+
182
+
183
+ @dataclass(kw_only=True)
184
+ class ForceSweepResponse:
185
+ status: str = ""
186
+
187
+
188
+ class SweepsService(BaseService):
189
+ async def force(self, address: str, network: str) -> ForceSweepResponse:
190
+ """Trigger an immediate transit->master sweep for one address.
191
+
192
+ The status acknowledges acceptance; the resulting :class:`Sweep` record
193
+ appears via :meth:`wallet_history` once the on-chain tx is built.
194
+ """
195
+ return from_dict(
196
+ ForceSweepResponse,
197
+ await self._post("/v1/sweeps/force", {"address": address, "network_code": network}),
198
+ )
199
+
200
+ async def history(self, query: Optional[SweepHistoryQuery] = None) -> SweepHistoryResponse:
201
+ """Recent sweeps across the whole project."""
202
+ return from_dict(
203
+ SweepHistoryResponse, await self._post("/v1/sweeps/history", query or SweepHistoryQuery())
204
+ )
205
+
206
+ async def wallet_history(
207
+ self, address: str, query: Optional[SweepHistoryQuery] = None
208
+ ) -> SweepHistoryResponse:
209
+ """Recent sweeps scoped to one wallet."""
210
+ body: dict[str, Any] = {"address": address}
211
+ if query is not None:
212
+ if query.mode is not None:
213
+ body["mode"] = query.mode
214
+ if query.page is not None:
215
+ body["page"] = query.page
216
+ if query.page_size is not None:
217
+ body["page_size"] = query.page_size
218
+ return from_dict(SweepHistoryResponse, await self._post("/v1/sweeps/wallet/history", body))
219
+
220
+ async def settings(
221
+ self, address: Optional[str] = None, network_code: Optional[str] = None
222
+ ) -> SweepSettings:
223
+ """The auto-sweep policy in force for one wallet.
224
+
225
+ Returns what will happen, what the wallet overrides, and what it
226
+ inherits. Omitting ``address`` asks for the project's own default rather
227
+ than any wallet's policy.
228
+
229
+ Scoped to the caller's own wallets: an address that is not the project's
230
+ answers ``WALLET_NOT_FOUND``.
231
+ """
232
+ body: dict[str, Any] = {}
233
+ if address:
234
+ body["address"] = address
235
+ if network_code:
236
+ body["network_code"] = network_code
237
+ return from_dict(SweepSettings, await self._post("/v1/sweeps/settings", body))
238
+
239
+ async def update_settings(
240
+ self,
241
+ address: str,
242
+ *,
243
+ network_code: Optional[str] = None,
244
+ type_work: Union[str, Clear, None] = None,
245
+ threshold_amount_usd: Union[str, Clear, None] = None,
246
+ fee_mode: Union[str, Clear, None] = None,
247
+ ) -> SweepSettings:
248
+ """Write a wallet's auto-sweep policy.
249
+
250
+ Returns the settings as they stand afterwards, so the caller sees what
251
+ the write resolved to without asking again.
252
+
253
+ ``None`` leaves a field alone. :data:`~cryptochief.CLEAR` stops
254
+ overriding it and goes back to inheriting - the only way to drop one
255
+ field while keeping the others. The API expresses that by naming the
256
+ field with no value, which ``None`` cannot say in Python because it
257
+ already means "not supplied".
258
+
259
+ Refusals are named: ``TYPE_WORK_INVALID``, ``FEE_MODE_INVALID``,
260
+ ``THRESHOLD_INVALID``, ``THRESHOLD_MUST_BE_POSITIVE``,
261
+ ``THRESHOLD_REQUIRED_FOR_THRESHOLD_MODE``, and
262
+ ``SWEEP_SETTINGS_LOCKED`` when an operator has pinned the policy.
263
+ """
264
+ body: dict[str, Any] = {"address": address}
265
+ if network_code:
266
+ body["network_code"] = network_code
267
+
268
+ fields: List[str] = []
269
+ for name, value in (
270
+ ("type_work", type_work),
271
+ ("threshold_amount_usd", threshold_amount_usd),
272
+ ("fee_mode", fee_mode),
273
+ ):
274
+ if value is None:
275
+ continue
276
+ fields.append(name)
277
+ if not isinstance(value, Clear):
278
+ body[name] = value.value if isinstance(value, Enum) else value
279
+ if fields:
280
+ body["fields"] = fields
281
+
282
+ return from_dict(SweepSettings, await self._post("/v1/sweeps/settings/update", body))
@@ -1,85 +0,0 @@
1
- """Treasury sweeps (transit -> master)."""
2
-
3
- from __future__ import annotations
4
-
5
- from dataclasses import dataclass
6
- from enum import Enum
7
- from typing import Any, List, Optional
8
-
9
- from .._models import from_dict
10
- from ..pagination import HistoryMeta
11
- from .base import BaseService
12
-
13
-
14
- class SweepMode(str, Enum):
15
- AUTO = "auto"
16
- FORCE = "force"
17
-
18
-
19
- @dataclass(kw_only=True)
20
- class SweepHistoryQuery:
21
- mode: Optional[str] = None
22
- page: Optional[int] = None
23
- page_size: Optional[int] = None
24
-
25
-
26
- @dataclass(kw_only=True)
27
- class Sweep:
28
- task_id: str = ""
29
- status: str = ""
30
- sweep_tx_hash: Optional[str] = None
31
- wallet_address: Optional[str] = None
32
- chain: Optional[str] = None
33
- chain_family: Optional[str] = None
34
- asset_symbol: Optional[str] = None
35
- asset_type: Optional[str] = None
36
- amount_human: Optional[str] = None
37
- gas_fee_human: Optional[str] = None
38
- gas_fee_fiat: Optional[str] = None
39
- service_fee_fiat: Optional[str] = None
40
- created_at: Optional[str] = None
41
- updated_at: Optional[str] = None
42
-
43
-
44
- @dataclass(kw_only=True)
45
- class SweepHistoryResponse:
46
- items: Optional[List[Sweep]] = None
47
- meta: Optional[HistoryMeta] = None
48
-
49
-
50
- @dataclass(kw_only=True)
51
- class ForceSweepResponse:
52
- status: str = ""
53
-
54
-
55
- class SweepsService(BaseService):
56
- async def force(self, address: str, network: str) -> ForceSweepResponse:
57
- """Trigger an immediate transit->master sweep for one address.
58
-
59
- The status acknowledges acceptance; the resulting :class:`Sweep` record
60
- appears via :meth:`wallet_history` once the on-chain tx is built.
61
- """
62
- return from_dict(
63
- ForceSweepResponse,
64
- await self._post("/v1/sweeps/force", {"address": address, "network_code": network}),
65
- )
66
-
67
- async def history(self, query: Optional[SweepHistoryQuery] = None) -> SweepHistoryResponse:
68
- """Recent sweeps across the whole project."""
69
- return from_dict(
70
- SweepHistoryResponse, await self._post("/v1/sweeps/history", query or SweepHistoryQuery())
71
- )
72
-
73
- async def wallet_history(
74
- self, address: str, query: Optional[SweepHistoryQuery] = None
75
- ) -> SweepHistoryResponse:
76
- """Recent sweeps scoped to one wallet."""
77
- body: dict[str, Any] = {"address": address}
78
- if query is not None:
79
- if query.mode is not None:
80
- body["mode"] = query.mode
81
- if query.page is not None:
82
- body["page"] = query.page
83
- if query.page_size is not None:
84
- body["page_size"] = query.page_size
85
- return from_dict(SweepHistoryResponse, await self._post("/v1/sweeps/wallet/history", body))