cryptochief-crypto-processing-python 0.2.0__tar.gz → 0.5.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 (44) hide show
  1. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/PKG-INFO +43 -2
  2. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/README.md +42 -1
  3. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/pyproject.toml +1 -1
  4. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/__init__.py +22 -0
  5. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/_version.py +1 -1
  6. cryptochief_crypto_processing_python-0.5.0/src/cryptochief/py.typed +0 -0
  7. cryptochief_crypto_processing_python-0.5.0/src/cryptochief/sentinels.py +38 -0
  8. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/payins.py +32 -0
  9. cryptochief_crypto_processing_python-0.5.0/src/cryptochief/services/sweeps.py +282 -0
  10. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/webhook.py +65 -0
  11. cryptochief_crypto_processing_python-0.2.0/src/cryptochief/services/sweeps.py +0 -85
  12. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/.gitignore +0 -0
  13. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/LICENSE +0 -0
  14. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/_models.py +0 -0
  15. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/amount.py +0 -0
  16. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/assets.py +0 -0
  17. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/chains.py +0 -0
  18. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/client.py +0 -0
  19. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/contract/__init__.py +0 -0
  20. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/contract/base58.py +0 -0
  21. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/contract/borsh.py +0 -0
  22. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/contract/evm_abi.py +0 -0
  23. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/contract/keccak.py +0 -0
  24. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/contract/tron_address.py +0 -0
  25. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/errors.py +0 -0
  26. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/pagination.py +0 -0
  27. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/poll.py +0 -0
  28. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/rsa.py +0 -0
  29. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/__init__.py +0 -0
  30. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/base.py +0 -0
  31. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/blockchain.py +0 -0
  32. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/credits.py +0 -0
  33. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/currencies.py +0 -0
  34. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/payouts.py +0 -0
  35. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/static_deposits.py +0 -0
  36. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/transactions.py +0 -0
  37. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/wallets.py +0 -0
  38. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/services/withdrawals.py +0 -0
  39. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/sign.py +0 -0
  40. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/ton/__init__.py +0 -0
  41. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/ton/address.py +0 -0
  42. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/ton/messages.py +0 -0
  43. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/ton/rpc.py +0 -0
  44. {cryptochief_crypto_processing_python-0.2.0 → cryptochief_crypto_processing_python-0.5.0}/src/cryptochief/transport.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: cryptochief-crypto-processing-python
3
- Version: 0.2.0
3
+ Version: 0.5.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
@@ -121,7 +121,7 @@ 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` |
@@ -200,6 +200,14 @@ decimal strings round-trip exactly. Discover an asset's decimals with
200
200
 
201
201
  ## Contract calls without hand-encoding
202
202
 
203
+ > **This snippet shows the encoder, not a complete swap.** Uniswap's router
204
+ > moves your input token with `transferFrom`, so it needs an ERC-20
205
+ > `approve(address,uint256)` on that token first, confirmed before the swap is
206
+ > signed — without it the swap reverts and burns the gas. And an `amountOutMin`
207
+ > of `0` accepts whatever the pool returns, which on a public mempool hands the
208
+ > trade to the first sandwich bot that sees it. The runnable version, with both,
209
+ > is in `examples/`.
210
+
203
211
  ```python
204
212
  from cryptochief import EvmCallRequest, Erc20TransferRequest, Chain, human_to_base
205
213
 
@@ -335,6 +343,39 @@ priv = client.wallets.decrypt_private_key(wallet.private_key_encrypted)
335
343
  - **How do I do a crypto swap?** A swap is a payout with `auto_convert=True`.
336
344
  - **How do I call a smart contract?** `client.transactions.sign_evm_call` /
337
345
  `sign_anchor_call` / `jetton_transfer`, then `transactions.execute`.
346
+ - **How do I control when a deposit wallet is swept?**
347
+ `client.sweeps.settings(...)` reads the policy in force for one wallet and
348
+ `client.sweeps.update_settings(...)` changes it - sweep on arrival
349
+ (`SweepPolicyMode.MOMENTUM`), sweep once the balance reaches an amount
350
+ (`SweepPolicyMode.THRESHOLD` plus `threshold_amount_usd`), or never on its own
351
+ (`SweepPolicyMode.OFF`, force still works). The read comes back in three
352
+ layers - what will happen, what this wallet overrides, and what it inherits
353
+ from the project - so a value of your own is distinguishable from an inherited
354
+ one:
355
+
356
+ ```python
357
+ s = await client.sweeps.update_settings(
358
+ deposit_address,
359
+ type_work=SweepPolicyMode.THRESHOLD,
360
+ threshold_amount_usd="250",
361
+ )
362
+ # s.effective is the resolved policy; s.effective.source names the layer it came from.
363
+ ```
364
+
365
+ Inheritance is per field: overriding the mode leaves the fee mode inherited.
366
+ To stop overriding a field, pass `CLEAR` - `None` already means "leave this
367
+ field alone", so it cannot also mean "reset it".
368
+ - **How do I know a sweep actually settled?** Check `status`.
369
+ `SweepStatus.BROADCASTED` means the transaction is out and not yet confirmed;
370
+ `SweepStatus.COMPLETED` means confirmed, with `sweep_confirmations` and
371
+ `completed_at` filled in. Earlier platform versions reported `completed` at
372
+ broadcast, so a sweep could read as settled while its transaction was still
373
+ unconfirmed.
374
+ - **How do I keep test payments off real chains?** Set `environment` on
375
+ `CreatePayInRequest` to `Environment.TESTNET` or `Environment.MAINNET`. It
376
+ constrains the asset the platform picks when you have not named a concrete
377
+ network - fiat mode and `ANY` - so an unconstrained pick cannot put a real
378
+ payment on a test chain. Omit it to use the project's default.
338
379
 
339
380
  ## Documentation
340
381
 
@@ -82,7 +82,7 @@ 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` |
@@ -161,6 +161,14 @@ decimal strings round-trip exactly. Discover an asset's decimals with
161
161
 
162
162
  ## Contract calls without hand-encoding
163
163
 
164
+ > **This snippet shows the encoder, not a complete swap.** Uniswap's router
165
+ > moves your input token with `transferFrom`, so it needs an ERC-20
166
+ > `approve(address,uint256)` on that token first, confirmed before the swap is
167
+ > signed — without it the swap reverts and burns the gas. And an `amountOutMin`
168
+ > of `0` accepts whatever the pool returns, which on a public mempool hands the
169
+ > trade to the first sandwich bot that sees it. The runnable version, with both,
170
+ > is in `examples/`.
171
+
164
172
  ```python
165
173
  from cryptochief import EvmCallRequest, Erc20TransferRequest, Chain, human_to_base
166
174
 
@@ -296,6 +304,39 @@ priv = client.wallets.decrypt_private_key(wallet.private_key_encrypted)
296
304
  - **How do I do a crypto swap?** A swap is a payout with `auto_convert=True`.
297
305
  - **How do I call a smart contract?** `client.transactions.sign_evm_call` /
298
306
  `sign_anchor_call` / `jetton_transfer`, then `transactions.execute`.
307
+ - **How do I control when a deposit wallet is swept?**
308
+ `client.sweeps.settings(...)` reads the policy in force for one wallet and
309
+ `client.sweeps.update_settings(...)` changes it - sweep on arrival
310
+ (`SweepPolicyMode.MOMENTUM`), sweep once the balance reaches an amount
311
+ (`SweepPolicyMode.THRESHOLD` plus `threshold_amount_usd`), or never on its own
312
+ (`SweepPolicyMode.OFF`, force still works). The read comes back in three
313
+ layers - what will happen, what this wallet overrides, and what it inherits
314
+ from the project - so a value of your own is distinguishable from an inherited
315
+ one:
316
+
317
+ ```python
318
+ s = await client.sweeps.update_settings(
319
+ deposit_address,
320
+ type_work=SweepPolicyMode.THRESHOLD,
321
+ threshold_amount_usd="250",
322
+ )
323
+ # s.effective is the resolved policy; s.effective.source names the layer it came from.
324
+ ```
325
+
326
+ Inheritance is per field: overriding the mode leaves the fee mode inherited.
327
+ To stop overriding a field, pass `CLEAR` - `None` already means "leave this
328
+ field alone", so it cannot also mean "reset it".
329
+ - **How do I know a sweep actually settled?** Check `status`.
330
+ `SweepStatus.BROADCASTED` means the transaction is out and not yet confirmed;
331
+ `SweepStatus.COMPLETED` means confirmed, with `sweep_confirmations` and
332
+ `completed_at` filled in. Earlier platform versions reported `completed` at
333
+ broadcast, so a sweep could read as settled while its transaction was still
334
+ unconfirmed.
335
+ - **How do I keep test payments off real chains?** Set `environment` on
336
+ `CreatePayInRequest` to `Environment.TESTNET` or `Environment.MAINNET`. It
337
+ constrains the asset the platform picks when you have not named a concrete
338
+ network - fiat mode and `ANY` - so an unconstrained pick cannot put a real
339
+ payment on a test chain. Omit it to use the project's default.
299
340
 
300
341
  ## Documentation
301
342
 
@@ -110,5 +110,5 @@ target-version = "py310"
110
110
 
111
111
  [tool.mypy]
112
112
  python_version = "3.10"
113
+ mypy_path = "src"
113
114
  warn_unused_ignores = true
114
- ignore_missing_imports = true
@@ -80,6 +80,7 @@ from .services.currencies import ConvertRequest, ConvertResponse, CurrenciesServ
80
80
  from .services.payins import (
81
81
  CoinOption,
82
82
  CreatePayInRequest,
83
+ Environment,
83
84
  PayIn,
84
85
  PayInHistoryResponse,
85
86
  PayInMode,
@@ -110,13 +111,20 @@ from .services.static_deposits import (
110
111
  StaticDepositsService,
111
112
  StaticDepositStatus,
112
113
  )
114
+ from .sentinels import CLEAR, Clear
113
115
  from .services.sweeps import (
114
116
  ForceSweepResponse,
115
117
  Sweep,
118
+ SweepFeeMode,
116
119
  SweepHistoryQuery,
117
120
  SweepHistoryResponse,
118
121
  SweepMode,
122
+ SweepOverride,
123
+ SweepPolicy,
124
+ SweepPolicyMode,
125
+ SweepSettings,
119
126
  SweepsService,
127
+ SweepStatus,
120
128
  )
121
129
  from .services.transactions import (
122
130
  AnchorCallRequest,
@@ -162,6 +170,8 @@ from .webhook import (
162
170
  PayInWebhookEvent,
163
171
  PayoutWebhookEvent,
164
172
  StaticDepositWebhookEvent,
173
+ SweepWebhookEvent,
174
+ SWEEP_EVENT_CONFIRMED,
165
175
  TransactionWebhookEvent,
166
176
  WebhookSignatureError,
167
177
  coerce_webhook_event,
@@ -171,6 +181,10 @@ from .webhook import (
171
181
 
172
182
  __all__ = [
173
183
  "__version__",
184
+ # Sentinels
185
+ "CLEAR",
186
+ "Clear",
187
+ "Environment",
174
188
  # Client
175
189
  "CryptoChiefClient",
176
190
  "VERSION",
@@ -218,6 +232,8 @@ __all__ = [
218
232
  "TransactionWebhookEvent",
219
233
  "PayInWebhookEvent",
220
234
  "StaticDepositWebhookEvent",
235
+ "SweepWebhookEvent",
236
+ "SWEEP_EVENT_CONFIRMED",
221
237
  # Services
222
238
  "PayoutsService",
223
239
  "TransactionsService",
@@ -278,10 +294,16 @@ __all__ = [
278
294
  "WalletType",
279
295
  # Sweep types
280
296
  "Sweep",
297
+ "SweepFeeMode",
281
298
  "SweepHistoryQuery",
282
299
  "SweepHistoryResponse",
283
300
  "ForceSweepResponse",
284
301
  "SweepMode",
302
+ "SweepOverride",
303
+ "SweepPolicy",
304
+ "SweepPolicyMode",
305
+ "SweepSettings",
306
+ "SweepStatus",
285
307
  # Withdrawal types
286
308
  "Withdrawal",
287
309
  "WithdrawalHistoryResponse",
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for the package version."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.5.0"
@@ -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()
@@ -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))
@@ -175,11 +175,75 @@ class StaticDepositWebhookEvent:
175
175
  paid_at: Optional[str] = None
176
176
 
177
177
 
178
+ #: The only sweep event the platform emits. There is deliberately no
179
+ #: ``sweep.broadcasted``: "we sent it" is not something you can act on, and an
180
+ #: event that means "maybe" is one more thing to reconcile.
181
+ SWEEP_EVENT_CONFIRMED = "sweep.confirmed"
182
+
183
+
184
+ @dataclass(kw_only=True)
185
+ class SweepWebhookEvent:
186
+ """Funds swept off a deposit wallet, confirmed on chain.
187
+
188
+ A ``static_deposit.paid`` tells you a customer paid you. This tells you the
189
+ money has finished moving into your own custody - until it fires, the
190
+ balance still sits on the deposit address. Reconciliation, treasury
191
+ reporting and "funds available to pay out" all key off this event, not off
192
+ the deposit.
193
+
194
+ Sweeps run on static deposit wallets *and* on the transit wallets issued per
195
+ pay-in order; both deliver here, to the callback URL configured for the
196
+ wallet the funds left.
197
+ """
198
+
199
+ event: str = ""
200
+ #: The sweeper task. One sweep settles once - use it as your idempotency key.
201
+ task_id: str = ""
202
+ #: Always ``"completed"``. A sweep reaches you in no other state.
203
+ status: str = ""
204
+
205
+ #: The wallet the funds left - the address your customer paid into.
206
+ wallet_address: str = ""
207
+ #: The master wallet they landed on.
208
+ to_address: Optional[str] = None
209
+
210
+ network: str = ""
211
+ chain_family: Optional[str] = None
212
+ asset_symbol: str = ""
213
+ asset_contract: Optional[str] = None
214
+ #: ``"native"`` or ``"token"``.
215
+ asset_type: Optional[str] = None
216
+ amount_raw: Optional[str] = None
217
+ amount_human: Optional[str] = None
218
+
219
+ sweep_tx_hash: str = ""
220
+ #: Set when the platform had to fund gas on the wallet before it could sweep.
221
+ gas_pump_tx_hash: Optional[str] = None
222
+
223
+ #: What makes this event true rather than hopeful, and never zero. It
224
+ #: travels with the event rather than being implied by it: "confirmed" is
225
+ #: not the same number on every chain, so if you run your own finality
226
+ #: policy you need the count to apply it.
227
+ sweep_confirmations: int = 0
228
+
229
+ #: When the chain was observed to hold the sweep. NOT the task's completion
230
+ #: timestamp, which is stamped on every terminal outcome - failures
231
+ #: included - and so says nothing about settlement.
232
+ confirmed_at: Optional[str] = None
233
+
234
+ #: What triggered it: ``"momentum"``, ``"threshold"`` or ``"force"``.
235
+ type_work: Optional[str] = None
236
+ #: What the sweep cost: network fee plus any gas or energy the platform
237
+ #: fronted to make it possible.
238
+ total_fee_usd: Optional[str] = None
239
+
240
+
178
241
  WebhookEvent = Union[
179
242
  PayoutWebhookEvent,
180
243
  TransactionWebhookEvent,
181
244
  PayInWebhookEvent,
182
245
  StaticDepositWebhookEvent,
246
+ SweepWebhookEvent,
183
247
  Dict[str, Any],
184
248
  ]
185
249
 
@@ -188,4 +252,5 @@ _EVENT_BY_PREFIX = {
188
252
  "transaction": TransactionWebhookEvent,
189
253
  "invoice": PayInWebhookEvent,
190
254
  "static_deposit": StaticDepositWebhookEvent,
255
+ "sweep": SweepWebhookEvent,
191
256
  }
@@ -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))