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
@@ -0,0 +1,24 @@
1
+ """Shared base for the domain services."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from .._models import to_payload
8
+
9
+ if TYPE_CHECKING:
10
+ from ..client import CryptoChiefClient
11
+
12
+
13
+ class BaseService:
14
+ """Holds the client reference and a signed-POST helper.
15
+
16
+ Request bodies are serialized with :func:`to_payload` (drops ``None``); the
17
+ field names already match the wire, so there is no case conversion.
18
+ """
19
+
20
+ def __init__(self, client: "CryptoChiefClient") -> None:
21
+ self._client = client
22
+
23
+ async def _post(self, path: str, body: Any = None) -> Any:
24
+ return await self._client.request(path, to_payload(body))
@@ -0,0 +1,76 @@
1
+ """Read-only on-chain queries: enabled assets, balances, tx status."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any, List, Optional
7
+
8
+ from .._models import from_dict
9
+ from .base import BaseService
10
+
11
+
12
+ @dataclass(kw_only=True)
13
+ class AvailableContract:
14
+ network: Optional[str] = None
15
+ coin: Optional[str] = None
16
+ contract: Optional[str] = None
17
+ type: Optional[str] = None # "native" or "token"
18
+ decimals: int = 0
19
+
20
+
21
+ @dataclass(kw_only=True)
22
+ class AvailableContractsResponse:
23
+ items: Optional[List[AvailableContract]] = None
24
+
25
+
26
+ @dataclass(kw_only=True)
27
+ class WalletBalanceRow:
28
+ address: str = ""
29
+ value: Optional[str] = None
30
+ human_value: Optional[str] = None
31
+ decimals: int = 0
32
+ contract: Optional[str] = None
33
+
34
+
35
+ @dataclass(kw_only=True)
36
+ class TxStatusRow:
37
+ confirmations: int = 0
38
+ fee: Optional[str] = None
39
+ human_fee: Optional[str] = None
40
+ block_number: Optional[int] = None
41
+ status: Optional[str] = None
42
+
43
+
44
+ class BlockchainService(BaseService):
45
+ async def contracts_available(
46
+ self, network: Optional[str] = None
47
+ ) -> AvailableContractsResponse:
48
+ """Coins/tokens this project may use.
49
+
50
+ Pass a ``network`` to scope to one chain, or omit for the full set. Each
51
+ row's ``decimals`` is what ``human_to_base`` / ``base_to_human`` need.
52
+ """
53
+ body = {"network": network} if network else {}
54
+ return from_dict(
55
+ AvailableContractsResponse, await self._post("/v1/blockchain/contracts/available", body)
56
+ )
57
+
58
+ async def wallet_balance(
59
+ self,
60
+ chain: str,
61
+ addresses: List[str],
62
+ contracts: Optional[List[str]] = None,
63
+ ) -> List[WalletBalanceRow]:
64
+ """Native + token balances for one or more addresses."""
65
+ body: dict[str, Any] = {"chain": chain, "addresses": addresses}
66
+ if contracts:
67
+ body["contracts"] = contracts
68
+ raw = await self._post("/v1/blockchain/wallet/balance", body)
69
+ return [from_dict(WalletBalanceRow, r) for r in (raw or [])]
70
+
71
+ async def transaction_status(self, chain: str, tx_hash: str) -> List[TxStatusRow]:
72
+ """Current on-chain state of a transaction by hash."""
73
+ raw = await self._post(
74
+ "/v1/blockchain/transaction/status", {"chain": chain, "hash": tx_hash}
75
+ )
76
+ return [from_dict(TxStatusRow, r) for r in (raw or [])]
@@ -0,0 +1,55 @@
1
+ """Fiat <-> crypto rate calculator.
2
+
3
+ These quote rates only - they do NOT move funds (a swap is a payout with
4
+ ``auto_convert=True``).
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from typing import Optional
11
+
12
+ from .._models import from_dict
13
+ from .base import BaseService
14
+
15
+
16
+ @dataclass(kw_only=True)
17
+ class ConvertRequest:
18
+ from_: str # source ticker (`from` is a Python keyword - serialized below)
19
+ to: str
20
+ amount: str
21
+ provider: Optional[str] = None
22
+
23
+
24
+ @dataclass(kw_only=True)
25
+ class ConvertResponse:
26
+ amount_crypto: float = 0.0
27
+ amount_fiat: float = 0.0
28
+ crypto: Optional[str] = None
29
+ crypto_to_usdt: float = 0.0
30
+ exchange: Optional[str] = None
31
+ fiat: Optional[str] = None
32
+ fiat_to_usd: float = 0.0
33
+ timestamp_crypto: int = 0
34
+ timestamp_fiat: int = 0
35
+
36
+
37
+ def _body(req: ConvertRequest) -> dict:
38
+ body = {"from": req.from_, "to": req.to, "amount": req.amount}
39
+ if req.provider is not None:
40
+ body["provider"] = req.provider
41
+ return body
42
+
43
+
44
+ class CurrenciesService(BaseService):
45
+ async def fiat_to_crypto(self, req: ConvertRequest) -> ConvertResponse:
46
+ """Quote how much crypto the given fiat amount is worth."""
47
+ return from_dict(
48
+ ConvertResponse, await self._post("/v1/currencies/convert/fiat-crypto", _body(req))
49
+ )
50
+
51
+ async def crypto_to_fiat(self, req: ConvertRequest) -> ConvertResponse:
52
+ """Quote how much fiat the given crypto amount is worth."""
53
+ return from_dict(
54
+ ConvertResponse, await self._post("/v1/currencies/convert/crypto-fiat", _body(req))
55
+ )
@@ -0,0 +1,145 @@
1
+ """Incoming-payment (invoice) endpoints."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import List, Optional
8
+
9
+ from .._models import from_dict
10
+ from ..assets import Asset, AssetsPolicy
11
+ from ..pagination import HistoryMeta, HistoryQuery
12
+ from ..poll import wait_for_terminal
13
+ from .base import BaseService
14
+
15
+
16
+ class PayInMode(str, Enum):
17
+ """``fiat`` fixes a stable fiat price; ``crypto`` fixes the crypto amount."""
18
+
19
+ FIAT = "fiat"
20
+ CRYPTO = "crypto"
21
+
22
+
23
+ class PayInStatus(str, Enum):
24
+ WAITING_ASSET_SELECT = "waiting_asset_select"
25
+ PENDING = "pending"
26
+ PROCESSING = "processing"
27
+ PROCESS = "process"
28
+ PAID = "paid"
29
+ CANCEL = "cancel"
30
+ EXPIRED = "expired"
31
+
32
+
33
+ _PAYIN_TERMINAL = frozenset({"paid", "cancel", "expired"})
34
+
35
+
36
+ def is_payin_terminal(status: str) -> bool:
37
+ """Whether a pay-in status is final."""
38
+ return status in _PAYIN_TERMINAL
39
+
40
+
41
+ @dataclass(kw_only=True)
42
+ class CreatePayInRequest:
43
+ order_id: str
44
+ user_id: str
45
+ mode: str
46
+ to_address: Optional[str] = None
47
+ lifetime_sec: Optional[int] = None
48
+ url_callback: Optional[str] = None
49
+ url_success: Optional[str] = None
50
+ url_error: Optional[str] = None
51
+ additional_data: Optional[str] = None
52
+ accuracy_payment_percent: Optional[int] = None
53
+ # FIAT mode.
54
+ amount_fiat: Optional[str] = None
55
+ currency: Optional[str] = None
56
+ course_source: Optional[str] = None
57
+ assets: Optional[AssetsPolicy] = None
58
+ # CRYPTO mode.
59
+ amount_crypto: Optional[str] = None
60
+ asset: Optional[Asset] = None
61
+
62
+
63
+ @dataclass(kw_only=True)
64
+ class CoinOption:
65
+ coin: Optional[str] = None
66
+ network: Optional[str] = None
67
+ chain_family: Optional[str] = None
68
+ contract: Optional[str] = None
69
+
70
+
71
+ @dataclass(kw_only=True)
72
+ class PayIn:
73
+ uuid: str = ""
74
+ status: str = ""
75
+ type: Optional[str] = None
76
+ order_id: Optional[str] = None
77
+ user_id: Optional[str] = None
78
+ mode: Optional[str] = None
79
+ amount_crypto: Optional[str] = None
80
+ amount_fiat: Optional[str] = None
81
+ currency: Optional[str] = None
82
+ payment_coin: Optional[str] = None
83
+ payment_network: Optional[str] = None
84
+ to_address: Optional[str] = None
85
+ coins: Optional[List[CoinOption]] = None
86
+ payment_link: Optional[str] = None
87
+ url_callback: Optional[str] = None
88
+ url_success: Optional[str] = None
89
+ url_error: Optional[str] = None
90
+ additional_data: Optional[str] = None
91
+ can_cancel: Optional[bool] = None
92
+ expired_at: Optional[str] = None
93
+ created_at: Optional[str] = None
94
+ updated_at: Optional[str] = None
95
+
96
+
97
+ @dataclass(kw_only=True)
98
+ class PayInHistoryResponse:
99
+ items: Optional[List[PayIn]] = None
100
+ meta: Optional[HistoryMeta] = None
101
+
102
+
103
+ @dataclass(kw_only=True)
104
+ class SelectAssetRequest:
105
+ uuid: str
106
+ coin: str
107
+ network: str
108
+
109
+
110
+ class PayInsService(BaseService):
111
+ async def create(self, req: CreatePayInRequest) -> PayIn:
112
+ """Open a new pay-in order."""
113
+ return from_dict(PayIn, await self._post("/v1/payments/order/create", req))
114
+
115
+ async def select_asset(self, req: SelectAssetRequest) -> PayIn:
116
+ """Commit the customer's coin/network choice on a ``waiting_asset_select`` order."""
117
+ return from_dict(PayIn, await self._post("/v1/payments/asset/select", req))
118
+
119
+ async def reset_asset(self, uuid: str) -> PayIn:
120
+ """Revert a pending order to ``waiting_asset_select`` (H2H only)."""
121
+ return from_dict(PayIn, await self._post("/v1/payments/asset/reset", {"uuid": uuid}))
122
+
123
+ async def cancel(self, uuid: str) -> PayIn:
124
+ """Cancel an open order."""
125
+ return from_dict(PayIn, await self._post("/v1/payments/order/cancel", {"uuid": uuid}))
126
+
127
+ async def info(self, uuid: str) -> PayIn:
128
+ """Fetch the current state of one pay-in by uuid."""
129
+ return from_dict(PayIn, await self._post("/v1/payments/order/info", {"uuid": uuid}))
130
+
131
+ async def history(self, query: Optional[HistoryQuery] = None) -> PayInHistoryResponse:
132
+ """Paged list of pay-ins."""
133
+ return from_dict(
134
+ PayInHistoryResponse, await self._post("/v1/payments/history", query or HistoryQuery())
135
+ )
136
+
137
+ async def wait_for(self, uuid: str, *, interval: float = 5.0, timeout: float = 600.0) -> PayIn:
138
+ """Poll ``info`` until the pay-in reaches a terminal state (or timeout)."""
139
+
140
+ async def fetch() -> PayIn:
141
+ return await self.info(uuid)
142
+
143
+ return await wait_for_terminal(
144
+ fetch, lambda p: is_payin_terminal(p.status), interval=interval, timeout=timeout
145
+ )
@@ -0,0 +1,175 @@
1
+ """Single and mass payout endpoints (including auto-convert swaps)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import Any, Dict, List, Optional
8
+
9
+ from .._models import from_dict
10
+ from ..assets import AssetsPolicy
11
+ from ..pagination import HistoryMeta, HistoryQuery
12
+ from ..poll import wait_for_terminal
13
+ from .base import BaseService
14
+
15
+
16
+ class PayoutStatus(str, Enum):
17
+ QUEUE = "queue"
18
+ PROCESS = "process"
19
+ PAID = "paid"
20
+ FAILED = "failed"
21
+ SYSTEM_FAIL = "system_fail"
22
+ EXPIRED = "expired"
23
+ CANCEL = "cancel"
24
+
25
+
26
+ _PAYOUT_TERMINAL = frozenset({"paid", "failed", "system_fail", "expired", "cancel"})
27
+
28
+
29
+ def is_payout_terminal(status: str) -> bool:
30
+ """Whether a payout status is final (no further transitions)."""
31
+ return status in _PAYOUT_TERMINAL
32
+
33
+
34
+ @dataclass(kw_only=True)
35
+ class EstimatePayoutRequest:
36
+ network: str
37
+ coin: str
38
+ amount: str
39
+ to_address: str
40
+ from_addresses: Optional[List[str]] = None
41
+ allow_multiple_sources: Optional[bool] = None
42
+ auto_convert: Optional[bool] = None
43
+ auto_convert_policy: Optional[AssetsPolicy] = None
44
+ max_fee_amount_fiat: Optional[str] = None
45
+ memo: Optional[str] = None
46
+
47
+
48
+ @dataclass(kw_only=True)
49
+ class ExecutePayoutRequest(EstimatePayoutRequest):
50
+ """``order_id`` is the idempotency key - resubmitting returns the same ``uuid``."""
51
+
52
+ order_id: str
53
+ user_id: str
54
+ url_callback: str
55
+
56
+
57
+ @dataclass(kw_only=True)
58
+ class PayoutFeeInfo:
59
+ fee_mode: Optional[str] = None
60
+ estimated_fiat: Optional[str] = None
61
+ estimated_coin: Optional[str] = None
62
+ estimated_asset: Optional[str] = None
63
+
64
+
65
+ @dataclass(kw_only=True)
66
+ class PayoutSource:
67
+ address: Optional[str] = None
68
+ amount: Optional[str] = None
69
+ coin: Optional[str] = None
70
+
71
+
72
+ @dataclass(kw_only=True)
73
+ class EstimatePayoutResponse:
74
+ network: Optional[str] = None
75
+ coin: Optional[str] = None
76
+ amount: Optional[str] = None
77
+ amount_to_receive: Optional[str] = None
78
+ to_address: Optional[str] = None
79
+ fee_info: Optional[PayoutFeeInfo] = None
80
+ sources: Optional[List[PayoutSource]] = None
81
+ service_operations: Optional[List[Dict[str, Any]]] = None
82
+ auto_convert_applied: Optional[bool] = None
83
+
84
+
85
+ @dataclass(kw_only=True)
86
+ class PayoutInfo:
87
+ uuid: str = ""
88
+ status: str = ""
89
+ order_id: Optional[str] = None
90
+ network: Optional[str] = None
91
+ coin: Optional[str] = None
92
+ amount: Optional[str] = None
93
+ to_address: Optional[str] = None
94
+ txid: Optional[str] = None
95
+ sources: Optional[List[PayoutSource]] = None
96
+ url_callback: Optional[str] = None
97
+ created_at: Optional[str] = None
98
+ updated_at: Optional[str] = None
99
+ error: Optional[str] = None
100
+
101
+
102
+ @dataclass(kw_only=True)
103
+ class BatchPayoutRequest:
104
+ """Batch body for ``/payout/batch/{estimate,execute}``. Up to 50 items per call."""
105
+
106
+ items: List[ExecutePayoutRequest]
107
+ url_callback: Optional[str] = None
108
+
109
+
110
+ @dataclass(kw_only=True)
111
+ class BatchItemResult:
112
+ index: int = 0
113
+ order_id: Optional[str] = None
114
+ status: Optional[str] = None
115
+ uuid: Optional[str] = None
116
+ error: Optional[str] = None
117
+
118
+
119
+ @dataclass(kw_only=True)
120
+ class BatchPayoutResponse:
121
+ total: int = 0
122
+ accepted: int = 0
123
+ rejected: int = 0
124
+ items: Optional[List[BatchItemResult]] = None
125
+ batch_uuid: Optional[str] = None
126
+
127
+
128
+ @dataclass(kw_only=True)
129
+ class PayoutHistoryResponse:
130
+ items: Optional[List[PayoutInfo]] = None
131
+ meta: Optional[HistoryMeta] = None
132
+
133
+
134
+ class PayoutsService(BaseService):
135
+ async def estimate(self, req: EstimatePayoutRequest) -> EstimatePayoutResponse:
136
+ """Preview fees and selected source(s) without locking funds."""
137
+ return from_dict(EstimatePayoutResponse, await self._post("/v1/payout/estimate", req))
138
+
139
+ async def execute(self, req: ExecutePayoutRequest) -> PayoutInfo:
140
+ """Create and dispatch a payout. Funds lock immediately; idempotent on ``order_id``."""
141
+ return from_dict(PayoutInfo, await self._post("/v1/payout/execute", req))
142
+
143
+ async def info(self, uuid: str) -> PayoutInfo:
144
+ """Fetch the current state of one payout by uuid."""
145
+ return from_dict(PayoutInfo, await self._post("/v1/payout/info", {"uuid": uuid}))
146
+
147
+ async def history(self, query: Optional[HistoryQuery] = None) -> PayoutHistoryResponse:
148
+ """Paged list of payouts matching the filter."""
149
+ return from_dict(
150
+ PayoutHistoryResponse, await self._post("/v1/payout/history", query or HistoryQuery())
151
+ )
152
+
153
+ async def batch_estimate(self, req: BatchPayoutRequest) -> BatchPayoutResponse:
154
+ """Preview fees for up to 50 payouts in one call."""
155
+ return from_dict(BatchPayoutResponse, await self._post("/v1/payout/batch/estimate", req))
156
+
157
+ async def batch_execute(self, req: BatchPayoutRequest) -> BatchPayoutResponse:
158
+ """Create up to 50 payouts in one call.
159
+
160
+ Bad items return their code in ``items[].error`` without blocking the
161
+ rest; funds lock sequentially so an intra-batch double-spend cannot occur.
162
+ """
163
+ return from_dict(BatchPayoutResponse, await self._post("/v1/payout/batch/execute", req))
164
+
165
+ async def wait_for(
166
+ self, uuid: str, *, interval: float = 5.0, timeout: float = 600.0
167
+ ) -> PayoutInfo:
168
+ """Poll ``info`` until the payout reaches a terminal state (or timeout)."""
169
+
170
+ async def fetch() -> PayoutInfo:
171
+ return await self.info(uuid)
172
+
173
+ return await wait_for_terminal(
174
+ fetch, lambda p: is_payout_terminal(p.status), interval=interval, timeout=timeout
175
+ )
@@ -0,0 +1,77 @@
1
+ """Read endpoints for deposits on per-customer static wallets."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import List, Optional
8
+
9
+ from .._models import from_dict
10
+ from ..pagination import HistoryMeta
11
+ from .base import BaseService
12
+
13
+
14
+ class StaticDepositStatus(str, Enum):
15
+ IN_MEMPOOL = "in_mempool"
16
+ CONFIRM_CHECK = "confirm_check"
17
+ PAID = "paid"
18
+ DROPPED = "dropped"
19
+ REORGED = "reorged"
20
+
21
+
22
+ @dataclass(kw_only=True)
23
+ class StaticDeposit:
24
+ uuid: str = ""
25
+ status: str = ""
26
+ network: Optional[str] = None
27
+ chain_family: Optional[str] = None
28
+ coin: Optional[str] = None
29
+ contract: Optional[str] = None
30
+ decimals: Optional[int] = None
31
+ to_address: Optional[str] = None
32
+ from_address: Optional[str] = None
33
+ tx_hash: Optional[str] = None
34
+ block_number: Optional[int] = None
35
+ amount: Optional[str] = None
36
+ amount_fiat: Optional[str] = None
37
+ confirmations: Optional[int] = None
38
+ required_confirmations: Optional[int] = None
39
+ found_in_mempool: Optional[bool] = None
40
+ log_type: Optional[str] = None
41
+ created_at: Optional[str] = None
42
+ updated_at: Optional[str] = None
43
+ confirmed_at: Optional[str] = None
44
+ paid_at: Optional[str] = None
45
+
46
+
47
+ @dataclass(kw_only=True)
48
+ class StaticDepositHistoryQuery:
49
+ address: Optional[str] = None
50
+ status: Optional[str] = None
51
+ coin: Optional[str] = None
52
+ network: Optional[str] = None
53
+ date_from: Optional[str] = None
54
+ date_to: Optional[str] = None
55
+ page: Optional[int] = None
56
+ page_size: Optional[int] = None
57
+
58
+
59
+ @dataclass(kw_only=True)
60
+ class StaticDepositHistoryResponse:
61
+ items: Optional[List[StaticDeposit]] = None
62
+ meta: Optional[HistoryMeta] = None
63
+
64
+
65
+ class StaticDepositsService(BaseService):
66
+ async def info(self, uuid: str) -> StaticDeposit:
67
+ """Fetch one deposit by uuid."""
68
+ return from_dict(StaticDeposit, await self._post("/v1/static-deposit/info", {"uuid": uuid}))
69
+
70
+ async def history(
71
+ self, query: Optional[StaticDepositHistoryQuery] = None
72
+ ) -> StaticDepositHistoryResponse:
73
+ """Paged list of static deposits."""
74
+ return from_dict(
75
+ StaticDepositHistoryResponse,
76
+ await self._post("/v1/static-deposit/history", query or StaticDepositHistoryQuery()),
77
+ )
@@ -0,0 +1,85 @@
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))