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.
- swig_developer_sdk/__init__.py +180 -0
- swig_developer_sdk/client.py +36 -0
- swig_developer_sdk/common.py +104 -0
- swig_developer_sdk/core.py +162 -0
- swig_developer_sdk/evm.py +64 -0
- swig_developer_sdk/one_business.py +163 -0
- swig_developer_sdk/passkeys.py +122 -0
- swig_developer_sdk/paymaster.py +106 -0
- swig_developer_sdk/proxy.py +724 -0
- swig_developer_sdk/ramp.py +724 -0
- swig_developer_sdk/signing.py +461 -0
- swig_developer_sdk/transactions.py +277 -0
- swig_developer_sdk/wallets.py +1393 -0
- swig_developer_sdk-0.6.0.dist-info/METADATA +89 -0
- swig_developer_sdk-0.6.0.dist-info/RECORD +16 -0
- swig_developer_sdk-0.6.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,724 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Awaitable, Callable, Mapping, Sequence
|
|
7
|
+
from dataclasses import dataclass, fields, is_dataclass
|
|
8
|
+
from typing import Literal, TypeAlias, TypeVar, cast
|
|
9
|
+
from urllib.parse import unquote
|
|
10
|
+
|
|
11
|
+
import httpx
|
|
12
|
+
|
|
13
|
+
from .client import SwigClient
|
|
14
|
+
from .common import DEFAULT_BACKEND_URL, Network, WalletAuthority, normalize_network
|
|
15
|
+
from .paymaster import PaymasterBalanceKind
|
|
16
|
+
from .ramp import (
|
|
17
|
+
ActiveRampDirection,
|
|
18
|
+
ActiveRampTransactionStatus,
|
|
19
|
+
CreateRampSessionArgs,
|
|
20
|
+
QuoteRampArgs,
|
|
21
|
+
RampCustomerContext,
|
|
22
|
+
RampPaymentMethodType,
|
|
23
|
+
RampServiceProvider,
|
|
24
|
+
RampWalletContext,
|
|
25
|
+
)
|
|
26
|
+
from .wallets import (
|
|
27
|
+
RecoveryOptions,
|
|
28
|
+
TransferSolOperation,
|
|
29
|
+
TransferTokenOperation,
|
|
30
|
+
WalletReference,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
PostProxyRoute: TypeAlias = Literal[
|
|
34
|
+
"wallet/create",
|
|
35
|
+
"prepare",
|
|
36
|
+
"transfer/sol",
|
|
37
|
+
"transfer/spl-token",
|
|
38
|
+
"swap/jupiter",
|
|
39
|
+
"ramp/quote",
|
|
40
|
+
"ramp/sessions",
|
|
41
|
+
]
|
|
42
|
+
ReadProxyRoute: TypeAlias = Literal[
|
|
43
|
+
"wallet/balance/usd",
|
|
44
|
+
"wallet/token-balances",
|
|
45
|
+
"wallet/token-transactions",
|
|
46
|
+
"paymaster/balance",
|
|
47
|
+
"ramp/options",
|
|
48
|
+
"ramp/transaction",
|
|
49
|
+
"ramp/wallet-transactions",
|
|
50
|
+
]
|
|
51
|
+
ProxyRoute: TypeAlias = PostProxyRoute | ReadProxyRoute
|
|
52
|
+
T = TypeVar("T")
|
|
53
|
+
MaybeAwaitable: TypeAlias = T | Awaitable[T]
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True, slots=True)
|
|
57
|
+
class ProxyWalletReference(WalletReference):
|
|
58
|
+
role_id: int | None = None
|
|
59
|
+
authority_public_key: str | None = None
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@dataclass(frozen=True, slots=True)
|
|
63
|
+
class SwigRouteContext:
|
|
64
|
+
method: Literal["GET", "POST"]
|
|
65
|
+
path: str
|
|
66
|
+
route: ProxyRoute
|
|
67
|
+
body: Mapping[str, object]
|
|
68
|
+
query: Mapping[str, str]
|
|
69
|
+
wallet: ProxyWalletReference | None = None
|
|
70
|
+
network: Network | None = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@dataclass(frozen=True, slots=True)
|
|
74
|
+
class SwigProxyConfig:
|
|
75
|
+
api_key: str | None = None
|
|
76
|
+
transaction_api_url: str | None = None
|
|
77
|
+
base_url: str | None = None
|
|
78
|
+
network: Network | None = None
|
|
79
|
+
fee_payer: str | Callable[[SwigRouteContext], MaybeAwaitable[str | None]] | None = (
|
|
80
|
+
None
|
|
81
|
+
)
|
|
82
|
+
resolve_requester_authority: (
|
|
83
|
+
Callable[[SwigRouteContext], MaybeAwaitable[WalletAuthority | None]] | None
|
|
84
|
+
) = None
|
|
85
|
+
resolve_requester_pubkey: (
|
|
86
|
+
Callable[[SwigRouteContext], MaybeAwaitable[str | None]] | None
|
|
87
|
+
) = None
|
|
88
|
+
resolve_ramp_customer: (
|
|
89
|
+
Callable[[SwigRouteContext], MaybeAwaitable[RampCustomerContext | None]] | None
|
|
90
|
+
) = None
|
|
91
|
+
transport: httpx.AsyncBaseTransport | None = None
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass(frozen=True, slots=True)
|
|
95
|
+
class ProxyResponse:
|
|
96
|
+
status: int
|
|
97
|
+
body: Mapping[str, object]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class SwigProxyRouteError(Exception):
|
|
101
|
+
def __init__(self, message: str, status: int = 400) -> None:
|
|
102
|
+
super().__init__(message)
|
|
103
|
+
self.status = status
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class SwigProxyHandler:
|
|
107
|
+
def __init__(self, config: SwigProxyConfig | None = None) -> None:
|
|
108
|
+
self._config = config or SwigProxyConfig()
|
|
109
|
+
|
|
110
|
+
async def handle(
|
|
111
|
+
self,
|
|
112
|
+
*,
|
|
113
|
+
method: Literal["GET", "POST"],
|
|
114
|
+
path: str,
|
|
115
|
+
body: Mapping[str, object] | None = None,
|
|
116
|
+
query: Mapping[str, str] | None = None,
|
|
117
|
+
) -> ProxyResponse:
|
|
118
|
+
try:
|
|
119
|
+
if method == "POST":
|
|
120
|
+
value = await self._handle_post(path, body or {}, query or {})
|
|
121
|
+
elif method == "GET":
|
|
122
|
+
value = await self._handle_get(path, query or {})
|
|
123
|
+
else:
|
|
124
|
+
raise SwigProxyRouteError("Unsupported method", 405)
|
|
125
|
+
serialized = _to_wire(value)
|
|
126
|
+
if not isinstance(serialized, Mapping):
|
|
127
|
+
raise TypeError("Proxy response must be an object")
|
|
128
|
+
return ProxyResponse(200, serialized)
|
|
129
|
+
except SwigProxyRouteError as error:
|
|
130
|
+
return ProxyResponse(error.status, {"error": str(error)})
|
|
131
|
+
except Exception as error:
|
|
132
|
+
return ProxyResponse(400, {"error": str(error)})
|
|
133
|
+
|
|
134
|
+
async def _handle_post(
|
|
135
|
+
self,
|
|
136
|
+
path: str,
|
|
137
|
+
body: Mapping[str, object],
|
|
138
|
+
query: Mapping[str, str],
|
|
139
|
+
) -> Mapping[str, object]:
|
|
140
|
+
route = _resolve_post_route(path)
|
|
141
|
+
network = _read_network(body.get("network")) or self._config.network
|
|
142
|
+
wallet = None if route.startswith("ramp/") else _read_wallet(body.get("wallet"))
|
|
143
|
+
context = SwigRouteContext(
|
|
144
|
+
method="POST",
|
|
145
|
+
path=path,
|
|
146
|
+
route=route,
|
|
147
|
+
body=body,
|
|
148
|
+
query=query,
|
|
149
|
+
wallet=wallet,
|
|
150
|
+
network=network,
|
|
151
|
+
)
|
|
152
|
+
swig = self._client(network)
|
|
153
|
+
if route == "wallet/create":
|
|
154
|
+
fee_payer = await self._fee_payer(context)
|
|
155
|
+
policy_id = _optional_string(body.get("policyId"))
|
|
156
|
+
initial_user = _read_authority(body.get("initialUser"))
|
|
157
|
+
if policy_id is None and initial_user is None:
|
|
158
|
+
raise SwigProxyRouteError("policyId or initialUser is required")
|
|
159
|
+
created = await swig.wallets.create(
|
|
160
|
+
fee_payer=fee_payer,
|
|
161
|
+
policy_id=policy_id,
|
|
162
|
+
initial_user=initial_user,
|
|
163
|
+
recovery=_read_recovery_options(body.get("recovery")),
|
|
164
|
+
network=network,
|
|
165
|
+
idempotency_key=_optional_string(body.get("idempotencyKey")),
|
|
166
|
+
)
|
|
167
|
+
if not created.transactions:
|
|
168
|
+
raise SwigProxyRouteError(
|
|
169
|
+
"Wallet creation response is missing transaction", 502
|
|
170
|
+
)
|
|
171
|
+
return {"prepared": created}
|
|
172
|
+
if route == "ramp/quote":
|
|
173
|
+
customer = await self._ramp_customer(context)
|
|
174
|
+
return cast(
|
|
175
|
+
Mapping[str, object],
|
|
176
|
+
_to_wire(await swig.ramp.quote(_quote_args(body, customer))),
|
|
177
|
+
)
|
|
178
|
+
if route == "ramp/sessions":
|
|
179
|
+
customer = await self._ramp_customer(context)
|
|
180
|
+
return cast(
|
|
181
|
+
Mapping[str, object],
|
|
182
|
+
_to_wire(await swig.ramp.create_session(_session_args(body, customer))),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
required_wallet = _require_wallet(wallet)
|
|
186
|
+
requester_authority = await self._requester_authority(context)
|
|
187
|
+
fee_payer = await self._fee_payer(context)
|
|
188
|
+
handle = swig.wallets.use(
|
|
189
|
+
required_wallet,
|
|
190
|
+
network=network,
|
|
191
|
+
requester_authority=requester_authority,
|
|
192
|
+
)
|
|
193
|
+
idempotency_key = _optional_string(body.get("idempotencyKey"))
|
|
194
|
+
prepared: object
|
|
195
|
+
if route == "prepare":
|
|
196
|
+
prepared = await handle.prepare(
|
|
197
|
+
fee_payer=fee_payer,
|
|
198
|
+
requester_authority=requester_authority,
|
|
199
|
+
network=network,
|
|
200
|
+
idempotency_key=idempotency_key,
|
|
201
|
+
operations=_read_operations(body.get("operations")),
|
|
202
|
+
)
|
|
203
|
+
elif route == "transfer/sol":
|
|
204
|
+
prepared = await handle.transfer.sol(
|
|
205
|
+
fee_payer=fee_payer,
|
|
206
|
+
requester_authority=requester_authority,
|
|
207
|
+
network=network,
|
|
208
|
+
idempotency_key=idempotency_key,
|
|
209
|
+
destination=_required_string(body, "destination"),
|
|
210
|
+
amount=_read_amount(body),
|
|
211
|
+
)
|
|
212
|
+
elif route == "transfer/spl-token":
|
|
213
|
+
prepared = await handle.transfer.token(
|
|
214
|
+
fee_payer=fee_payer,
|
|
215
|
+
requester_authority=requester_authority,
|
|
216
|
+
network=network,
|
|
217
|
+
idempotency_key=idempotency_key,
|
|
218
|
+
mint=_required_string(body, "mint"),
|
|
219
|
+
destination_owner=_required_string(body, "destinationOwner"),
|
|
220
|
+
amount=_read_amount(body),
|
|
221
|
+
)
|
|
222
|
+
else:
|
|
223
|
+
prepared = await handle.swap.jupiter(
|
|
224
|
+
fee_payer=fee_payer,
|
|
225
|
+
requester_authority=requester_authority,
|
|
226
|
+
network=network,
|
|
227
|
+
idempotency_key=idempotency_key,
|
|
228
|
+
input_mint=_required_string(body, "inputMint"),
|
|
229
|
+
output_mint=_required_string(body, "outputMint"),
|
|
230
|
+
amount=_read_amount(body),
|
|
231
|
+
slippage_bps=_optional_int(body.get("slippageBps")),
|
|
232
|
+
destination_account=_optional_string(body.get("destinationAccount")),
|
|
233
|
+
wrap_and_unwrap_sol=_optional_bool(body.get("wrapAndUnwrapSol")),
|
|
234
|
+
tip_amount_lamports=_optional_string(body.get("tipAmountLamports")),
|
|
235
|
+
compute_unit_price_percentile=_optional_string(
|
|
236
|
+
body.get("computeUnitPricePercentile")
|
|
237
|
+
),
|
|
238
|
+
max_accounts=_optional_int(body.get("maxAccounts")),
|
|
239
|
+
mode=_optional_string(body.get("mode")),
|
|
240
|
+
blockhash_slots_to_expiry=_optional_int(
|
|
241
|
+
body.get("blockhashSlotsToExpiry")
|
|
242
|
+
),
|
|
243
|
+
)
|
|
244
|
+
return {"prepared": prepared}
|
|
245
|
+
|
|
246
|
+
async def _handle_get(
|
|
247
|
+
self,
|
|
248
|
+
path: str,
|
|
249
|
+
query: Mapping[str, str],
|
|
250
|
+
) -> Mapping[str, object]:
|
|
251
|
+
route, identifier = _resolve_read_route(path)
|
|
252
|
+
network = _read_network(query.get("network")) or self._config.network
|
|
253
|
+
context = SwigRouteContext(
|
|
254
|
+
method="GET",
|
|
255
|
+
path=path,
|
|
256
|
+
route=route,
|
|
257
|
+
body={},
|
|
258
|
+
query=query,
|
|
259
|
+
network=network,
|
|
260
|
+
)
|
|
261
|
+
swig = self._client(network)
|
|
262
|
+
if route == "paymaster/balance":
|
|
263
|
+
kind_value = query.get("kind", "").strip().upper()
|
|
264
|
+
kind: PaymasterBalanceKind | None
|
|
265
|
+
if kind_value in ("",):
|
|
266
|
+
kind = None
|
|
267
|
+
elif kind_value in ("API", "PAYMASTER_KIND_API"):
|
|
268
|
+
kind = "api"
|
|
269
|
+
elif kind_value in ("IDP", "PAYMASTER_KIND_IDP"):
|
|
270
|
+
kind = "idp"
|
|
271
|
+
else:
|
|
272
|
+
raise SwigProxyRouteError("kind must be api or idp")
|
|
273
|
+
return cast(
|
|
274
|
+
Mapping[str, object],
|
|
275
|
+
_to_wire(await swig.paymaster.get_balance(network=network, kind=kind)),
|
|
276
|
+
)
|
|
277
|
+
if route == "ramp/options":
|
|
278
|
+
customer = await self._resolved_ramp_customer(context)
|
|
279
|
+
return cast(
|
|
280
|
+
Mapping[str, object],
|
|
281
|
+
_to_wire(
|
|
282
|
+
await swig.ramp.get_options(
|
|
283
|
+
partner_application_id=(
|
|
284
|
+
customer.partner_application_id
|
|
285
|
+
if customer is not None
|
|
286
|
+
else query.get("partnerApplicationId")
|
|
287
|
+
),
|
|
288
|
+
country_code=query.get("countryCode"),
|
|
289
|
+
fiat_currency_code=query.get("fiatCurrencyCode"),
|
|
290
|
+
)
|
|
291
|
+
),
|
|
292
|
+
)
|
|
293
|
+
if route == "ramp/transaction":
|
|
294
|
+
return cast(
|
|
295
|
+
Mapping[str, object],
|
|
296
|
+
_to_wire(await swig.ramp.get_transaction(transaction_id=identifier)),
|
|
297
|
+
)
|
|
298
|
+
if route == "ramp/wallet-transactions":
|
|
299
|
+
direction = _active_direction(query.get("direction"))
|
|
300
|
+
status = _active_status(query.get("status"))
|
|
301
|
+
return cast(
|
|
302
|
+
Mapping[str, object],
|
|
303
|
+
_to_wire(
|
|
304
|
+
await swig.ramp.list_transactions(
|
|
305
|
+
wallet_id=identifier,
|
|
306
|
+
network=network,
|
|
307
|
+
direction=direction,
|
|
308
|
+
status=status,
|
|
309
|
+
limit=_optional_int(query.get("limit")),
|
|
310
|
+
)
|
|
311
|
+
),
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
handle = swig.wallets.use(identifier, network=network)
|
|
315
|
+
result: object
|
|
316
|
+
if route == "wallet/balance/usd":
|
|
317
|
+
result = await handle.get_usd_balance(network=network)
|
|
318
|
+
elif route == "wallet/token-balances":
|
|
319
|
+
result = await handle.list_token_balances(network=network)
|
|
320
|
+
else:
|
|
321
|
+
result = await handle.list_token_transactions(
|
|
322
|
+
network=network,
|
|
323
|
+
limit=_optional_int(query.get("limit")),
|
|
324
|
+
)
|
|
325
|
+
return cast(Mapping[str, object], _to_wire(result))
|
|
326
|
+
|
|
327
|
+
def _client(self, network: Network | None) -> SwigClient:
|
|
328
|
+
api_key = self._config.api_key or _read_env(
|
|
329
|
+
"SWIG_DEVELOPER_API_KEY", "SWIG_API_KEY"
|
|
330
|
+
)
|
|
331
|
+
if not api_key:
|
|
332
|
+
raise SwigProxyRouteError("SWIG_DEVELOPER_API_KEY is required", 500)
|
|
333
|
+
base_url = (
|
|
334
|
+
self._config.transaction_api_url
|
|
335
|
+
or self._config.base_url
|
|
336
|
+
or _read_env(
|
|
337
|
+
"SWIG_TRANSACTION_API_URL",
|
|
338
|
+
"SWIG_BACKEND_URL",
|
|
339
|
+
"NEXT_PUBLIC_SWIG_BACKEND_URL",
|
|
340
|
+
)
|
|
341
|
+
)
|
|
342
|
+
return SwigClient(
|
|
343
|
+
api_key=api_key,
|
|
344
|
+
base_url=base_url or DEFAULT_BACKEND_URL,
|
|
345
|
+
network=network,
|
|
346
|
+
transport=self._config.transport,
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
async def _fee_payer(self, context: SwigRouteContext) -> str:
|
|
350
|
+
configured = self._config.fee_payer
|
|
351
|
+
if callable(configured):
|
|
352
|
+
configured = await _resolve(configured(context))
|
|
353
|
+
value = (
|
|
354
|
+
configured
|
|
355
|
+
or _optional_string(context.body.get("feePayer"))
|
|
356
|
+
or _read_env(
|
|
357
|
+
"SWIG_FEE_PAYER",
|
|
358
|
+
"SWIG_TRANSFER_FEE_PAYER",
|
|
359
|
+
"SWIG_TRANSACTION_FEE_PAYER",
|
|
360
|
+
)
|
|
361
|
+
)
|
|
362
|
+
if not value:
|
|
363
|
+
raise SwigProxyRouteError("feePayer is required")
|
|
364
|
+
return value
|
|
365
|
+
|
|
366
|
+
async def _requester_authority(self, context: SwigRouteContext) -> WalletAuthority:
|
|
367
|
+
authority = (
|
|
368
|
+
context.wallet.requester_authority if context.wallet else None
|
|
369
|
+
) or _read_authority(context.body.get("requesterAuthority"))
|
|
370
|
+
if authority is None and self._config.resolve_requester_authority:
|
|
371
|
+
authority = await _resolve(
|
|
372
|
+
self._config.resolve_requester_authority(context)
|
|
373
|
+
)
|
|
374
|
+
if authority is None and self._config.resolve_requester_pubkey:
|
|
375
|
+
public_key = await _resolve(self._config.resolve_requester_pubkey(context))
|
|
376
|
+
if public_key:
|
|
377
|
+
authority = {"ed25519": {"publicKey": public_key}}
|
|
378
|
+
if authority is None:
|
|
379
|
+
raise SwigProxyRouteError("requesterAuthority is required")
|
|
380
|
+
return authority
|
|
381
|
+
|
|
382
|
+
async def _resolved_ramp_customer(
|
|
383
|
+
self, context: SwigRouteContext
|
|
384
|
+
) -> RampCustomerContext | None:
|
|
385
|
+
if self._config.resolve_ramp_customer is None:
|
|
386
|
+
return None
|
|
387
|
+
return await _resolve(self._config.resolve_ramp_customer(context))
|
|
388
|
+
|
|
389
|
+
async def _ramp_customer(self, context: SwigRouteContext) -> RampCustomerContext:
|
|
390
|
+
resolved = await self._resolved_ramp_customer(context)
|
|
391
|
+
if resolved is not None:
|
|
392
|
+
return resolved
|
|
393
|
+
return _read_ramp_customer(context.body.get("customer"))
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def create_swig_proxy_handler(
|
|
397
|
+
config: SwigProxyConfig | None = None,
|
|
398
|
+
) -> SwigProxyHandler:
|
|
399
|
+
return SwigProxyHandler(config)
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
def _resolve_post_route(path: str) -> PostProxyRoute:
|
|
403
|
+
routes: tuple[PostProxyRoute, ...] = (
|
|
404
|
+
"wallet/create",
|
|
405
|
+
"prepare",
|
|
406
|
+
"transfer/sol",
|
|
407
|
+
"transfer/spl-token",
|
|
408
|
+
"swap/jupiter",
|
|
409
|
+
"ramp/quote",
|
|
410
|
+
"ramp/sessions",
|
|
411
|
+
)
|
|
412
|
+
normalized = path.rstrip("/")
|
|
413
|
+
for route in routes:
|
|
414
|
+
if normalized == f"/{route}" or normalized.endswith(f"/{route}"):
|
|
415
|
+
return route
|
|
416
|
+
raise SwigProxyRouteError("Unsupported Swig route", 404)
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _resolve_read_route(path: str) -> tuple[ReadProxyRoute, str]:
|
|
420
|
+
normalized = path.rstrip("/")
|
|
421
|
+
if normalized.endswith("/paymaster/balance"):
|
|
422
|
+
return "paymaster/balance", ""
|
|
423
|
+
if normalized.endswith("/ramp/options"):
|
|
424
|
+
return "ramp/options", ""
|
|
425
|
+
match = re.search(r"/ramp/transactions/([^/]+)$", normalized)
|
|
426
|
+
if match:
|
|
427
|
+
return "ramp/transaction", unquote(match.group(1))
|
|
428
|
+
match = re.search(r"/ramp/wallets/([^/]+)/transactions$", normalized)
|
|
429
|
+
if match:
|
|
430
|
+
return "ramp/wallet-transactions", unquote(match.group(1))
|
|
431
|
+
match = re.search(
|
|
432
|
+
r"/wallet/([^/]+)/(balance/usd|token-balances|token-transactions)$",
|
|
433
|
+
normalized,
|
|
434
|
+
)
|
|
435
|
+
if not match:
|
|
436
|
+
raise SwigProxyRouteError("Unsupported Swig route", 404)
|
|
437
|
+
route_map: dict[str, ReadProxyRoute] = {
|
|
438
|
+
"balance/usd": "wallet/balance/usd",
|
|
439
|
+
"token-balances": "wallet/token-balances",
|
|
440
|
+
"token-transactions": "wallet/token-transactions",
|
|
441
|
+
}
|
|
442
|
+
return route_map[match.group(2)], unquote(match.group(1))
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def _read_wallet(value: object) -> ProxyWalletReference | None:
|
|
446
|
+
if value is None:
|
|
447
|
+
return None
|
|
448
|
+
body = _mapping(value, "wallet")
|
|
449
|
+
return ProxyWalletReference(
|
|
450
|
+
swig_config_address=_required_string(body, "swigConfigAddress"),
|
|
451
|
+
wallet_address=_optional_string(body.get("walletAddress")),
|
|
452
|
+
role_id=_optional_int(body.get("roleId")),
|
|
453
|
+
authority_public_key=_optional_string(body.get("authorityPublicKey")),
|
|
454
|
+
requester_authority=_read_authority(body.get("requesterAuthority")),
|
|
455
|
+
network=_read_network(body.get("network")),
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _read_authority(value: object) -> WalletAuthority | None:
|
|
460
|
+
if value is None:
|
|
461
|
+
return None
|
|
462
|
+
body = _mapping(value, "authority")
|
|
463
|
+
for scheme in ("ed25519", "secp256k1", "secp256r1"):
|
|
464
|
+
nested = body.get(scheme)
|
|
465
|
+
if isinstance(nested, Mapping):
|
|
466
|
+
public_key = _optional_string(nested.get("publicKey"))
|
|
467
|
+
if public_key:
|
|
468
|
+
return {scheme: {"publicKey": public_key}}
|
|
469
|
+
proof = body.get("programExecProof")
|
|
470
|
+
if isinstance(proof, Mapping):
|
|
471
|
+
role_id = _optional_int(proof.get("roleId"))
|
|
472
|
+
zk_proof = _optional_string(proof.get("zkProof"))
|
|
473
|
+
if role_id is not None and zk_proof:
|
|
474
|
+
return {"programExecProof": {"roleId": role_id, "zkProof": zk_proof}}
|
|
475
|
+
raise SwigProxyRouteError("authority must include a supported authority")
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _read_recovery_options(value: object) -> RecoveryOptions | None:
|
|
479
|
+
if value is None:
|
|
480
|
+
return None
|
|
481
|
+
body = _mapping(value, "recovery")
|
|
482
|
+
return RecoveryOptions(
|
|
483
|
+
guardian_pubkey=_optional_string(body.get("guardianPubkey")),
|
|
484
|
+
delay_seconds=_optional_int(body.get("delaySeconds")),
|
|
485
|
+
target_role_id=_optional_int(body.get("targetRoleId")),
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
def _read_operations(
|
|
490
|
+
value: object,
|
|
491
|
+
) -> tuple[TransferSolOperation | TransferTokenOperation, ...]:
|
|
492
|
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)) or not value:
|
|
493
|
+
raise SwigProxyRouteError("operations must be a non-empty array")
|
|
494
|
+
operations: list[TransferSolOperation | TransferTokenOperation] = []
|
|
495
|
+
for index, item in enumerate(value):
|
|
496
|
+
body = _mapping(item, f"operations[{index}]")
|
|
497
|
+
if body.get("type") == "transferSol":
|
|
498
|
+
operations.append(
|
|
499
|
+
TransferSolOperation(
|
|
500
|
+
_required_string(body, "destination"), _read_amount(body)
|
|
501
|
+
)
|
|
502
|
+
)
|
|
503
|
+
elif body.get("type") == "transferToken":
|
|
504
|
+
operations.append(
|
|
505
|
+
TransferTokenOperation(
|
|
506
|
+
_required_string(body, "mint"),
|
|
507
|
+
_required_string(body, "destinationOwner"),
|
|
508
|
+
_read_amount(body),
|
|
509
|
+
)
|
|
510
|
+
)
|
|
511
|
+
else:
|
|
512
|
+
raise SwigProxyRouteError(
|
|
513
|
+
f"operations[{index}].type must be transferSol or transferToken"
|
|
514
|
+
)
|
|
515
|
+
return tuple(operations)
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
def _quote_args(
|
|
519
|
+
body: Mapping[str, object], customer: RampCustomerContext
|
|
520
|
+
) -> QuoteRampArgs:
|
|
521
|
+
return QuoteRampArgs(
|
|
522
|
+
customer=customer,
|
|
523
|
+
wallet=_read_ramp_wallet(body.get("wallet")),
|
|
524
|
+
direction=_required_active_direction(_required_string(body, "direction")),
|
|
525
|
+
source_amount=_required_string(body, "sourceAmount"),
|
|
526
|
+
source_currency_code=_required_string(body, "sourceCurrencyCode"),
|
|
527
|
+
destination_currency_code=_required_string(body, "destinationCurrencyCode"),
|
|
528
|
+
country_code=_required_string(body, "countryCode"),
|
|
529
|
+
subdivision=_optional_string(body.get("subdivision")),
|
|
530
|
+
payment_method_type=_payment_method(body.get("paymentMethodType")),
|
|
531
|
+
service_providers=_service_providers(body.get("serviceProviders")),
|
|
532
|
+
)
|
|
533
|
+
|
|
534
|
+
|
|
535
|
+
def _session_args(
|
|
536
|
+
body: Mapping[str, object], customer: RampCustomerContext
|
|
537
|
+
) -> CreateRampSessionArgs:
|
|
538
|
+
provider = _service_provider(body.get("serviceProvider"))
|
|
539
|
+
return CreateRampSessionArgs(
|
|
540
|
+
customer=customer,
|
|
541
|
+
wallet=_read_ramp_wallet(body.get("wallet")),
|
|
542
|
+
direction=_required_active_direction(_required_string(body, "direction")),
|
|
543
|
+
selected_quote_id=_required_string(body, "selectedQuoteId"),
|
|
544
|
+
source_amount=_required_string(body, "sourceAmount"),
|
|
545
|
+
source_currency_code=_required_string(body, "sourceCurrencyCode"),
|
|
546
|
+
destination_currency_code=_required_string(body, "destinationCurrencyCode"),
|
|
547
|
+
country_code=_required_string(body, "countryCode"),
|
|
548
|
+
subdivision=_optional_string(body.get("subdivision")),
|
|
549
|
+
service_provider=provider,
|
|
550
|
+
payment_method_type=_payment_method(body.get("paymentMethodType")),
|
|
551
|
+
redirect_url=_optional_string(body.get("redirectUrl")),
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _read_ramp_customer(value: object) -> RampCustomerContext:
|
|
556
|
+
body = _mapping(value, "customer")
|
|
557
|
+
customer_type = body.get("customerType")
|
|
558
|
+
if customer_type not in ("individual", "business", "unspecified"):
|
|
559
|
+
raise SwigProxyRouteError("customer.customerType is required")
|
|
560
|
+
return RampCustomerContext(
|
|
561
|
+
customer_type=customer_type,
|
|
562
|
+
partner_application_id=_optional_string(body.get("partnerApplicationId")),
|
|
563
|
+
swig_user_id=_optional_string(body.get("swigUserId")),
|
|
564
|
+
external_customer_id=_optional_string(body.get("externalCustomerId")),
|
|
565
|
+
external_business_id=_optional_string(body.get("externalBusinessId")),
|
|
566
|
+
)
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
def _read_ramp_wallet(value: object) -> RampWalletContext:
|
|
570
|
+
body = _mapping(value, "wallet")
|
|
571
|
+
network = _read_network(body.get("network"))
|
|
572
|
+
if network is None:
|
|
573
|
+
raise SwigProxyRouteError("wallet.network is required")
|
|
574
|
+
return RampWalletContext(
|
|
575
|
+
wallet_id=_required_string(body, "walletId"),
|
|
576
|
+
wallet_address=_required_string(body, "walletAddress"),
|
|
577
|
+
network=network,
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _read_amount(body: Mapping[str, object]) -> str:
|
|
582
|
+
amount = _required_string(body, "amount")
|
|
583
|
+
if not amount.isdigit() or int(amount) <= 0:
|
|
584
|
+
raise SwigProxyRouteError("amount must be a positive integer string")
|
|
585
|
+
return amount
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def _active_direction(value: object) -> ActiveRampDirection | None:
|
|
589
|
+
if value in ("onramp", "offramp", "transfer"):
|
|
590
|
+
return value
|
|
591
|
+
return None
|
|
592
|
+
|
|
593
|
+
|
|
594
|
+
def _required_active_direction(value: object) -> ActiveRampDirection:
|
|
595
|
+
direction = _active_direction(value)
|
|
596
|
+
if direction is None:
|
|
597
|
+
raise SwigProxyRouteError("direction is required")
|
|
598
|
+
return direction
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def _active_status(value: object) -> ActiveRampTransactionStatus | None:
|
|
602
|
+
statuses = (
|
|
603
|
+
"created",
|
|
604
|
+
"pending",
|
|
605
|
+
"settling",
|
|
606
|
+
"settled",
|
|
607
|
+
"failed",
|
|
608
|
+
"declined",
|
|
609
|
+
"cancelled",
|
|
610
|
+
"refunded",
|
|
611
|
+
)
|
|
612
|
+
return cast(ActiveRampTransactionStatus, value) if value in statuses else None
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def _payment_method(value: object) -> RampPaymentMethodType | None:
|
|
616
|
+
values = (
|
|
617
|
+
"other",
|
|
618
|
+
"credit-debit-card",
|
|
619
|
+
"ach",
|
|
620
|
+
"bank-transfer",
|
|
621
|
+
"apple-pay",
|
|
622
|
+
"google-pay",
|
|
623
|
+
"pix",
|
|
624
|
+
"unspecified",
|
|
625
|
+
)
|
|
626
|
+
if value is None:
|
|
627
|
+
return None
|
|
628
|
+
if value not in values:
|
|
629
|
+
raise SwigProxyRouteError("Invalid ramp payment method type")
|
|
630
|
+
return cast(RampPaymentMethodType, value)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _service_provider(value: object) -> RampServiceProvider:
|
|
634
|
+
if value not in ("other", "unspecified"):
|
|
635
|
+
raise SwigProxyRouteError("Invalid ramp service provider")
|
|
636
|
+
return value
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
def _service_providers(value: object) -> tuple[RampServiceProvider, ...]:
|
|
640
|
+
if value is None:
|
|
641
|
+
return ()
|
|
642
|
+
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
|
|
643
|
+
raise SwigProxyRouteError("serviceProviders must be an array")
|
|
644
|
+
return tuple(_service_provider(item) for item in value)
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def _require_wallet(value: ProxyWalletReference | None) -> ProxyWalletReference:
|
|
648
|
+
if value is None:
|
|
649
|
+
raise SwigProxyRouteError("wallet is required")
|
|
650
|
+
return value
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _read_network(value: object) -> Network | None:
|
|
654
|
+
if isinstance(value, str) and value in ("1", "2"):
|
|
655
|
+
value = int(value)
|
|
656
|
+
return normalize_network(value)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
def _required_string(body: Mapping[str, object], key: str) -> str:
|
|
660
|
+
value = _optional_string(body.get(key))
|
|
661
|
+
if not value:
|
|
662
|
+
raise SwigProxyRouteError(f"{key} is required")
|
|
663
|
+
return value
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
def _optional_string(value: object) -> str | None:
|
|
667
|
+
return value.strip() if isinstance(value, str) and value.strip() else None
|
|
668
|
+
|
|
669
|
+
|
|
670
|
+
def _optional_int(value: object) -> int | None:
|
|
671
|
+
if isinstance(value, int) and not isinstance(value, bool):
|
|
672
|
+
return value
|
|
673
|
+
if isinstance(value, str):
|
|
674
|
+
try:
|
|
675
|
+
return int(value)
|
|
676
|
+
except ValueError:
|
|
677
|
+
return None
|
|
678
|
+
return None
|
|
679
|
+
|
|
680
|
+
|
|
681
|
+
def _optional_bool(value: object) -> bool | None:
|
|
682
|
+
return value if isinstance(value, bool) else None
|
|
683
|
+
|
|
684
|
+
|
|
685
|
+
def _mapping(value: object, label: str) -> Mapping[str, object]:
|
|
686
|
+
if isinstance(value, Mapping):
|
|
687
|
+
return value
|
|
688
|
+
raise SwigProxyRouteError(f"{label} must be an object")
|
|
689
|
+
|
|
690
|
+
|
|
691
|
+
def _read_env(*names: str) -> str | None:
|
|
692
|
+
for name in names:
|
|
693
|
+
value = os.environ.get(name, "").strip()
|
|
694
|
+
if value:
|
|
695
|
+
return value
|
|
696
|
+
return None
|
|
697
|
+
|
|
698
|
+
|
|
699
|
+
def _to_wire(value: object) -> object:
|
|
700
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
701
|
+
result: dict[str, object] = {}
|
|
702
|
+
for field in fields(value):
|
|
703
|
+
item = getattr(value, field.name)
|
|
704
|
+
if item is not None:
|
|
705
|
+
result[_snake_to_camel(field.name)] = _to_wire(item)
|
|
706
|
+
return result
|
|
707
|
+
if isinstance(value, Mapping):
|
|
708
|
+
return {
|
|
709
|
+
str(key): _to_wire(item) for key, item in value.items() if item is not None
|
|
710
|
+
}
|
|
711
|
+
if isinstance(value, (list, tuple)):
|
|
712
|
+
return [_to_wire(item) for item in value]
|
|
713
|
+
return value
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
def _snake_to_camel(value: str) -> str:
|
|
717
|
+
head, *tail = value.split("_")
|
|
718
|
+
return head + "".join(part.capitalize() for part in tail)
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
async def _resolve(value: MaybeAwaitable[T]) -> T:
|
|
722
|
+
if inspect.isawaitable(value):
|
|
723
|
+
return await cast(Awaitable[T], value)
|
|
724
|
+
return value
|