cryptochief-crypto-processing-python 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cryptochief/__init__.py +336 -0
- cryptochief/_models.py +93 -0
- cryptochief/_version.py +3 -0
- cryptochief/amount.py +97 -0
- cryptochief/assets.py +32 -0
- cryptochief/chains.py +115 -0
- cryptochief/client.py +189 -0
- cryptochief/contract/__init__.py +69 -0
- cryptochief/contract/base58.py +40 -0
- cryptochief/contract/borsh.py +141 -0
- cryptochief/contract/evm_abi.py +321 -0
- cryptochief/contract/keccak.py +16 -0
- cryptochief/contract/tron_address.py +60 -0
- cryptochief/errors.py +111 -0
- cryptochief/pagination.py +32 -0
- cryptochief/poll.py +56 -0
- cryptochief/rsa.py +70 -0
- cryptochief/services/__init__.py +1 -0
- cryptochief/services/base.py +24 -0
- cryptochief/services/blockchain.py +76 -0
- cryptochief/services/currencies.py +55 -0
- cryptochief/services/payins.py +145 -0
- cryptochief/services/payouts.py +175 -0
- cryptochief/services/static_deposits.py +77 -0
- cryptochief/services/sweeps.py +85 -0
- cryptochief/services/transactions.py +470 -0
- cryptochief/services/wallets.py +86 -0
- cryptochief/services/withdrawals.py +51 -0
- cryptochief/sign.py +112 -0
- cryptochief/ton/__init__.py +19 -0
- cryptochief/ton/address.py +109 -0
- cryptochief/ton/messages.py +105 -0
- cryptochief/ton/rpc.py +159 -0
- cryptochief/transport.py +48 -0
- cryptochief/webhook.py +191 -0
- cryptochief_crypto_processing_python-0.1.0.dist-info/METADATA +346 -0
- cryptochief_crypto_processing_python-0.1.0.dist-info/RECORD +39 -0
- cryptochief_crypto_processing_python-0.1.0.dist-info/WHEEL +4 -0
- cryptochief_crypto_processing_python-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -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))
|