t402 1.6.1__py3-none-any.whl → 1.7.1__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,477 @@
1
+ """Scheme Registry for T402 Protocol.
2
+
3
+ This module provides the SchemeRegistry class for managing payment scheme
4
+ implementations across different blockchain networks and protocol versions.
5
+
6
+ The registry supports:
7
+ - Registering schemes by network (exact match) or network pattern (wildcards)
8
+ - Looking up schemes by network and scheme name
9
+ - Separate registries for client, server, and facilitator schemes
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import re
15
+ import threading
16
+ from typing import (
17
+ Any,
18
+ Dict,
19
+ Generic,
20
+ List,
21
+ Optional,
22
+ TypeVar,
23
+ )
24
+
25
+ from t402.types import (
26
+ Network,
27
+ T402_VERSION_V1,
28
+ T402_VERSION_V2,
29
+ )
30
+ from t402.schemes.interfaces import (
31
+ SchemeNetworkClient,
32
+ SchemeNetworkServer,
33
+ SchemeNetworkFacilitator,
34
+ )
35
+
36
+ # Type variable for generic scheme types
37
+ T = TypeVar("T")
38
+
39
+
40
+ def _matches_network_pattern(pattern: str, network: str) -> bool:
41
+ """Check if a network matches a pattern.
42
+
43
+ Supports:
44
+ - Exact match: "eip155:8453" matches "eip155:8453"
45
+ - Wildcard: "eip155:*" matches any "eip155:..." network
46
+ - Full wildcard: "*" matches any network
47
+
48
+ Args:
49
+ pattern: Network pattern (may include wildcards)
50
+ network: Actual network identifier
51
+
52
+ Returns:
53
+ True if network matches pattern
54
+ """
55
+ if pattern == "*":
56
+ return True
57
+
58
+ if "*" in pattern:
59
+ # Convert glob pattern to regex
60
+ # e.g., "eip155:*" -> "^eip155:.*$"
61
+ regex_pattern = "^" + pattern.replace("*", ".*") + "$"
62
+ return bool(re.match(regex_pattern, network))
63
+
64
+ return pattern == network
65
+
66
+
67
+ def _extract_caip_family(network: str) -> str:
68
+ """Extract CAIP-2 family from network identifier.
69
+
70
+ Args:
71
+ network: Network identifier (e.g., "eip155:8453", "solana:mainnet")
72
+
73
+ Returns:
74
+ Family pattern (e.g., "eip155:*", "solana:*")
75
+ """
76
+ if ":" in network:
77
+ namespace = network.split(":")[0]
78
+ return f"{namespace}:*"
79
+ return network
80
+
81
+
82
+ class SchemeRegistry(Generic[T]):
83
+ """Registry for managing payment scheme implementations.
84
+
85
+ This class provides a thread-safe registry for registering and looking up
86
+ payment scheme implementations. Schemes can be registered with exact network
87
+ identifiers or wildcard patterns.
88
+
89
+ The registry is organized by:
90
+ - Protocol version (V1 or V2)
91
+ - Network identifier or pattern
92
+ - Scheme name
93
+
94
+ Example:
95
+ ```python
96
+ registry = SchemeRegistry[SchemeNetworkClient]()
97
+
98
+ # Register for exact network
99
+ registry.register("eip155:8453", evm_client)
100
+
101
+ # Register with wildcard (all EVM networks)
102
+ registry.register("eip155:*", evm_client)
103
+
104
+ # Look up scheme
105
+ client = registry.get("eip155:8453", "exact")
106
+ ```
107
+ """
108
+
109
+ def __init__(self, default_version: int = T402_VERSION_V2):
110
+ """Initialize the registry.
111
+
112
+ Args:
113
+ default_version: Default protocol version for registrations
114
+ """
115
+ self._default_version = default_version
116
+ self._lock = threading.RLock()
117
+
118
+ # Structure: {version: {network_pattern: {scheme: implementation}}}
119
+ self._schemes: Dict[int, Dict[str, Dict[str, T]]] = {
120
+ T402_VERSION_V1: {},
121
+ T402_VERSION_V2: {},
122
+ }
123
+
124
+ # Cache for pattern-based lookups
125
+ self._patterns: Dict[int, List[str]] = {
126
+ T402_VERSION_V1: [],
127
+ T402_VERSION_V2: [],
128
+ }
129
+
130
+ def register(
131
+ self,
132
+ network: Network,
133
+ scheme: T,
134
+ version: Optional[int] = None,
135
+ ) -> "SchemeRegistry[T]":
136
+ """Register a scheme for a network.
137
+
138
+ Args:
139
+ network: Network identifier or pattern (e.g., "eip155:8453" or "eip155:*")
140
+ scheme: Scheme implementation (must have 'scheme' attribute)
141
+ version: Protocol version (defaults to current default)
142
+
143
+ Returns:
144
+ Self for chaining
145
+
146
+ Raises:
147
+ ValueError: If scheme doesn't have 'scheme' attribute
148
+ """
149
+ if not hasattr(scheme, "scheme"):
150
+ raise ValueError("Scheme must have 'scheme' attribute")
151
+
152
+ scheme_name = scheme.scheme
153
+ v = version or self._default_version
154
+
155
+ with self._lock:
156
+ if v not in self._schemes:
157
+ self._schemes[v] = {}
158
+ self._patterns[v] = []
159
+
160
+ if network not in self._schemes[v]:
161
+ self._schemes[v][network] = {}
162
+
163
+ self._schemes[v][network][scheme_name] = scheme
164
+
165
+ # Track patterns for wildcard matching
166
+ if "*" in network and network not in self._patterns[v]:
167
+ self._patterns[v].append(network)
168
+
169
+ return self
170
+
171
+ def register_v1(
172
+ self,
173
+ network: Network,
174
+ scheme: T,
175
+ ) -> "SchemeRegistry[T]":
176
+ """Register a scheme for V1 protocol.
177
+
178
+ Args:
179
+ network: V1 network identifier (e.g., "base-sepolia")
180
+ scheme: Scheme implementation
181
+
182
+ Returns:
183
+ Self for chaining
184
+ """
185
+ return self.register(network, scheme, T402_VERSION_V1)
186
+
187
+ def register_v2(
188
+ self,
189
+ network: Network,
190
+ scheme: T,
191
+ ) -> "SchemeRegistry[T]":
192
+ """Register a scheme for V2 protocol.
193
+
194
+ Args:
195
+ network: V2 network identifier (CAIP-2 format)
196
+ scheme: Scheme implementation
197
+
198
+ Returns:
199
+ Self for chaining
200
+ """
201
+ return self.register(network, scheme, T402_VERSION_V2)
202
+
203
+ def get(
204
+ self,
205
+ network: Network,
206
+ scheme_name: str,
207
+ version: Optional[int] = None,
208
+ ) -> Optional[T]:
209
+ """Get a scheme for a specific network and scheme name.
210
+
211
+ Lookup order:
212
+ 1. Exact network match
213
+ 2. Pattern match (e.g., "eip155:*" for "eip155:8453")
214
+
215
+ Args:
216
+ network: Network identifier
217
+ scheme_name: Scheme name (e.g., "exact")
218
+ version: Protocol version (defaults to current default)
219
+
220
+ Returns:
221
+ Scheme implementation or None if not found
222
+ """
223
+ v = version or self._default_version
224
+
225
+ with self._lock:
226
+ if v not in self._schemes:
227
+ return None
228
+
229
+ # Try exact match first
230
+ if network in self._schemes[v]:
231
+ schemes = self._schemes[v][network]
232
+ if scheme_name in schemes:
233
+ return schemes[scheme_name]
234
+
235
+ # Try pattern matching
236
+ for pattern in self._patterns.get(v, []):
237
+ if _matches_network_pattern(pattern, network):
238
+ schemes = self._schemes[v].get(pattern, {})
239
+ if scheme_name in schemes:
240
+ return schemes[scheme_name]
241
+
242
+ return None
243
+
244
+ def get_for_network(
245
+ self,
246
+ network: Network,
247
+ version: Optional[int] = None,
248
+ ) -> Dict[str, T]:
249
+ """Get all schemes registered for a network.
250
+
251
+ Args:
252
+ network: Network identifier
253
+ version: Protocol version
254
+
255
+ Returns:
256
+ Dict of scheme_name -> scheme implementation
257
+ """
258
+ v = version or self._default_version
259
+ result: Dict[str, T] = {}
260
+
261
+ with self._lock:
262
+ if v not in self._schemes:
263
+ return result
264
+
265
+ # Exact match
266
+ if network in self._schemes[v]:
267
+ result.update(self._schemes[v][network])
268
+
269
+ # Pattern matches
270
+ for pattern in self._patterns.get(v, []):
271
+ if _matches_network_pattern(pattern, network):
272
+ # Don't override exact matches
273
+ for scheme_name, scheme in self._schemes[v].get(pattern, {}).items():
274
+ if scheme_name not in result:
275
+ result[scheme_name] = scheme
276
+
277
+ return result
278
+
279
+ def has_scheme(
280
+ self,
281
+ network: Network,
282
+ scheme_name: str,
283
+ version: Optional[int] = None,
284
+ ) -> bool:
285
+ """Check if a scheme is registered for a network.
286
+
287
+ Args:
288
+ network: Network identifier
289
+ scheme_name: Scheme name
290
+ version: Protocol version
291
+
292
+ Returns:
293
+ True if scheme is registered
294
+ """
295
+ return self.get(network, scheme_name, version) is not None
296
+
297
+ def get_registered_networks(
298
+ self,
299
+ version: Optional[int] = None,
300
+ ) -> List[str]:
301
+ """Get all registered network patterns.
302
+
303
+ Args:
304
+ version: Protocol version
305
+
306
+ Returns:
307
+ List of network identifiers/patterns
308
+ """
309
+ v = version or self._default_version
310
+
311
+ with self._lock:
312
+ return list(self._schemes.get(v, {}).keys())
313
+
314
+ def get_registered_schemes(
315
+ self,
316
+ network: Network,
317
+ version: Optional[int] = None,
318
+ ) -> List[str]:
319
+ """Get all scheme names registered for a network.
320
+
321
+ Args:
322
+ network: Network identifier
323
+ version: Protocol version
324
+
325
+ Returns:
326
+ List of scheme names
327
+ """
328
+ schemes = self.get_for_network(network, version)
329
+ return list(schemes.keys())
330
+
331
+ def clear(self, version: Optional[int] = None) -> None:
332
+ """Clear all registered schemes.
333
+
334
+ Args:
335
+ version: If provided, only clear that version. Otherwise clear all.
336
+ """
337
+ with self._lock:
338
+ if version is not None:
339
+ self._schemes[version] = {}
340
+ self._patterns[version] = []
341
+ else:
342
+ for v in self._schemes:
343
+ self._schemes[v] = {}
344
+ self._patterns[v] = []
345
+
346
+
347
+ class ClientSchemeRegistry(SchemeRegistry[SchemeNetworkClient]):
348
+ """Registry specifically for client schemes."""
349
+
350
+ pass
351
+
352
+
353
+ class ServerSchemeRegistry(SchemeRegistry[SchemeNetworkServer]):
354
+ """Registry specifically for server schemes."""
355
+
356
+ pass
357
+
358
+
359
+ class FacilitatorSchemeRegistry(SchemeRegistry[SchemeNetworkFacilitator]):
360
+ """Registry specifically for facilitator schemes.
361
+
362
+ Provides additional methods for building /supported responses.
363
+ """
364
+
365
+ def get_supported_kinds(
366
+ self,
367
+ version: int = T402_VERSION_V2,
368
+ ) -> List[Dict[str, Any]]:
369
+ """Get all supported kinds for the /supported endpoint.
370
+
371
+ Returns:
372
+ List of SupportedKind dicts
373
+ """
374
+ result: List[Dict[str, Any]] = []
375
+
376
+ with self._lock:
377
+ for network, schemes in self._schemes.get(version, {}).items():
378
+ # Skip wildcard patterns - they represent capabilities, not specific networks
379
+ if "*" in network:
380
+ continue
381
+
382
+ for scheme_name, scheme in schemes.items():
383
+ kind: Dict[str, Any] = {
384
+ "t402Version": version,
385
+ "scheme": scheme_name,
386
+ "network": network,
387
+ }
388
+
389
+ # Add extra data if available
390
+ if hasattr(scheme, "get_extra"):
391
+ extra = scheme.get_extra(network)
392
+ if extra:
393
+ kind["extra"] = extra
394
+
395
+ result.append(kind)
396
+
397
+ return result
398
+
399
+ def get_signers_by_family(
400
+ self,
401
+ version: int = T402_VERSION_V2,
402
+ ) -> Dict[str, List[str]]:
403
+ """Get signer addresses grouped by CAIP family.
404
+
405
+ Returns:
406
+ Dict of caip_family -> list of signer addresses
407
+ """
408
+ result: Dict[str, List[str]] = {}
409
+ seen_schemes: Dict[str, set] = {} # Track seen scheme instances by family
410
+
411
+ with self._lock:
412
+ for network, schemes in self._schemes.get(version, {}).items():
413
+ for scheme_name, scheme in schemes.items():
414
+ if not hasattr(scheme, "caip_family") or not hasattr(scheme, "get_signers"):
415
+ continue
416
+
417
+ family = scheme.caip_family
418
+
419
+ # Initialize family tracking
420
+ if family not in result:
421
+ result[family] = []
422
+ seen_schemes[family] = set()
423
+
424
+ # Avoid duplicate signers from same scheme instance
425
+ scheme_id = id(scheme)
426
+ if scheme_id in seen_schemes[family]:
427
+ continue
428
+ seen_schemes[family].add(scheme_id)
429
+
430
+ # Get signers
431
+ try:
432
+ signers = scheme.get_signers(network)
433
+ for signer in signers:
434
+ if signer not in result[family]:
435
+ result[family].append(signer)
436
+ except Exception:
437
+ pass # Ignore errors from get_signers
438
+
439
+ return result
440
+
441
+
442
+ # Global registry instances (optional convenience)
443
+ _client_registry: Optional[ClientSchemeRegistry] = None
444
+ _server_registry: Optional[ServerSchemeRegistry] = None
445
+ _facilitator_registry: Optional[FacilitatorSchemeRegistry] = None
446
+
447
+
448
+ def get_client_registry() -> ClientSchemeRegistry:
449
+ """Get the global client scheme registry."""
450
+ global _client_registry
451
+ if _client_registry is None:
452
+ _client_registry = ClientSchemeRegistry()
453
+ return _client_registry
454
+
455
+
456
+ def get_server_registry() -> ServerSchemeRegistry:
457
+ """Get the global server scheme registry."""
458
+ global _server_registry
459
+ if _server_registry is None:
460
+ _server_registry = ServerSchemeRegistry()
461
+ return _server_registry
462
+
463
+
464
+ def get_facilitator_registry() -> FacilitatorSchemeRegistry:
465
+ """Get the global facilitator scheme registry."""
466
+ global _facilitator_registry
467
+ if _facilitator_registry is None:
468
+ _facilitator_registry = FacilitatorSchemeRegistry()
469
+ return _facilitator_registry
470
+
471
+
472
+ def reset_global_registries() -> None:
473
+ """Reset all global registries. Useful for testing."""
474
+ global _client_registry, _server_registry, _facilitator_registry
475
+ _client_registry = None
476
+ _server_registry = None
477
+ _facilitator_registry = None
@@ -0,0 +1,22 @@
1
+ """TON Blockchain Payment Schemes.
2
+
3
+ This package provides payment scheme implementations for TON blockchain.
4
+
5
+ Supported schemes:
6
+ - exact: Jetton TransferWithAuthorization
7
+ """
8
+
9
+ from t402.schemes.ton.exact import (
10
+ ExactTonClientScheme,
11
+ ExactTonServerScheme,
12
+ TonSigner,
13
+ SCHEME_EXACT,
14
+ )
15
+
16
+ __all__ = [
17
+ # Exact scheme
18
+ "ExactTonClientScheme",
19
+ "ExactTonServerScheme",
20
+ "TonSigner",
21
+ "SCHEME_EXACT",
22
+ ]
@@ -0,0 +1,27 @@
1
+ """TON Exact Payment Scheme.
2
+
3
+ This package provides the exact payment scheme implementation for TON
4
+ using Jetton TransferWithAuthorization.
5
+
6
+ The exact scheme allows users to authorize Jetton transfers that can be
7
+ executed by a facilitator, enabling gasless payments on TON.
8
+ """
9
+
10
+ from t402.schemes.ton.exact.client import (
11
+ ExactTonClientScheme,
12
+ TonSigner,
13
+ SCHEME_EXACT,
14
+ )
15
+ from t402.schemes.ton.exact.server import (
16
+ ExactTonServerScheme,
17
+ )
18
+
19
+ __all__ = [
20
+ # Client
21
+ "ExactTonClientScheme",
22
+ "TonSigner",
23
+ # Server
24
+ "ExactTonServerScheme",
25
+ # Constants
26
+ "SCHEME_EXACT",
27
+ ]