phala-pay 0.2.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.
- phala_pay/__init__.py +96 -0
- phala_pay/_client.py +582 -0
- phala_pay/_types.py +42 -0
- phala_pay/_webhook.py +174 -0
- phala_pay/py.typed +0 -0
- phala_pay-0.2.0.dist-info/METADATA +204 -0
- phala_pay-0.2.0.dist-info/RECORD +193 -0
- phala_pay-0.2.0.dist-info/WHEEL +4 -0
- phala_pay-0.2.0.dist-info/entry_points.txt +2 -0
- phala_pay-0.2.0.dist-info/licenses/LICENSE +202 -0
- topup_client/__init__.py +8 -0
- topup_client/api/__init__.py +1 -0
- topup_client/api/account/__init__.py +1 -0
- topup_client/api/account/get_account.py +142 -0
- topup_client/api/account/pause_account.py +212 -0
- topup_client/api/account/resume_account.py +208 -0
- topup_client/api/account/roll_webhook_key.py +237 -0
- topup_client/api/account/update_account.py +220 -0
- topup_client/api/api_keys/__init__.py +1 -0
- topup_client/api/api_keys/create_api_key.py +229 -0
- topup_client/api/api_keys/get_api_key.py +173 -0
- topup_client/api/api_keys/list_api_keys.py +219 -0
- topup_client/api/api_keys/revoke_api_key.py +186 -0
- topup_client/api/api_keys/roll_api_key.py +232 -0
- topup_client/api/attestation/__init__.py +1 -0
- topup_client/api/attestation/get_attestation.py +196 -0
- topup_client/api/balance/__init__.py +1 -0
- topup_client/api/balance/get_balance.py +150 -0
- topup_client/api/config/__init__.py +1 -0
- topup_client/api/config/get_config.py +146 -0
- topup_client/api/deposit_addresses/__init__.py +1 -0
- topup_client/api/deposit_addresses/create_deposit_address.py +220 -0
- topup_client/api/deposit_addresses/get_deposit_address.py +220 -0
- topup_client/api/deposit_addresses/list_deposit_addresses.py +244 -0
- topup_client/api/deposit_addresses/rotate_deposit_address.py +206 -0
- topup_client/api/deposit_addresses/update_deposit_address.py +240 -0
- topup_client/api/deposits/__init__.py +1 -0
- topup_client/api/deposits/get_deposit.py +199 -0
- topup_client/api/deposits/list_deposits.py +368 -0
- topup_client/api/deposits/update_deposit.py +240 -0
- topup_client/api/events/__init__.py +1 -0
- topup_client/api/events/get_event.py +173 -0
- topup_client/api/events/list_events.py +327 -0
- topup_client/api/events/resend_event.py +232 -0
- topup_client/api/forwarders/__init__.py +1 -0
- topup_client/api/forwarders/list_forwarders.py +291 -0
- topup_client/api/quotes/__init__.py +1 -0
- topup_client/api/quotes/cancel_quote.py +206 -0
- topup_client/api/quotes/create_quote.py +217 -0
- topup_client/api/quotes/get_quote.py +235 -0
- topup_client/api/quotes/list_quotes.py +240 -0
- topup_client/api/quotes/update_quote.py +236 -0
- topup_client/api/refunds/__init__.py +1 -0
- topup_client/api/refunds/cancel_refund.py +222 -0
- topup_client/api/refunds/create_refund.py +233 -0
- topup_client/api/refunds/get_refund.py +199 -0
- topup_client/api/refunds/list_refunds.py +240 -0
- topup_client/api/refunds/mark_refund_paid.py +252 -0
- topup_client/api/refunds/update_refund.py +236 -0
- topup_client/api/sweeps/__init__.py +1 -0
- topup_client/api/sweeps/list_sweeps.py +263 -0
- topup_client/api/treasuries/__init__.py +1 -0
- topup_client/api/treasuries/cancel_treasury.py +206 -0
- topup_client/api/treasuries/create_treasury.py +278 -0
- topup_client/api/treasuries/create_treasury_challenge.py +221 -0
- topup_client/api/treasuries/get_treasury.py +173 -0
- topup_client/api/treasuries/list_treasuries.py +244 -0
- topup_client/api/treasuries/pause_treasury.py +214 -0
- topup_client/api/treasuries/resume_treasury.py +210 -0
- topup_client/api/webhook_endpoints/__init__.py +1 -0
- topup_client/api/webhook_endpoints/create_webhook_endpoint.py +224 -0
- topup_client/api/webhook_endpoints/delete_webhook_endpoint.py +177 -0
- topup_client/api/webhook_endpoints/get_webhook_endpoint.py +173 -0
- topup_client/api/webhook_endpoints/list_webhook_endpoints.py +210 -0
- topup_client/api/webhook_endpoints/test_webhook_endpoint.py +205 -0
- topup_client/api/webhook_endpoints/update_webhook_endpoint.py +240 -0
- topup_client/client.py +272 -0
- topup_client/errors.py +16 -0
- topup_client/models/__init__.py +203 -0
- topup_client/models/account_object.py +174 -0
- topup_client/models/account_object_object.py +13 -0
- topup_client/models/account_self_pause_request.py +53 -0
- topup_client/models/api_key_list.py +115 -0
- topup_client/models/api_key_list_object.py +13 -0
- topup_client/models/api_key_object.py +227 -0
- topup_client/models/api_key_object_object.py +13 -0
- topup_client/models/attestation_response.py +136 -0
- topup_client/models/attestation_response_object.py +15 -0
- topup_client/models/balance.py +106 -0
- topup_client/models/balance_amount.py +114 -0
- topup_client/models/balance_object.py +13 -0
- topup_client/models/client_deposit_address.py +160 -0
- topup_client/models/client_deposit_address_network.py +98 -0
- topup_client/models/client_deposit_address_object.py +15 -0
- topup_client/models/client_deposit_address_payment.py +164 -0
- topup_client/models/client_quote.py +209 -0
- topup_client/models/client_quote_object.py +13 -0
- topup_client/models/config.py +143 -0
- topup_client/models/config_asset.py +174 -0
- topup_client/models/config_object.py +13 -0
- topup_client/models/confirmation_policy.py +74 -0
- topup_client/models/create_api_key_request.py +111 -0
- topup_client/models/create_deposit_address_request.py +97 -0
- topup_client/models/create_quote_request.py +130 -0
- topup_client/models/create_refund_request.py +127 -0
- topup_client/models/create_treasury_challenge_request.py +58 -0
- topup_client/models/create_treasury_request.py +73 -0
- topup_client/models/create_webhook_endpoint_request.py +126 -0
- topup_client/models/deleted_webhook_endpoint.py +88 -0
- topup_client/models/deleted_webhook_endpoint_object.py +15 -0
- topup_client/models/delivery_attempt.py +90 -0
- topup_client/models/deposit.py +518 -0
- topup_client/models/deposit_address.py +270 -0
- topup_client/models/deposit_address_asset.py +90 -0
- topup_client/models/deposit_address_list.py +119 -0
- topup_client/models/deposit_address_list_object.py +15 -0
- topup_client/models/deposit_address_metadata.py +53 -0
- topup_client/models/deposit_address_network.py +108 -0
- topup_client/models/deposit_address_object.py +15 -0
- topup_client/models/deposit_admin.py +237 -0
- topup_client/models/deposit_event_delivery.py +117 -0
- topup_client/models/deposit_list.py +120 -0
- topup_client/models/deposit_list_object.py +13 -0
- topup_client/models/deposit_metadata.py +54 -0
- topup_client/models/deposit_object.py +13 -0
- topup_client/models/deposit_transition.py +109 -0
- topup_client/models/error_detail.py +117 -0
- topup_client/models/error_response.py +80 -0
- topup_client/models/error_type.py +15 -0
- topup_client/models/event_data.py +113 -0
- topup_client/models/event_data_object.py +53 -0
- topup_client/models/event_data_previous_attributes_type_0.py +53 -0
- topup_client/models/event_list.py +123 -0
- topup_client/models/event_list_object.py +13 -0
- topup_client/models/event_object_response.py +190 -0
- topup_client/models/event_object_response_object.py +15 -0
- topup_client/models/event_request.py +88 -0
- topup_client/models/forwarder.py +190 -0
- topup_client/models/forwarder_list.py +115 -0
- topup_client/models/forwarder_list_object.py +13 -0
- topup_client/models/forwarder_object.py +13 -0
- topup_client/models/mark_refund_paid_request.py +77 -0
- topup_client/models/metadata_clear.py +13 -0
- topup_client/models/metadata_param_type_0.py +50 -0
- topup_client/models/payment.py +187 -0
- topup_client/models/quote.py +314 -0
- topup_client/models/quote_list.py +121 -0
- topup_client/models/quote_list_object.py +13 -0
- topup_client/models/quote_metadata.py +50 -0
- topup_client/models/quote_object.py +13 -0
- topup_client/models/refund.py +249 -0
- topup_client/models/refund_list.py +116 -0
- topup_client/models/refund_list_object.py +13 -0
- topup_client/models/refund_metadata.py +50 -0
- topup_client/models/refund_object.py +13 -0
- topup_client/models/resend_event_request.py +50 -0
- topup_client/models/roll_api_key_request.py +52 -0
- topup_client/models/roll_webhook_key_request.py +53 -0
- topup_client/models/sweep.py +188 -0
- topup_client/models/sweep_list.py +117 -0
- topup_client/models/sweep_list_object.py +13 -0
- topup_client/models/sweep_object.py +13 -0
- topup_client/models/treasury.py +227 -0
- topup_client/models/treasury_challenge.py +128 -0
- topup_client/models/treasury_challenge_object.py +15 -0
- topup_client/models/treasury_list.py +115 -0
- topup_client/models/treasury_list_object.py +13 -0
- topup_client/models/treasury_object.py +13 -0
- topup_client/models/update_account_object_request.py +92 -0
- topup_client/models/update_metadata_request.py +88 -0
- topup_client/models/update_webhook_endpoint_request.py +179 -0
- topup_client/models/webhook_endpoint_list.py +116 -0
- topup_client/models/webhook_endpoint_list_object.py +15 -0
- topup_client/models/webhook_endpoint_object.py +274 -0
- topup_client/models/webhook_endpoint_object_metadata.py +50 -0
- topup_client/models/webhook_endpoint_object_object.py +15 -0
- topup_client/models/webhook_key_object.py +97 -0
- topup_client/models/webhook_key_version.py +89 -0
- topup_client/types.py +54 -0
- topup_sdk/__init__.py +79 -0
- topup_sdk/__main__.py +171 -0
- topup_sdk/addresses.py +168 -0
- topup_sdk/attestation.py +73 -0
- topup_sdk/client.py +1188 -0
- topup_sdk/errors.py +57 -0
- topup_sdk/export.py +51 -0
- topup_sdk/fulfillment.py +143 -0
- topup_sdk/ids.py +28 -0
- topup_sdk/py.typed +0 -0
- topup_sdk/signing.py +329 -0
- topup_sdk/sweeps.py +210 -0
- topup_sdk/treasury.py +47 -0
- topup_sdk/webhooks.py +162 -0
phala_pay/__init__.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"""Phala Pay for Python, in the shape of Stripe's.
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
5
|
+
from phala_pay import PhalaPay
|
|
6
|
+
|
|
7
|
+
pay = PhalaPay(
|
|
8
|
+
api_base="https://pay.example.com",
|
|
9
|
+
api_key=os.environ["PHALA_PAY_KEY"],
|
|
10
|
+
forwarder=(FACTORY, IMPLEMENTATION),
|
|
11
|
+
)
|
|
12
|
+
quote = pay.quotes.create(
|
|
13
|
+
client_reference_id="team-42", amount=2500, chain_id=11155111, asset="pha"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
event = pay.webhooks.construct_event(
|
|
17
|
+
raw_body, request.headers, WEBHOOK_PUBLIC_KEY, "acct_…", expected_livemode=False
|
|
18
|
+
)
|
|
19
|
+
if event.type == "deposit.credited":
|
|
20
|
+
credit_once(event.deposit.id, event.deposit.client_reference_id, event.deposit.amount)
|
|
21
|
+
|
|
22
|
+
`topup_sdk` holds the lower-level pieces (address derivation, attestation, webhook and admin
|
|
23
|
+
request signatures, offline `flush_transaction` and `safe_batch`, `export_account`) and
|
|
24
|
+
`topup_client` the client generated from the OpenAPI document.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from topup_client.models import (
|
|
28
|
+
Balance,
|
|
29
|
+
ClientQuote,
|
|
30
|
+
Config,
|
|
31
|
+
Deposit,
|
|
32
|
+
DepositAddress,
|
|
33
|
+
Forwarder,
|
|
34
|
+
Quote,
|
|
35
|
+
Refund,
|
|
36
|
+
Sweep,
|
|
37
|
+
Treasury,
|
|
38
|
+
)
|
|
39
|
+
from topup_sdk import (
|
|
40
|
+
AddressMismatchError,
|
|
41
|
+
ApiError,
|
|
42
|
+
TopupError,
|
|
43
|
+
flush_transaction,
|
|
44
|
+
flush_transactions,
|
|
45
|
+
safe_batch,
|
|
46
|
+
write_safe_batch,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
from ._client import PhalaPay
|
|
50
|
+
from ._types import (
|
|
51
|
+
ApiKeyStatus,
|
|
52
|
+
DepositAddressStatus,
|
|
53
|
+
DepositStatus,
|
|
54
|
+
EventType,
|
|
55
|
+
PaymentStatus,
|
|
56
|
+
QuoteStatus,
|
|
57
|
+
RefundStatus,
|
|
58
|
+
TreasuryStatus,
|
|
59
|
+
WebhookEndpointStatus,
|
|
60
|
+
)
|
|
61
|
+
from ._webhook import Event, EventData, EventRequest, SignatureVerificationError, Webhook
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
"AddressMismatchError",
|
|
65
|
+
"ApiError",
|
|
66
|
+
"ApiKeyStatus",
|
|
67
|
+
"Balance",
|
|
68
|
+
"ClientQuote",
|
|
69
|
+
"Config",
|
|
70
|
+
"Deposit",
|
|
71
|
+
"DepositAddress",
|
|
72
|
+
"DepositAddressStatus",
|
|
73
|
+
"DepositStatus",
|
|
74
|
+
"Event",
|
|
75
|
+
"EventData",
|
|
76
|
+
"EventRequest",
|
|
77
|
+
"EventType",
|
|
78
|
+
"Forwarder",
|
|
79
|
+
"PaymentStatus",
|
|
80
|
+
"PhalaPay",
|
|
81
|
+
"Quote",
|
|
82
|
+
"QuoteStatus",
|
|
83
|
+
"Refund",
|
|
84
|
+
"RefundStatus",
|
|
85
|
+
"SignatureVerificationError",
|
|
86
|
+
"Sweep",
|
|
87
|
+
"TopupError",
|
|
88
|
+
"Treasury",
|
|
89
|
+
"TreasuryStatus",
|
|
90
|
+
"Webhook",
|
|
91
|
+
"WebhookEndpointStatus",
|
|
92
|
+
"flush_transaction",
|
|
93
|
+
"flush_transactions",
|
|
94
|
+
"safe_batch",
|
|
95
|
+
"write_safe_batch",
|
|
96
|
+
]
|
phala_pay/_client.py
ADDED
|
@@ -0,0 +1,582 @@
|
|
|
1
|
+
"""`PhalaPay`: the Phala Pay merchant API as Stripe-style resources over `TopupClient`."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Iterator, Mapping, Sequence
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
from topup_client.models import (
|
|
11
|
+
AccountObject,
|
|
12
|
+
ApiKeyObject,
|
|
13
|
+
Balance,
|
|
14
|
+
Config,
|
|
15
|
+
DeletedWebhookEndpoint,
|
|
16
|
+
Deposit,
|
|
17
|
+
DepositAddress,
|
|
18
|
+
EventObjectResponse,
|
|
19
|
+
Forwarder,
|
|
20
|
+
Quote,
|
|
21
|
+
Refund,
|
|
22
|
+
Sweep,
|
|
23
|
+
Treasury,
|
|
24
|
+
TreasuryChallenge,
|
|
25
|
+
WebhookEndpointObject,
|
|
26
|
+
)
|
|
27
|
+
from topup_sdk import TopupClient, export_account, sign_treasury_challenge
|
|
28
|
+
from topup_sdk.client import Metadata
|
|
29
|
+
|
|
30
|
+
from ._types import (
|
|
31
|
+
DepositAddressStatus,
|
|
32
|
+
DepositStatus,
|
|
33
|
+
EventType,
|
|
34
|
+
QuoteStatus,
|
|
35
|
+
RefundStatus,
|
|
36
|
+
TreasuryStatus,
|
|
37
|
+
)
|
|
38
|
+
from ._webhook import Webhook
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class PhalaPay:
|
|
42
|
+
"""A client for one account and mode, authenticated with its API key.
|
|
43
|
+
|
|
44
|
+
pay = PhalaPay(
|
|
45
|
+
api_base="https://pay.example.com",
|
|
46
|
+
api_key=os.environ["PHALA_PAY_KEY"],
|
|
47
|
+
account="acct_…",
|
|
48
|
+
forwarder=(FACTORY, IMPLEMENTATION),
|
|
49
|
+
treasuries={1: "0x…your treasury on Ethereum"},
|
|
50
|
+
)
|
|
51
|
+
quote = pay.quotes.create(client_reference_id="team-42", amount=2500,
|
|
52
|
+
chain_id=11155111, asset="pha")
|
|
53
|
+
return {"client_secret": quote.client_secret, "expected_address": quote.address}
|
|
54
|
+
|
|
55
|
+
`api_key` is a restricted key (`ppay_rk_…`, recommended for a production server) or a secret
|
|
56
|
+
key (`ppay_sk_…`, for administration); the key selects the account and the mode. Every quote
|
|
57
|
+
and deposit address is recomputed before it is returned from pins you configure yourself:
|
|
58
|
+
`forwarder`, the `(factory, implementation)` pair pinned from the attested deployment;
|
|
59
|
+
`treasuries` (`{chain_id: treasury}`), your own treasury per chain; and `account` (`acct_…`).
|
|
60
|
+
The service's `treasury` is never trusted, and an address you cannot derive raises
|
|
61
|
+
`AddressMismatchError` (fail closed). A live key requires all three pins: without them every
|
|
62
|
+
address check raises. In test mode `account` is read from `GET /v1/account` when not given,
|
|
63
|
+
and without `treasuries` the service's treasury is used with an `UnpinnedTreasuryWarning`.
|
|
64
|
+
|
|
65
|
+
Requests that fail with a transport error, `429` (after its `Retry-After`), or `5xx` are
|
|
66
|
+
retried with backoff; `POST`s reuse one `Idempotency-Key` across retries, so a retry never
|
|
67
|
+
creates a second object, and a response the service saved for the key (even a `500`) is
|
|
68
|
+
returned as it was, not retried. A failed request raises `ApiError` with the service's
|
|
69
|
+
`code`, `param`, `doc_url`, and `request_id`.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
api_base: str,
|
|
75
|
+
api_key: str,
|
|
76
|
+
*,
|
|
77
|
+
forwarder: tuple[str, str],
|
|
78
|
+
treasuries: Mapping[int, str] | None = None,
|
|
79
|
+
account: str | None = None,
|
|
80
|
+
timeout: float = 15.0,
|
|
81
|
+
max_attempts: int = 4,
|
|
82
|
+
transport: httpx.BaseTransport | None = None,
|
|
83
|
+
) -> None:
|
|
84
|
+
self._client = TopupClient(
|
|
85
|
+
api_base,
|
|
86
|
+
api_key,
|
|
87
|
+
account=account,
|
|
88
|
+
forwarder=forwarder,
|
|
89
|
+
treasuries=treasuries,
|
|
90
|
+
timeout=timeout,
|
|
91
|
+
max_attempts=max_attempts,
|
|
92
|
+
transport=transport,
|
|
93
|
+
)
|
|
94
|
+
self.account = AccountResource(self._client)
|
|
95
|
+
self.config = ConfigResource(self._client)
|
|
96
|
+
self.quotes = Quotes(self._client)
|
|
97
|
+
self.deposits = Deposits(self._client)
|
|
98
|
+
self.deposit_addresses = DepositAddresses(self._client)
|
|
99
|
+
self.refunds = Refunds(self._client)
|
|
100
|
+
self.balance = BalanceResource(self._client)
|
|
101
|
+
self.sweeps = Sweeps(self._client)
|
|
102
|
+
self.forwarders = Forwarders(self._client)
|
|
103
|
+
self.treasuries = Treasuries(self._client)
|
|
104
|
+
self.api_keys = ApiKeys(self._client)
|
|
105
|
+
self.webhook_endpoints = WebhookEndpoints(self._client)
|
|
106
|
+
self.events = Events(self._client)
|
|
107
|
+
self.webhooks = Webhook
|
|
108
|
+
|
|
109
|
+
def export_account(self, directory: str | Path) -> dict[str, int]:
|
|
110
|
+
"""Writes every object of the key's account and mode to `directory`, one JSON file per
|
|
111
|
+
resource, and returns the counts (design §13). `forwarders.json` keeps funds sweepable
|
|
112
|
+
without Phala Pay."""
|
|
113
|
+
return export_account(self._client, directory)
|
|
114
|
+
|
|
115
|
+
def close(self) -> None:
|
|
116
|
+
self._client.close()
|
|
117
|
+
|
|
118
|
+
def __enter__(self) -> PhalaPay:
|
|
119
|
+
return self
|
|
120
|
+
|
|
121
|
+
def __exit__(self, *_: object) -> None:
|
|
122
|
+
self.close()
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
class AccountResource:
|
|
126
|
+
def __init__(self, client: TopupClient) -> None:
|
|
127
|
+
self._client = client
|
|
128
|
+
|
|
129
|
+
def retrieve(self) -> AccountObject:
|
|
130
|
+
"""The key's account, in the key's mode."""
|
|
131
|
+
return self._client.get_account()
|
|
132
|
+
|
|
133
|
+
def update(self, *, confirmation_policies: Mapping[int, str | None]) -> AccountObject:
|
|
134
|
+
"""Requires, per chain, a confirmation stricter than the route's before a payment is
|
|
135
|
+
credited: a depth such as `"12"`, `"safe"`, or `"finalized"`; `None` restores the
|
|
136
|
+
route's."""
|
|
137
|
+
return self._client.update_account(confirmation_policies=confirmation_policies)
|
|
138
|
+
|
|
139
|
+
def pause_quotes(self) -> AccountObject:
|
|
140
|
+
"""Stops issuing quotes, deposit addresses, and networks in both modes, for an
|
|
141
|
+
emergency; existing addresses keep being credited."""
|
|
142
|
+
return self._client.pause_quotes()
|
|
143
|
+
|
|
144
|
+
def resume_quotes(self) -> AccountObject:
|
|
145
|
+
"""Lifts your own `quotes` pause; an operator's pause stays."""
|
|
146
|
+
return self._client.resume_quotes()
|
|
147
|
+
|
|
148
|
+
def roll_webhook_key(self, *, expires_in: int = 172_800) -> AccountObject:
|
|
149
|
+
"""Rolls this mode's webhook signing key; the old one signs beside it for `expires_in`
|
|
150
|
+
seconds: 48 hours (the default) to 7 days live, `0` to 7 days in test mode."""
|
|
151
|
+
return self._client.roll_webhook_key(expires_in=expires_in)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class ConfigResource:
|
|
155
|
+
def __init__(self, client: TopupClient) -> None:
|
|
156
|
+
self._client = client
|
|
157
|
+
|
|
158
|
+
def retrieve(self) -> Config:
|
|
159
|
+
"""The payable assets, limits, quote terms, and confirmations, for the product's UI."""
|
|
160
|
+
return self._client.get_config()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class Quotes:
|
|
164
|
+
def __init__(self, client: TopupClient) -> None:
|
|
165
|
+
self._client = client
|
|
166
|
+
|
|
167
|
+
def create(
|
|
168
|
+
self,
|
|
169
|
+
*,
|
|
170
|
+
client_reference_id: str,
|
|
171
|
+
amount: int,
|
|
172
|
+
chain_id: int,
|
|
173
|
+
asset: str,
|
|
174
|
+
currency: str = "usd",
|
|
175
|
+
idempotency_key: str | None = None,
|
|
176
|
+
metadata: Mapping[str, str] | None = None,
|
|
177
|
+
) -> Quote:
|
|
178
|
+
"""Quotes `amount` cents for the customer `client_reference_id`, payable in `asset` on
|
|
179
|
+
`chain_id`.
|
|
180
|
+
|
|
181
|
+
Only this response carries `client_secret`, the value the payer's browser needs, beside
|
|
182
|
+
the recomputed `address` to pass as `<Checkout expectedAddress>`. Pass an
|
|
183
|
+
`idempotency_key` of your own (for example your order id) to resume a checkout: for 24
|
|
184
|
+
hours a repeat of the same request replays the first response, the same quote and the
|
|
185
|
+
same `client_secret` included.
|
|
186
|
+
|
|
187
|
+
`metadata` is Stripe's: up to 50 string pairs for your own use, such as your order id,
|
|
188
|
+
copied to the deposit that pays the quote. Do not store sensitive information in it.
|
|
189
|
+
"""
|
|
190
|
+
return self._client.create_quote(
|
|
191
|
+
client_reference_id,
|
|
192
|
+
amount,
|
|
193
|
+
chain_id=chain_id,
|
|
194
|
+
asset=asset,
|
|
195
|
+
currency=currency,
|
|
196
|
+
idempotency_key=idempotency_key,
|
|
197
|
+
metadata=metadata,
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
def retrieve(self, quote_id: str) -> Quote:
|
|
201
|
+
return self._client.get_quote(quote_id)
|
|
202
|
+
|
|
203
|
+
def list(
|
|
204
|
+
self, *, client_reference_id: str | None = None, status: QuoteStatus | None = None
|
|
205
|
+
) -> Iterator[Quote]:
|
|
206
|
+
"""Yields every matching quote, newest first, fetching pages as it goes."""
|
|
207
|
+
return self._client.list_quotes(client_reference_id=client_reference_id, status=status)
|
|
208
|
+
|
|
209
|
+
def update(self, quote_id: str, *, metadata: Metadata | None = None) -> Quote:
|
|
210
|
+
"""Merges `metadata` into the quote's: a key set to `""` is unset, and `metadata=""`
|
|
211
|
+
unsets every key."""
|
|
212
|
+
return self._client.update_quote(quote_id, metadata=metadata)
|
|
213
|
+
|
|
214
|
+
def cancel(self, quote_id: str) -> Quote:
|
|
215
|
+
"""Cancels an open quote no payment has reached; repeating it returns the canceled quote."""
|
|
216
|
+
return self._client.cancel_quote(quote_id)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class Deposits:
|
|
220
|
+
def __init__(self, client: TopupClient) -> None:
|
|
221
|
+
self._client = client
|
|
222
|
+
|
|
223
|
+
# Before `list`, whose name would shadow the builtin in later annotations.
|
|
224
|
+
def retrieve(self, deposit_id: str, *, expand: list[str] | None = None) -> Deposit:
|
|
225
|
+
return self._client.get_deposit(deposit_id, expand=expand)
|
|
226
|
+
|
|
227
|
+
def update(self, deposit_id: str, *, metadata: Metadata | None = None) -> Deposit:
|
|
228
|
+
"""Merges `metadata` into the deposit's, which started as a copy of its quote's or its
|
|
229
|
+
deposit address's; theirs are left unchanged."""
|
|
230
|
+
return self._client.update_deposit(deposit_id, metadata=metadata)
|
|
231
|
+
|
|
232
|
+
def list(
|
|
233
|
+
self,
|
|
234
|
+
*,
|
|
235
|
+
client_reference_id: str | None = None,
|
|
236
|
+
quote: str | None = None,
|
|
237
|
+
deposit_address: str | None = None,
|
|
238
|
+
status: DepositStatus | None = None,
|
|
239
|
+
tx_hash: str | None = None,
|
|
240
|
+
created_gt: int | None = None,
|
|
241
|
+
created_gte: int | None = None,
|
|
242
|
+
created_lt: int | None = None,
|
|
243
|
+
created_lte: int | None = None,
|
|
244
|
+
expand: list[str] | None = None,
|
|
245
|
+
) -> Iterator[Deposit]:
|
|
246
|
+
"""Yields every matching deposit, newest first, fetching pages as it goes. `created_*`
|
|
247
|
+
are Unix seconds (Stripe's `created[gt|gte|lt|lte]`)."""
|
|
248
|
+
return self._client.list_deposits(
|
|
249
|
+
client_reference_id=client_reference_id,
|
|
250
|
+
quote=quote,
|
|
251
|
+
deposit_address=deposit_address,
|
|
252
|
+
status=status,
|
|
253
|
+
tx_hash=tx_hash,
|
|
254
|
+
created_gt=created_gt,
|
|
255
|
+
created_gte=created_gte,
|
|
256
|
+
created_lt=created_lt,
|
|
257
|
+
created_lte=created_lte,
|
|
258
|
+
expand=expand,
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class DepositAddresses:
|
|
263
|
+
"""A customer's persistent deposit address: one address for every supported token on every
|
|
264
|
+
supported network. Show it like a bank account number; any amount of a supported token sent
|
|
265
|
+
to it is credited at the market rate when it arrives. `networks` lists each chain's address
|
|
266
|
+
(the same wherever the treasury is the same; `address` is it when all agree) and tokens, and
|
|
267
|
+
`payments` the transfers seen and recorded in the last 24 hours.
|
|
268
|
+
|
|
269
|
+
`topup_sdk.deposit_address(...)` recomputes any version offline from its salt inputs and a
|
|
270
|
+
network's treasury.
|
|
271
|
+
"""
|
|
272
|
+
|
|
273
|
+
def __init__(self, client: TopupClient) -> None:
|
|
274
|
+
self._client = client
|
|
275
|
+
|
|
276
|
+
def create(
|
|
277
|
+
self,
|
|
278
|
+
*,
|
|
279
|
+
client_reference_id: str,
|
|
280
|
+
metadata: Metadata | None = None,
|
|
281
|
+
) -> DepositAddress:
|
|
282
|
+
"""Returns the customer's active address, recomputed; the same call keeps returning it
|
|
283
|
+
until it is rotated, and adds a network supported since. Each response carries a new
|
|
284
|
+
`client_secret` for the customer's page (`<DepositAddress clientSecret>`) to follow
|
|
285
|
+
payments. `metadata` is merged into the address's; each deposit to it starts with a
|
|
286
|
+
copy, and a rotation carries it to the next address."""
|
|
287
|
+
return self._client.create_deposit_address(client_reference_id, metadata=metadata)
|
|
288
|
+
|
|
289
|
+
def update(
|
|
290
|
+
self, deposit_address_id: str, *, metadata: Metadata | None = None
|
|
291
|
+
) -> DepositAddress:
|
|
292
|
+
"""Merges `metadata` into the address's (a key set to `""` is unset, `""` unsets all)."""
|
|
293
|
+
return self._client.update_deposit_address(deposit_address_id, metadata=metadata)
|
|
294
|
+
|
|
295
|
+
def retrieve(self, deposit_address_id: str) -> DepositAddress:
|
|
296
|
+
return self._client.get_deposit_address(deposit_address_id)
|
|
297
|
+
|
|
298
|
+
def list(
|
|
299
|
+
self,
|
|
300
|
+
*,
|
|
301
|
+
client_reference_id: str | None = None,
|
|
302
|
+
status: DepositAddressStatus | None = None,
|
|
303
|
+
) -> Iterator[DepositAddress]:
|
|
304
|
+
"""Yields every matching deposit address, newest first, fetching pages as it goes."""
|
|
305
|
+
return self._client.list_deposit_addresses(
|
|
306
|
+
client_reference_id=client_reference_id, status=status
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
def rotate(
|
|
310
|
+
self, deposit_address_id: str, *, idempotency_key: str | None = None
|
|
311
|
+
) -> DepositAddress:
|
|
312
|
+
"""Retires the address and returns the customer's new one, a new address on every
|
|
313
|
+
network; the retired address is still credited, so stop showing it rather than telling
|
|
314
|
+
the customer it is invalid."""
|
|
315
|
+
return self._client.rotate_deposit_address(
|
|
316
|
+
deposit_address_id, idempotency_key=idempotency_key
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
class Refunds:
|
|
321
|
+
def __init__(self, client: TopupClient) -> None:
|
|
322
|
+
self._client = client
|
|
323
|
+
|
|
324
|
+
def create(
|
|
325
|
+
self,
|
|
326
|
+
*,
|
|
327
|
+
deposit: str,
|
|
328
|
+
destination_address: str,
|
|
329
|
+
amount_atomic: int | None = None,
|
|
330
|
+
idempotency_key: str | None = None,
|
|
331
|
+
metadata: Mapping[str, str] | None = None,
|
|
332
|
+
) -> Refund:
|
|
333
|
+
"""Creates a pending refund of a final `deposit` (its unrefunded remainder unless
|
|
334
|
+
`amount_atomic` is given) to an address the customer controls; pay it from its
|
|
335
|
+
`treasury`, then call `mark_paid`."""
|
|
336
|
+
return self._client.create_refund(
|
|
337
|
+
deposit,
|
|
338
|
+
destination_address,
|
|
339
|
+
amount_atomic,
|
|
340
|
+
idempotency_key=idempotency_key,
|
|
341
|
+
metadata=metadata,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
def mark_paid(
|
|
345
|
+
self, refund_id: str, *, transaction_hash: str, receipt_log_index: int | None = None
|
|
346
|
+
) -> Refund:
|
|
347
|
+
"""Attaches the transaction that pays the refund; it is verified at finality. From then
|
|
348
|
+
on the refund cannot be canceled: it stays pending until it succeeds, or fails when the
|
|
349
|
+
transaction does not pay it or is proven dropped."""
|
|
350
|
+
return self._client.mark_refund_paid(
|
|
351
|
+
refund_id, transaction_hash, receipt_log_index=receipt_log_index
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
def cancel(self, refund_id: str) -> Refund:
|
|
355
|
+
"""Cancels a pending refund."""
|
|
356
|
+
return self._client.cancel_refund(refund_id)
|
|
357
|
+
|
|
358
|
+
def retrieve(self, refund_id: str, *, expand: list[str] | None = None) -> Refund:
|
|
359
|
+
return self._client.get_refund(refund_id, expand=expand)
|
|
360
|
+
|
|
361
|
+
def update(self, refund_id: str, *, metadata: Metadata | None = None) -> Refund:
|
|
362
|
+
"""Merges `metadata` into the refund's."""
|
|
363
|
+
return self._client.update_refund(refund_id, metadata=metadata)
|
|
364
|
+
|
|
365
|
+
def list(
|
|
366
|
+
self, *, deposit: str | None = None, status: RefundStatus | None = None
|
|
367
|
+
) -> Iterator[Refund]:
|
|
368
|
+
"""Yields every matching refund, newest first, fetching pages as it goes."""
|
|
369
|
+
return self._client.list_refunds(deposit=deposit, status=status)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
class BalanceResource:
|
|
373
|
+
def __init__(self, client: TopupClient) -> None:
|
|
374
|
+
self._client = client
|
|
375
|
+
|
|
376
|
+
def retrieve(self) -> Balance:
|
|
377
|
+
"""What the account's forwarders hold, per chain and token, and the final part of it."""
|
|
378
|
+
return self._client.get_balance()
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class Sweeps:
|
|
382
|
+
"""Sweeps: finalized `Flushed` events that moved a forwarder's balance to its treasury.
|
|
383
|
+
Build the `flush` calls offline with `topup_sdk.flush_transactions` from
|
|
384
|
+
`forwarders.list(sweepable=token)`, and for a Safe treasury write them with
|
|
385
|
+
`topup_sdk.safe_batch`."""
|
|
386
|
+
|
|
387
|
+
def __init__(self, client: TopupClient) -> None:
|
|
388
|
+
self._client = client
|
|
389
|
+
|
|
390
|
+
def list(
|
|
391
|
+
self,
|
|
392
|
+
*,
|
|
393
|
+
chain_id: int | None = None,
|
|
394
|
+
forwarder: str | None = None,
|
|
395
|
+
token: str | None = None,
|
|
396
|
+
) -> Iterator[Sweep]:
|
|
397
|
+
"""Yields the sweeps, newest first, fetching pages as it goes."""
|
|
398
|
+
return self._client.list_sweeps(chain_id=chain_id, forwarder=forwarder, token=token)
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class Forwarders:
|
|
402
|
+
def __init__(self, client: TopupClient) -> None:
|
|
403
|
+
self._client = client
|
|
404
|
+
|
|
405
|
+
def list(
|
|
406
|
+
self,
|
|
407
|
+
*,
|
|
408
|
+
chain_id: int | None = None,
|
|
409
|
+
quote: str | None = None,
|
|
410
|
+
deposit_address: str | None = None,
|
|
411
|
+
sweepable: str | None = None,
|
|
412
|
+
) -> Iterator[Forwarder]:
|
|
413
|
+
"""Yields the account's forwarders with their `(factory, salt, treasury)`: all of them, or
|
|
414
|
+
those of one `quote` or `deposit_address`; with `sweepable` (a token contract), only those
|
|
415
|
+
safe to sweep of it."""
|
|
416
|
+
return self._client.list_forwarders(
|
|
417
|
+
chain_id=chain_id, quote=quote, deposit_address=deposit_address, sweepable=sweepable
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class Treasuries:
|
|
422
|
+
"""Treasuries, proven through the API (design D10): an EOA signs the challenge with
|
|
423
|
+
`set_eoa`; a Safe's owners sign it as a Safe message and `create` submits it
|
|
424
|
+
(docs/integration.md, "Set a Safe as treasury")."""
|
|
425
|
+
|
|
426
|
+
def __init__(self, client: TopupClient) -> None:
|
|
427
|
+
self._client = client
|
|
428
|
+
|
|
429
|
+
def challenge(self, *, chain_id: int, address: str) -> TreasuryChallenge:
|
|
430
|
+
"""The EIP-4361 message that proves `address` as the treasury of `chain_id`."""
|
|
431
|
+
return self._client.create_treasury_challenge(chain_id, address)
|
|
432
|
+
|
|
433
|
+
def create(self, *, chain_id: int, message: str, signature: str) -> Treasury:
|
|
434
|
+
"""Submits a signed challenge `message`."""
|
|
435
|
+
return self._client.create_treasury(chain_id, message, signature)
|
|
436
|
+
|
|
437
|
+
def set_eoa(self, *, chain_id: int, address: str, private_key: str | bytes) -> Treasury:
|
|
438
|
+
"""Proves the EOA `address` as the treasury of `chain_id`: requests a challenge, signs it
|
|
439
|
+
with `private_key` (which must be `address`'s; needs `phala-pay[eoa]`), and submits it."""
|
|
440
|
+
challenge = self.challenge(chain_id=chain_id, address=address)
|
|
441
|
+
signature = sign_treasury_challenge(challenge.message, private_key, address=address)
|
|
442
|
+
return self.create(chain_id=chain_id, message=challenge.message, signature=signature)
|
|
443
|
+
|
|
444
|
+
def retrieve(self, treasury_id: str) -> Treasury:
|
|
445
|
+
return self._client.get_treasury(treasury_id)
|
|
446
|
+
|
|
447
|
+
def list(
|
|
448
|
+
self, *, chain_id: int | None = None, status: TreasuryStatus | None = None
|
|
449
|
+
) -> list[Treasury]:
|
|
450
|
+
"""The treasuries of this mode, newest first."""
|
|
451
|
+
return self._client.list_treasuries(chain_id=chain_id, status=status)
|
|
452
|
+
|
|
453
|
+
def cancel(self, treasury_id: str) -> Treasury:
|
|
454
|
+
"""Cancels a pending treasury change before it applies."""
|
|
455
|
+
return self._client.cancel_treasury(treasury_id)
|
|
456
|
+
|
|
457
|
+
def pause(self, treasury_id: str) -> Treasury:
|
|
458
|
+
"""Pauses crediting of deposits to every forwarder over the treasury, for an incident
|
|
459
|
+
such as a compromised former treasury: they stay `pending` until `resume`."""
|
|
460
|
+
return self._client.pause_treasury(treasury_id)
|
|
461
|
+
|
|
462
|
+
def resume(self, treasury_id: str) -> Treasury:
|
|
463
|
+
"""Lifts your crediting pause; an operator's pause stays."""
|
|
464
|
+
return self._client.resume_treasury(treasury_id)
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class ApiKeys:
|
|
468
|
+
def __init__(self, client: TopupClient) -> None:
|
|
469
|
+
self._client = client
|
|
470
|
+
|
|
471
|
+
def create(self, *, name: str = "", permissions: Sequence[str] | None = None) -> ApiKeyObject:
|
|
472
|
+
"""A new key of this mode; its `secret` is in this response only. With `permissions`
|
|
473
|
+
(such as `["quotes.write", "deposit_addresses.write", "deposits.read", "events.read",
|
|
474
|
+
"refunds.read", "account.read"]`) it is a restricted key, `ppay_rk_…`, which never
|
|
475
|
+
manages keys, treasuries, webhook endpoints, webhook keys, or account settings: run
|
|
476
|
+
production with one and keep the secret key offline."""
|
|
477
|
+
return self._client.create_api_key(name=name, permissions=permissions)
|
|
478
|
+
|
|
479
|
+
def retrieve(self, api_key_id: str) -> ApiKeyObject:
|
|
480
|
+
return self._client.get_api_key(api_key_id)
|
|
481
|
+
|
|
482
|
+
def list(self) -> list[ApiKeyObject]:
|
|
483
|
+
"""This mode's keys, without their secrets."""
|
|
484
|
+
return self._client.list_api_keys()
|
|
485
|
+
|
|
486
|
+
def roll(self, api_key_id: str, *, expires_in: int = 0) -> ApiKeyObject:
|
|
487
|
+
"""A replacement key of the same kind and permissions with its `secret`; the old one
|
|
488
|
+
works for `expires_in` seconds."""
|
|
489
|
+
return self._client.roll_api_key(api_key_id, expires_in=expires_in)
|
|
490
|
+
|
|
491
|
+
def revoke(self, api_key_id: str) -> ApiKeyObject:
|
|
492
|
+
return self._client.revoke_api_key(api_key_id)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
class WebhookEndpoints:
|
|
496
|
+
def __init__(self, client: TopupClient) -> None:
|
|
497
|
+
self._client = client
|
|
498
|
+
|
|
499
|
+
def create(
|
|
500
|
+
self,
|
|
501
|
+
*,
|
|
502
|
+
url: str,
|
|
503
|
+
enabled_events: list[str],
|
|
504
|
+
description: str | None = None,
|
|
505
|
+
metadata: Mapping[str, str] | None = None,
|
|
506
|
+
) -> WebhookEndpointObject:
|
|
507
|
+
return self._client.create_webhook_endpoint(
|
|
508
|
+
url, enabled_events, description=description, metadata=metadata
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
def retrieve(self, endpoint_id: str) -> WebhookEndpointObject:
|
|
512
|
+
return self._client.get_webhook_endpoint(endpoint_id)
|
|
513
|
+
|
|
514
|
+
def update(
|
|
515
|
+
self,
|
|
516
|
+
endpoint_id: str,
|
|
517
|
+
*,
|
|
518
|
+
url: str | None = None,
|
|
519
|
+
enabled_events: list[str] | None = None,
|
|
520
|
+
description: str | None = None,
|
|
521
|
+
disabled: bool | None = None,
|
|
522
|
+
metadata: Metadata | None = None,
|
|
523
|
+
) -> WebhookEndpointObject:
|
|
524
|
+
return self._client.update_webhook_endpoint(
|
|
525
|
+
endpoint_id,
|
|
526
|
+
url=url,
|
|
527
|
+
enabled_events=enabled_events,
|
|
528
|
+
description=description,
|
|
529
|
+
disabled=disabled,
|
|
530
|
+
metadata=metadata,
|
|
531
|
+
)
|
|
532
|
+
|
|
533
|
+
def delete(self, endpoint_id: str) -> DeletedWebhookEndpoint:
|
|
534
|
+
return self._client.delete_webhook_endpoint(endpoint_id)
|
|
535
|
+
|
|
536
|
+
def test(self, endpoint_id: str) -> EventObjectResponse:
|
|
537
|
+
"""Sends a test event to the endpoint."""
|
|
538
|
+
return self._client.test_webhook_endpoint(endpoint_id)
|
|
539
|
+
|
|
540
|
+
def list(self) -> Iterator[WebhookEndpointObject]:
|
|
541
|
+
return self._client.list_webhook_endpoints()
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
class Events:
|
|
545
|
+
"""Events of this mode: every webhook ever sent, and the account's audit log."""
|
|
546
|
+
|
|
547
|
+
def __init__(self, client: TopupClient) -> None:
|
|
548
|
+
self._client = client
|
|
549
|
+
|
|
550
|
+
def retrieve(self, event_id: str) -> EventObjectResponse:
|
|
551
|
+
return self._client.get_event(event_id)
|
|
552
|
+
|
|
553
|
+
def resend(self, event_id: str, *, webhook_endpoint: str) -> EventObjectResponse:
|
|
554
|
+
"""Delivers the event again to one enabled endpoint."""
|
|
555
|
+
return self._client.resend_event(event_id, webhook_endpoint=webhook_endpoint)
|
|
556
|
+
|
|
557
|
+
def list(
|
|
558
|
+
self,
|
|
559
|
+
*,
|
|
560
|
+
type: EventType | str | None = None,
|
|
561
|
+
types: Sequence[EventType | str] | None = None,
|
|
562
|
+
delivery_success: bool | None = None,
|
|
563
|
+
created_gt: int | None = None,
|
|
564
|
+
created_gte: int | None = None,
|
|
565
|
+
created_lt: int | None = None,
|
|
566
|
+
created_lte: int | None = None,
|
|
567
|
+
) -> Iterator[EventObjectResponse]:
|
|
568
|
+
"""Yields events, newest first. `type` filters by one type, such as `deposit.reversed`,
|
|
569
|
+
or a group, `deposit.*`; `types` by up to 20. `delivery_success=False` yields the events
|
|
570
|
+
a webhook endpoint has not received: its `pending_deliveries`, `oldest_pending_at`, and
|
|
571
|
+
`last_attempt` say whether it is keeping up; resend the missed events once it is fixed.
|
|
572
|
+
`data.object` is the object as it was when the event happened; `*.updated` events carry
|
|
573
|
+
`data.previous_attributes`, and events caused by your requests their `request`."""
|
|
574
|
+
return self._client.list_events(
|
|
575
|
+
type=type,
|
|
576
|
+
types=types,
|
|
577
|
+
delivery_success=delivery_success,
|
|
578
|
+
created_gt=created_gt,
|
|
579
|
+
created_gte=created_gte,
|
|
580
|
+
created_lt=created_lt,
|
|
581
|
+
created_lte=created_lte,
|
|
582
|
+
)
|