uvd-x402-sdk 0.2.2__py3-none-any.whl → 0.2.3__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.
@@ -1,347 +1,347 @@
1
- """
2
- Base network configuration and registry.
3
-
4
- This module provides the foundation for network configuration, including:
5
- - NetworkConfig dataclass for defining network parameters
6
- - NetworkType enum for categorizing networks
7
- - Global registry for storing and retrieving network configurations
8
- """
9
-
10
- from dataclasses import dataclass, field
11
- from enum import Enum
12
- from typing import Dict, List, Optional, Any
13
-
14
-
15
- class NetworkType(Enum):
16
- """
17
- Network type categorization.
18
-
19
- Different network types use different signature/transaction formats:
20
- - EVM: EIP-712 signed TransferWithAuthorization (ERC-3009)
21
- - SVM: Partially-signed VersionedTransaction (SPL token transfer) - Solana, Fogo
22
- - NEAR: NEP-366 SignedDelegateAction (meta-transaction)
23
- - STELLAR: Soroban Authorization Entry XDR
24
-
25
- Note: SOLANA is deprecated, use SVM instead for Solana-compatible chains.
26
- """
27
-
28
- EVM = "evm"
29
- SVM = "svm" # Solana Virtual Machine chains (Solana, Fogo, etc.)
30
- SOLANA = "solana" # Deprecated: use SVM
31
- NEAR = "near"
32
- STELLAR = "stellar"
33
-
34
- @classmethod
35
- def is_svm(cls, network_type: "NetworkType") -> bool:
36
- """Check if network type is SVM-compatible (Solana, Fogo, etc.)."""
37
- return network_type in (cls.SVM, cls.SOLANA)
38
-
39
-
40
- @dataclass
41
- class NetworkConfig:
42
- """
43
- Configuration for a blockchain network supporting x402 payments.
44
-
45
- Attributes:
46
- name: Lowercase network identifier (e.g., 'base', 'solana')
47
- display_name: Human-readable name (e.g., 'Base', 'Solana')
48
- network_type: Type of network (EVM, SOLANA, NEAR, STELLAR)
49
- chain_id: EVM chain ID (0 for non-EVM networks)
50
- usdc_address: USDC contract/token address
51
- usdc_decimals: Number of decimals for USDC (6 for EVM/SVM, 7 for Stellar)
52
- usdc_domain_name: EIP-712 domain name for USDC (EVM only)
53
- usdc_domain_version: EIP-712 domain version (EVM only)
54
- rpc_url: Default RPC endpoint
55
- enabled: Whether network is currently enabled
56
- extra_config: Additional network-specific configuration
57
- """
58
-
59
- name: str
60
- display_name: str
61
- network_type: NetworkType
62
- chain_id: int = 0
63
- usdc_address: str = ""
64
- usdc_decimals: int = 6
65
- usdc_domain_name: str = "USD Coin"
66
- usdc_domain_version: str = "2"
67
- rpc_url: str = ""
68
- enabled: bool = True
69
- extra_config: Dict[str, Any] = field(default_factory=dict)
70
-
71
- def __post_init__(self) -> None:
72
- """Validate configuration after initialization."""
73
- if not self.name:
74
- raise ValueError("Network name is required")
75
- if not self.usdc_address:
76
- raise ValueError(f"USDC address is required for network {self.name}")
77
-
78
- def get_token_amount(self, usd_amount: float) -> int:
79
- """
80
- Convert USD amount to token base units.
81
-
82
- Args:
83
- usd_amount: Amount in USD (e.g., 10.50)
84
-
85
- Returns:
86
- Amount in token base units (e.g., 10500000 for 6 decimals)
87
- """
88
- return int(usd_amount * (10**self.usdc_decimals))
89
-
90
- def format_token_amount(self, base_units: int) -> float:
91
- """
92
- Convert token base units to USD amount.
93
-
94
- Args:
95
- base_units: Amount in token base units
96
-
97
- Returns:
98
- Amount in USD
99
- """
100
- return base_units / (10**self.usdc_decimals)
101
-
102
-
103
- # Global network registry
104
- _NETWORK_REGISTRY: Dict[str, NetworkConfig] = {}
105
-
106
-
107
- def register_network(config: NetworkConfig) -> None:
108
- """
109
- Register a network configuration.
110
-
111
- This allows adding custom networks or overriding built-in configurations.
112
-
113
- Args:
114
- config: NetworkConfig instance to register
115
-
116
- Example:
117
- >>> from uvd_x402_sdk.networks import register_network, NetworkConfig, NetworkType
118
- >>> custom_network = NetworkConfig(
119
- ... name="mychain",
120
- ... display_name="My Custom Chain",
121
- ... network_type=NetworkType.EVM,
122
- ... chain_id=12345,
123
- ... usdc_address="0x...",
124
- ... )
125
- >>> register_network(custom_network)
126
- """
127
- _NETWORK_REGISTRY[config.name.lower()] = config
128
-
129
-
130
- def get_network(name: str) -> Optional[NetworkConfig]:
131
- """
132
- Get network configuration by name.
133
-
134
- Args:
135
- name: Network identifier (case-insensitive)
136
-
137
- Returns:
138
- NetworkConfig if found, None otherwise
139
- """
140
- return _NETWORK_REGISTRY.get(name.lower())
141
-
142
-
143
- def get_network_by_chain_id(chain_id: int) -> Optional[NetworkConfig]:
144
- """
145
- Get network configuration by EVM chain ID.
146
-
147
- Args:
148
- chain_id: EVM chain ID
149
-
150
- Returns:
151
- NetworkConfig if found, None otherwise
152
- """
153
- for config in _NETWORK_REGISTRY.values():
154
- if config.chain_id == chain_id and config.network_type == NetworkType.EVM:
155
- return config
156
- return None
157
-
158
-
159
- def list_networks(
160
- enabled_only: bool = True,
161
- network_type: Optional[NetworkType] = None,
162
- ) -> List[NetworkConfig]:
163
- """
164
- List all registered networks.
165
-
166
- Args:
167
- enabled_only: Only return enabled networks
168
- network_type: Filter by network type
169
-
170
- Returns:
171
- List of matching NetworkConfig instances
172
- """
173
- networks = list(_NETWORK_REGISTRY.values())
174
-
175
- if enabled_only:
176
- networks = [n for n in networks if n.enabled]
177
-
178
- if network_type:
179
- networks = [n for n in networks if n.network_type == network_type]
180
-
181
- return networks
182
-
183
-
184
- def get_supported_chain_ids() -> List[int]:
185
- """
186
- Get list of supported EVM chain IDs.
187
-
188
- Returns:
189
- List of chain IDs for enabled EVM networks
190
- """
191
- return [
192
- n.chain_id
193
- for n in _NETWORK_REGISTRY.values()
194
- if n.enabled and n.network_type == NetworkType.EVM and n.chain_id > 0
195
- ]
196
-
197
-
198
- def get_supported_network_names() -> List[str]:
199
- """
200
- Get list of supported network names.
201
-
202
- Returns:
203
- List of network names for enabled networks
204
- """
205
- return [n.name for n in _NETWORK_REGISTRY.values() if n.enabled]
206
-
207
-
208
- # Expose registry for inspection
209
- SUPPORTED_NETWORKS = _NETWORK_REGISTRY
210
-
211
-
212
- # =============================================================================
213
- # CAIP-2 Utilities (x402 v2 support)
214
- # =============================================================================
215
-
216
- # CAIP-2 namespace to network mapping
217
- _CAIP2_NAMESPACE_MAP = {
218
- "eip155": NetworkType.EVM,
219
- "solana": NetworkType.SVM,
220
- "near": NetworkType.NEAR,
221
- "stellar": NetworkType.STELLAR,
222
- }
223
-
224
- # Network name to CAIP-2 format
225
- _NETWORK_TO_CAIP2 = {
226
- # EVM chains (eip155:chainId)
227
- "base": "eip155:8453",
228
- "ethereum": "eip155:1",
229
- "polygon": "eip155:137",
230
- "arbitrum": "eip155:42161",
231
- "optimism": "eip155:10",
232
- "avalanche": "eip155:43114",
233
- "celo": "eip155:42220",
234
- "hyperevm": "eip155:999",
235
- "unichain": "eip155:130",
236
- "monad": "eip155:143",
237
- # SVM chains (solana:genesisHash first 32 chars)
238
- "solana": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
239
- "fogo": "solana:fogo", # Placeholder - update when known
240
- # NEAR
241
- "near": "near:mainnet",
242
- # Stellar
243
- "stellar": "stellar:pubnet",
244
- }
245
-
246
- # CAIP-2 to network name mapping (reverse of above)
247
- _CAIP2_TO_NETWORK = {v: k for k, v in _NETWORK_TO_CAIP2.items()}
248
-
249
-
250
- def parse_caip2_network(caip2_id: str) -> Optional[str]:
251
- """
252
- Parse a CAIP-2 network identifier to network name.
253
-
254
- CAIP-2 format: namespace:reference
255
- Examples:
256
- - "eip155:8453" -> "base"
257
- - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" -> "solana"
258
- - "near:mainnet" -> "near"
259
-
260
- Args:
261
- caip2_id: CAIP-2 format network identifier
262
-
263
- Returns:
264
- Network name if recognized, None otherwise
265
- """
266
- if not caip2_id or ":" not in caip2_id:
267
- return None
268
-
269
- # Direct lookup first
270
- if caip2_id in _CAIP2_TO_NETWORK:
271
- return _CAIP2_TO_NETWORK[caip2_id]
272
-
273
- # Parse namespace and reference
274
- parts = caip2_id.split(":", 1)
275
- if len(parts) != 2:
276
- return None
277
-
278
- namespace, reference = parts
279
-
280
- # For EIP-155 (EVM), the reference is the chain ID
281
- if namespace == "eip155":
282
- try:
283
- chain_id = int(reference)
284
- network = get_network_by_chain_id(chain_id)
285
- return network.name if network else None
286
- except ValueError:
287
- return None
288
-
289
- # For other namespaces, check if reference matches known patterns
290
- # This handles cases like "solana:mainnet" or "near:mainnet"
291
- if namespace == "solana" and reference in ("mainnet", "mainnet-beta"):
292
- return "solana"
293
- if namespace == "near" and reference == "mainnet":
294
- return "near"
295
- if namespace == "stellar" and reference in ("pubnet", "mainnet"):
296
- return "stellar"
297
-
298
- return None
299
-
300
-
301
- def to_caip2_network(network_name: str) -> Optional[str]:
302
- """
303
- Convert network name to CAIP-2 format.
304
-
305
- Args:
306
- network_name: Network identifier (e.g., 'base', 'solana')
307
-
308
- Returns:
309
- CAIP-2 format string (e.g., 'eip155:8453'), or None if unknown
310
- """
311
- return _NETWORK_TO_CAIP2.get(network_name.lower())
312
-
313
-
314
- def is_caip2_format(network: str) -> bool:
315
- """
316
- Check if a network identifier is in CAIP-2 format.
317
-
318
- Args:
319
- network: Network identifier to check
320
-
321
- Returns:
322
- True if CAIP-2 format (contains colon), False if v1 format
323
- """
324
- return ":" in network
325
-
326
-
327
- def normalize_network(network: str) -> str:
328
- """
329
- Normalize a network identifier to v1 format (network name).
330
-
331
- Handles both v1 ("base") and v2 CAIP-2 ("eip155:8453") formats.
332
-
333
- Args:
334
- network: Network identifier in either format
335
-
336
- Returns:
337
- Normalized network name (v1 format)
338
-
339
- Raises:
340
- ValueError: If network cannot be parsed
341
- """
342
- if is_caip2_format(network):
343
- normalized = parse_caip2_network(network)
344
- if normalized is None:
345
- raise ValueError(f"Unknown CAIP-2 network: {network}")
346
- return normalized
347
- return network.lower()
1
+ """
2
+ Base network configuration and registry.
3
+
4
+ This module provides the foundation for network configuration, including:
5
+ - NetworkConfig dataclass for defining network parameters
6
+ - NetworkType enum for categorizing networks
7
+ - Global registry for storing and retrieving network configurations
8
+ """
9
+
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from typing import Dict, List, Optional, Any
13
+
14
+
15
+ class NetworkType(Enum):
16
+ """
17
+ Network type categorization.
18
+
19
+ Different network types use different signature/transaction formats:
20
+ - EVM: EIP-712 signed TransferWithAuthorization (ERC-3009)
21
+ - SVM: Partially-signed VersionedTransaction (SPL token transfer) - Solana, Fogo
22
+ - NEAR: NEP-366 SignedDelegateAction (meta-transaction)
23
+ - STELLAR: Soroban Authorization Entry XDR
24
+
25
+ Note: SOLANA is deprecated, use SVM instead for Solana-compatible chains.
26
+ """
27
+
28
+ EVM = "evm"
29
+ SVM = "svm" # Solana Virtual Machine chains (Solana, Fogo, etc.)
30
+ SOLANA = "solana" # Deprecated: use SVM
31
+ NEAR = "near"
32
+ STELLAR = "stellar"
33
+
34
+ @classmethod
35
+ def is_svm(cls, network_type: "NetworkType") -> bool:
36
+ """Check if network type is SVM-compatible (Solana, Fogo, etc.)."""
37
+ return network_type in (cls.SVM, cls.SOLANA)
38
+
39
+
40
+ @dataclass
41
+ class NetworkConfig:
42
+ """
43
+ Configuration for a blockchain network supporting x402 payments.
44
+
45
+ Attributes:
46
+ name: Lowercase network identifier (e.g., 'base', 'solana')
47
+ display_name: Human-readable name (e.g., 'Base', 'Solana')
48
+ network_type: Type of network (EVM, SOLANA, NEAR, STELLAR)
49
+ chain_id: EVM chain ID (0 for non-EVM networks)
50
+ usdc_address: USDC contract/token address
51
+ usdc_decimals: Number of decimals for USDC (6 for EVM/SVM, 7 for Stellar)
52
+ usdc_domain_name: EIP-712 domain name for USDC (EVM only)
53
+ usdc_domain_version: EIP-712 domain version (EVM only)
54
+ rpc_url: Default RPC endpoint
55
+ enabled: Whether network is currently enabled
56
+ extra_config: Additional network-specific configuration
57
+ """
58
+
59
+ name: str
60
+ display_name: str
61
+ network_type: NetworkType
62
+ chain_id: int = 0
63
+ usdc_address: str = ""
64
+ usdc_decimals: int = 6
65
+ usdc_domain_name: str = "USD Coin"
66
+ usdc_domain_version: str = "2"
67
+ rpc_url: str = ""
68
+ enabled: bool = True
69
+ extra_config: Dict[str, Any] = field(default_factory=dict)
70
+
71
+ def __post_init__(self) -> None:
72
+ """Validate configuration after initialization."""
73
+ if not self.name:
74
+ raise ValueError("Network name is required")
75
+ if not self.usdc_address:
76
+ raise ValueError(f"USDC address is required for network {self.name}")
77
+
78
+ def get_token_amount(self, usd_amount: float) -> int:
79
+ """
80
+ Convert USD amount to token base units.
81
+
82
+ Args:
83
+ usd_amount: Amount in USD (e.g., 10.50)
84
+
85
+ Returns:
86
+ Amount in token base units (e.g., 10500000 for 6 decimals)
87
+ """
88
+ return int(usd_amount * (10**self.usdc_decimals))
89
+
90
+ def format_token_amount(self, base_units: int) -> float:
91
+ """
92
+ Convert token base units to USD amount.
93
+
94
+ Args:
95
+ base_units: Amount in token base units
96
+
97
+ Returns:
98
+ Amount in USD
99
+ """
100
+ return base_units / (10**self.usdc_decimals)
101
+
102
+
103
+ # Global network registry
104
+ _NETWORK_REGISTRY: Dict[str, NetworkConfig] = {}
105
+
106
+
107
+ def register_network(config: NetworkConfig) -> None:
108
+ """
109
+ Register a network configuration.
110
+
111
+ This allows adding custom networks or overriding built-in configurations.
112
+
113
+ Args:
114
+ config: NetworkConfig instance to register
115
+
116
+ Example:
117
+ >>> from uvd_x402_sdk.networks import register_network, NetworkConfig, NetworkType
118
+ >>> custom_network = NetworkConfig(
119
+ ... name="mychain",
120
+ ... display_name="My Custom Chain",
121
+ ... network_type=NetworkType.EVM,
122
+ ... chain_id=12345,
123
+ ... usdc_address="0x...",
124
+ ... )
125
+ >>> register_network(custom_network)
126
+ """
127
+ _NETWORK_REGISTRY[config.name.lower()] = config
128
+
129
+
130
+ def get_network(name: str) -> Optional[NetworkConfig]:
131
+ """
132
+ Get network configuration by name.
133
+
134
+ Args:
135
+ name: Network identifier (case-insensitive)
136
+
137
+ Returns:
138
+ NetworkConfig if found, None otherwise
139
+ """
140
+ return _NETWORK_REGISTRY.get(name.lower())
141
+
142
+
143
+ def get_network_by_chain_id(chain_id: int) -> Optional[NetworkConfig]:
144
+ """
145
+ Get network configuration by EVM chain ID.
146
+
147
+ Args:
148
+ chain_id: EVM chain ID
149
+
150
+ Returns:
151
+ NetworkConfig if found, None otherwise
152
+ """
153
+ for config in _NETWORK_REGISTRY.values():
154
+ if config.chain_id == chain_id and config.network_type == NetworkType.EVM:
155
+ return config
156
+ return None
157
+
158
+
159
+ def list_networks(
160
+ enabled_only: bool = True,
161
+ network_type: Optional[NetworkType] = None,
162
+ ) -> List[NetworkConfig]:
163
+ """
164
+ List all registered networks.
165
+
166
+ Args:
167
+ enabled_only: Only return enabled networks
168
+ network_type: Filter by network type
169
+
170
+ Returns:
171
+ List of matching NetworkConfig instances
172
+ """
173
+ networks = list(_NETWORK_REGISTRY.values())
174
+
175
+ if enabled_only:
176
+ networks = [n for n in networks if n.enabled]
177
+
178
+ if network_type:
179
+ networks = [n for n in networks if n.network_type == network_type]
180
+
181
+ return networks
182
+
183
+
184
+ def get_supported_chain_ids() -> List[int]:
185
+ """
186
+ Get list of supported EVM chain IDs.
187
+
188
+ Returns:
189
+ List of chain IDs for enabled EVM networks
190
+ """
191
+ return [
192
+ n.chain_id
193
+ for n in _NETWORK_REGISTRY.values()
194
+ if n.enabled and n.network_type == NetworkType.EVM and n.chain_id > 0
195
+ ]
196
+
197
+
198
+ def get_supported_network_names() -> List[str]:
199
+ """
200
+ Get list of supported network names.
201
+
202
+ Returns:
203
+ List of network names for enabled networks
204
+ """
205
+ return [n.name for n in _NETWORK_REGISTRY.values() if n.enabled]
206
+
207
+
208
+ # Expose registry for inspection
209
+ SUPPORTED_NETWORKS = _NETWORK_REGISTRY
210
+
211
+
212
+ # =============================================================================
213
+ # CAIP-2 Utilities (x402 v2 support)
214
+ # =============================================================================
215
+
216
+ # CAIP-2 namespace to network mapping
217
+ _CAIP2_NAMESPACE_MAP = {
218
+ "eip155": NetworkType.EVM,
219
+ "solana": NetworkType.SVM,
220
+ "near": NetworkType.NEAR,
221
+ "stellar": NetworkType.STELLAR,
222
+ }
223
+
224
+ # Network name to CAIP-2 format
225
+ _NETWORK_TO_CAIP2 = {
226
+ # EVM chains (eip155:chainId)
227
+ "base": "eip155:8453",
228
+ "ethereum": "eip155:1",
229
+ "polygon": "eip155:137",
230
+ "arbitrum": "eip155:42161",
231
+ "optimism": "eip155:10",
232
+ "avalanche": "eip155:43114",
233
+ "celo": "eip155:42220",
234
+ "hyperevm": "eip155:999",
235
+ "unichain": "eip155:130",
236
+ "monad": "eip155:143",
237
+ # SVM chains (solana:genesisHash first 32 chars)
238
+ "solana": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
239
+ "fogo": "solana:fogo", # Placeholder - update when known
240
+ # NEAR
241
+ "near": "near:mainnet",
242
+ # Stellar
243
+ "stellar": "stellar:pubnet",
244
+ }
245
+
246
+ # CAIP-2 to network name mapping (reverse of above)
247
+ _CAIP2_TO_NETWORK = {v: k for k, v in _NETWORK_TO_CAIP2.items()}
248
+
249
+
250
+ def parse_caip2_network(caip2_id: str) -> Optional[str]:
251
+ """
252
+ Parse a CAIP-2 network identifier to network name.
253
+
254
+ CAIP-2 format: namespace:reference
255
+ Examples:
256
+ - "eip155:8453" -> "base"
257
+ - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" -> "solana"
258
+ - "near:mainnet" -> "near"
259
+
260
+ Args:
261
+ caip2_id: CAIP-2 format network identifier
262
+
263
+ Returns:
264
+ Network name if recognized, None otherwise
265
+ """
266
+ if not caip2_id or ":" not in caip2_id:
267
+ return None
268
+
269
+ # Direct lookup first
270
+ if caip2_id in _CAIP2_TO_NETWORK:
271
+ return _CAIP2_TO_NETWORK[caip2_id]
272
+
273
+ # Parse namespace and reference
274
+ parts = caip2_id.split(":", 1)
275
+ if len(parts) != 2:
276
+ return None
277
+
278
+ namespace, reference = parts
279
+
280
+ # For EIP-155 (EVM), the reference is the chain ID
281
+ if namespace == "eip155":
282
+ try:
283
+ chain_id = int(reference)
284
+ network = get_network_by_chain_id(chain_id)
285
+ return network.name if network else None
286
+ except ValueError:
287
+ return None
288
+
289
+ # For other namespaces, check if reference matches known patterns
290
+ # This handles cases like "solana:mainnet" or "near:mainnet"
291
+ if namespace == "solana" and reference in ("mainnet", "mainnet-beta"):
292
+ return "solana"
293
+ if namespace == "near" and reference == "mainnet":
294
+ return "near"
295
+ if namespace == "stellar" and reference in ("pubnet", "mainnet"):
296
+ return "stellar"
297
+
298
+ return None
299
+
300
+
301
+ def to_caip2_network(network_name: str) -> Optional[str]:
302
+ """
303
+ Convert network name to CAIP-2 format.
304
+
305
+ Args:
306
+ network_name: Network identifier (e.g., 'base', 'solana')
307
+
308
+ Returns:
309
+ CAIP-2 format string (e.g., 'eip155:8453'), or None if unknown
310
+ """
311
+ return _NETWORK_TO_CAIP2.get(network_name.lower())
312
+
313
+
314
+ def is_caip2_format(network: str) -> bool:
315
+ """
316
+ Check if a network identifier is in CAIP-2 format.
317
+
318
+ Args:
319
+ network: Network identifier to check
320
+
321
+ Returns:
322
+ True if CAIP-2 format (contains colon), False if v1 format
323
+ """
324
+ return ":" in network
325
+
326
+
327
+ def normalize_network(network: str) -> str:
328
+ """
329
+ Normalize a network identifier to v1 format (network name).
330
+
331
+ Handles both v1 ("base") and v2 CAIP-2 ("eip155:8453") formats.
332
+
333
+ Args:
334
+ network: Network identifier in either format
335
+
336
+ Returns:
337
+ Normalized network name (v1 format)
338
+
339
+ Raises:
340
+ ValueError: If network cannot be parsed
341
+ """
342
+ if is_caip2_format(network):
343
+ normalized = parse_caip2_network(network)
344
+ if normalized is None:
345
+ raise ValueError(f"Unknown CAIP-2 network: {network}")
346
+ return normalized
347
+ return network.lower()