swig-developer-sdk 0.6.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.
@@ -0,0 +1,277 @@
1
+ from __future__ import annotations
2
+
3
+ import base64
4
+ from collections.abc import Mapping, Sequence
5
+ from dataclasses import dataclass
6
+ from typing import Literal
7
+
8
+ import base58
9
+
10
+ from .common import (
11
+ Network,
12
+ WalletAddressInfo,
13
+ normalize_network,
14
+ wallet_address_info_from_wire,
15
+ )
16
+ from .core import HttpClient
17
+
18
+ TransactionEncoding = Literal["base64"]
19
+ PreparedTransactionKind = Literal[
20
+ "create-swig-wallet", "add-authority", "configure-recovery"
21
+ ]
22
+ SignatureScheme = Literal["secp256r1", "secp256k1"]
23
+
24
+
25
+ @dataclass(frozen=True, slots=True)
26
+ class ClientSignatureRequest:
27
+ scheme: SignatureScheme
28
+ signer: str
29
+ message_hash: str
30
+ slot: int
31
+ counter: int
32
+
33
+
34
+ @dataclass(frozen=True, slots=True)
35
+ class PreparedTransaction:
36
+ transaction: str
37
+ signature_requests: tuple[ClientSignatureRequest, ...]
38
+ transaction_encoding: TransactionEncoding | None = None
39
+ wallet: WalletAddressInfo | None = None
40
+ expires_at: str | None = None
41
+ network: Network | None = None
42
+ recent_blockhash: str | None = None
43
+ kind: PreparedTransactionKind | None = None
44
+
45
+
46
+ @dataclass(frozen=True, slots=True)
47
+ class SignedPreparedTransaction:
48
+ transaction: str
49
+ transaction_encoding: TransactionEncoding | None = None
50
+ network: Network | None = None
51
+
52
+
53
+ @dataclass(frozen=True, slots=True)
54
+ class SubmittedTransaction:
55
+ signature: str
56
+ status: Literal["submitted", "confirmed"] | None = None
57
+
58
+
59
+ @dataclass(frozen=True, slots=True)
60
+ class SponsorSignedTransactionArgs:
61
+ transaction: str
62
+ transaction_encoding: TransactionEncoding | None = None
63
+ network: Network | None = None
64
+ idempotency_key: str | None = None
65
+
66
+
67
+ @dataclass(frozen=True, slots=True)
68
+ class PreparedTransactionsResult:
69
+ transactions: tuple[PreparedTransaction, ...]
70
+ client_authority_transactions: tuple[PreparedTransaction, ...]
71
+ fee_payer_only_transactions: tuple[PreparedTransaction, ...]
72
+ wallet: WalletAddressInfo | None = None
73
+ network: Network | None = None
74
+
75
+
76
+ class TransactionsClient:
77
+ def __init__(
78
+ self,
79
+ http: HttpClient,
80
+ default_network: Network | None = None,
81
+ ) -> None:
82
+ self._http = http
83
+ self._default_network = default_network
84
+
85
+ async def sponsor(
86
+ self,
87
+ args: SponsorSignedTransactionArgs,
88
+ ) -> SubmittedTransaction:
89
+ transaction_bytes = base64.b64decode(args.transaction)
90
+ response = await self._http.post(
91
+ "/paymaster/sponsor",
92
+ {
93
+ "base58_encoded_transaction": base58.b58encode(
94
+ transaction_bytes
95
+ ).decode("ascii"),
96
+ "network": args.network or self._default_network,
97
+ "idempotencyKey": args.idempotency_key,
98
+ },
99
+ )
100
+ return normalize_submitted_transaction(response)
101
+
102
+
103
+ def normalize_prepared_transaction(response: object) -> PreparedTransaction:
104
+ body = _mapping(response, "Prepared transaction response")
105
+ transaction = _pick(
106
+ body, "transaction", "unsignedTransaction", "unsigned_transaction"
107
+ )
108
+ if not isinstance(transaction, str) or not transaction:
109
+ raise ValueError("Prepared transaction response is missing transaction")
110
+
111
+ wallet_value = body.get("wallet")
112
+ return PreparedTransaction(
113
+ transaction=transaction,
114
+ transaction_encoding=_normalize_transaction_encoding(
115
+ _pick(body, "transactionEncoding", "transaction_encoding")
116
+ ),
117
+ wallet=(
118
+ wallet_address_info_from_wire(wallet_value)
119
+ if wallet_value is not None
120
+ else None
121
+ ),
122
+ expires_at=_optional_string(_pick(body, "expiresAt", "expires_at")),
123
+ network=normalize_network(body.get("network")),
124
+ recent_blockhash=_optional_string(
125
+ _pick(body, "recentBlockhash", "recent_blockhash")
126
+ ),
127
+ kind=_normalize_kind(body.get("kind")),
128
+ signature_requests=_normalize_signature_requests(
129
+ _pick(body, "signatureRequests", "signature_requests")
130
+ ),
131
+ )
132
+
133
+
134
+ def normalize_prepared_transactions_result(
135
+ response: object,
136
+ ) -> PreparedTransactionsResult:
137
+ body = _mapping(response, "Prepare transactions response")
138
+ network = normalize_network(body.get("network"))
139
+ raw_transactions = body.get("transactions", [])
140
+ if not isinstance(raw_transactions, Sequence) or isinstance(
141
+ raw_transactions, (str, bytes)
142
+ ):
143
+ raise ValueError("Prepare transactions response has invalid transactions")
144
+ transactions = tuple(
145
+ normalize_prepared_transaction(
146
+ {
147
+ **_mapping(item, "Prepared transaction"),
148
+ "network": _mapping(item, "Prepared transaction").get(
149
+ "network", body.get("network")
150
+ ),
151
+ }
152
+ )
153
+ for item in raw_transactions
154
+ )
155
+ wallet_value = body.get("wallet")
156
+ wallet = (
157
+ wallet_address_info_from_wire(wallet_value)
158
+ if wallet_value is not None
159
+ else None
160
+ )
161
+ if wallet is not None and wallet.network is None and network is not None:
162
+ wallet = WalletAddressInfo(
163
+ wallet.swig_config_address,
164
+ wallet.wallet_address,
165
+ network,
166
+ )
167
+ return PreparedTransactionsResult(
168
+ wallet=wallet,
169
+ transactions=transactions,
170
+ client_authority_transactions=tuple(
171
+ item for item in transactions if item.signature_requests
172
+ ),
173
+ fee_payer_only_transactions=tuple(
174
+ item for item in transactions if not item.signature_requests
175
+ ),
176
+ network=network,
177
+ )
178
+
179
+
180
+ def normalize_submitted_transaction(response: object) -> SubmittedTransaction:
181
+ body = _mapping(response, "Sponsor response")
182
+ signature = body.get("signature")
183
+ if not isinstance(signature, str) or not signature:
184
+ raise ValueError("Sponsor response is missing signature")
185
+ status = body.get("status")
186
+ if status not in (None, "submitted", "confirmed"):
187
+ raise ValueError("Sponsor response has invalid status")
188
+ return SubmittedTransaction(signature=signature, status=status)
189
+
190
+
191
+ def _normalize_signature_requests(value: object) -> tuple[ClientSignatureRequest, ...]:
192
+ if value is None:
193
+ return ()
194
+ if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
195
+ raise ValueError("Prepared transaction has invalid signature request")
196
+ requests: list[ClientSignatureRequest] = []
197
+ for item in value:
198
+ request = _mapping(item, "Prepared transaction signature request")
199
+ scheme = _normalize_signature_scheme(request.get("scheme"))
200
+ signer = request.get("signer")
201
+ message_hash = _pick(request, "messageHash", "message_hash")
202
+ slot = request.get("slot")
203
+ counter = request.get("counter")
204
+ if isinstance(slot, str):
205
+ try:
206
+ slot = int(slot, 10)
207
+ except ValueError as error:
208
+ raise ValueError(
209
+ "Prepared transaction has invalid signature request"
210
+ ) from error
211
+ if (
212
+ scheme is None
213
+ or not isinstance(signer, str)
214
+ or not isinstance(message_hash, str)
215
+ or not isinstance(slot, int)
216
+ or not isinstance(counter, int)
217
+ ):
218
+ raise ValueError("Prepared transaction has invalid signature request")
219
+ requests.append(
220
+ ClientSignatureRequest(
221
+ scheme=scheme,
222
+ signer=signer,
223
+ message_hash=message_hash,
224
+ slot=slot,
225
+ counter=counter,
226
+ )
227
+ )
228
+ return tuple(requests)
229
+
230
+
231
+ def _normalize_signature_scheme(value: object) -> SignatureScheme | None:
232
+ if value in ("secp256r1", "AUTHORITY_SIGNATURE_SCHEME_SECP256R1", 1):
233
+ return "secp256r1"
234
+ if value in ("secp256k1", "AUTHORITY_SIGNATURE_SCHEME_SECP256K1", 2):
235
+ return "secp256k1"
236
+ return None
237
+
238
+
239
+ def _normalize_transaction_encoding(value: object) -> TransactionEncoding | None:
240
+ if value in ("base64", "TRANSACTION_ENCODING_BASE64", 1):
241
+ return "base64"
242
+ return None
243
+
244
+
245
+ def _normalize_kind(value: object) -> PreparedTransactionKind | None:
246
+ if value in (
247
+ "create-swig-wallet",
248
+ "PREPARED_TRANSACTION_KIND_CREATE_SWIG_WALLET",
249
+ 1,
250
+ ):
251
+ return "create-swig-wallet"
252
+ if value in ("add-authority", "PREPARED_TRANSACTION_KIND_ADD_AUTHORITY", 2):
253
+ return "add-authority"
254
+ if value in (
255
+ "configure-recovery",
256
+ "PREPARED_TRANSACTION_KIND_CONFIGURE_RECOVERY",
257
+ 3,
258
+ ):
259
+ return "configure-recovery"
260
+ return None
261
+
262
+
263
+ def _mapping(value: object, label: str) -> Mapping[str, object]:
264
+ if isinstance(value, Mapping):
265
+ return value
266
+ raise ValueError(f"{label} must be an object")
267
+
268
+
269
+ def _pick(value: Mapping[str, object], *keys: str) -> object:
270
+ for key in keys:
271
+ if key in value:
272
+ return value[key]
273
+ return None
274
+
275
+
276
+ def _optional_string(value: object) -> str | None:
277
+ return value if isinstance(value, str) else None