uvd-x402-sdk 0.2.2__py3-none-any.whl → 0.3.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.
@@ -1,347 +1,498 @@
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
+ - TokenType for multi-stablecoin support
8
+ - Global registry for storing and retrieving network configurations
9
+ """
10
+
11
+ from dataclasses import dataclass, field
12
+ from enum import Enum
13
+ from typing import Dict, List, Literal, Optional, Any
14
+
15
+
16
+ # =============================================================================
17
+ # Token Type Definitions (Multi-Stablecoin Support)
18
+ # =============================================================================
19
+
20
+ # Supported stablecoin token types
21
+ # - usdc: USD Coin (Circle) - 6 decimals
22
+ # - eurc: Euro Coin (Circle) - 6 decimals
23
+ # - ausd: Agora USD (Agora Finance) - 6 decimals
24
+ # - pyusd: PayPal USD (PayPal/Paxos) - 6 decimals
25
+ # - gho: GHO Stablecoin (Aave) - 18 decimals
26
+ # - crvusd: Curve USD (Curve Finance) - 18 decimals
27
+ TokenType = Literal["usdc", "eurc", "ausd", "pyusd", "gho", "crvusd"]
28
+
29
+ # All supported token types
30
+ ALL_TOKEN_TYPES: List[TokenType] = ["usdc", "eurc", "ausd", "pyusd", "gho", "crvusd"]
31
+
32
+
33
+ @dataclass
34
+ class TokenConfig:
35
+ """
36
+ Configuration for a stablecoin token on a specific network.
37
+
38
+ Attributes:
39
+ address: Contract address of the token
40
+ decimals: Number of decimals (6 for most stablecoins, 18 for GHO/crvUSD)
41
+ name: Token name for EIP-712 domain (e.g., "USD Coin" or "USDC")
42
+ version: Token version for EIP-712 domain
43
+ """
44
+
45
+ address: str
46
+ decimals: int
47
+ name: str
48
+ version: str
49
+
50
+
51
+ class NetworkType(Enum):
52
+ """
53
+ Network type categorization.
54
+
55
+ Different network types use different signature/transaction formats:
56
+ - EVM: EIP-712 signed TransferWithAuthorization (ERC-3009)
57
+ - SVM: Partially-signed VersionedTransaction (SPL token transfer) - Solana, Fogo
58
+ - NEAR: NEP-366 SignedDelegateAction (meta-transaction)
59
+ - STELLAR: Soroban Authorization Entry XDR
60
+
61
+ Note: SOLANA is deprecated, use SVM instead for Solana-compatible chains.
62
+ """
63
+
64
+ EVM = "evm"
65
+ SVM = "svm" # Solana Virtual Machine chains (Solana, Fogo, etc.)
66
+ SOLANA = "solana" # Deprecated: use SVM
67
+ NEAR = "near"
68
+ STELLAR = "stellar"
69
+
70
+ @classmethod
71
+ def is_svm(cls, network_type: "NetworkType") -> bool:
72
+ """Check if network type is SVM-compatible (Solana, Fogo, etc.)."""
73
+ return network_type in (cls.SVM, cls.SOLANA)
74
+
75
+
76
+ @dataclass
77
+ class NetworkConfig:
78
+ """
79
+ Configuration for a blockchain network supporting x402 payments.
80
+
81
+ Attributes:
82
+ name: Lowercase network identifier (e.g., 'base', 'solana')
83
+ display_name: Human-readable name (e.g., 'Base', 'Solana')
84
+ network_type: Type of network (EVM, SOLANA, NEAR, STELLAR)
85
+ chain_id: EVM chain ID (0 for non-EVM networks)
86
+ usdc_address: USDC contract/token address
87
+ usdc_decimals: Number of decimals for USDC (6 for EVM/SVM, 7 for Stellar)
88
+ usdc_domain_name: EIP-712 domain name for USDC (EVM only)
89
+ usdc_domain_version: EIP-712 domain version (EVM only)
90
+ rpc_url: Default RPC endpoint
91
+ enabled: Whether network is currently enabled
92
+ tokens: Multi-token configurations (EVM chains only, maps token type to config)
93
+ extra_config: Additional network-specific configuration
94
+ """
95
+
96
+ name: str
97
+ display_name: str
98
+ network_type: NetworkType
99
+ chain_id: int = 0
100
+ usdc_address: str = ""
101
+ usdc_decimals: int = 6
102
+ usdc_domain_name: str = "USD Coin"
103
+ usdc_domain_version: str = "2"
104
+ rpc_url: str = ""
105
+ enabled: bool = True
106
+ tokens: Dict[TokenType, TokenConfig] = field(default_factory=dict)
107
+ extra_config: Dict[str, Any] = field(default_factory=dict)
108
+
109
+ def __post_init__(self) -> None:
110
+ """Validate configuration after initialization."""
111
+ if not self.name:
112
+ raise ValueError("Network name is required")
113
+ if not self.usdc_address:
114
+ raise ValueError(f"USDC address is required for network {self.name}")
115
+
116
+ def get_token_amount(self, usd_amount: float) -> int:
117
+ """
118
+ Convert USD amount to token base units.
119
+
120
+ Args:
121
+ usd_amount: Amount in USD (e.g., 10.50)
122
+
123
+ Returns:
124
+ Amount in token base units (e.g., 10500000 for 6 decimals)
125
+ """
126
+ return int(usd_amount * (10**self.usdc_decimals))
127
+
128
+ def format_token_amount(self, base_units: int) -> float:
129
+ """
130
+ Convert token base units to USD amount.
131
+
132
+ Args:
133
+ base_units: Amount in token base units
134
+
135
+ Returns:
136
+ Amount in USD
137
+ """
138
+ return base_units / (10**self.usdc_decimals)
139
+
140
+
141
+ # Global network registry
142
+ _NETWORK_REGISTRY: Dict[str, NetworkConfig] = {}
143
+
144
+
145
+ def register_network(config: NetworkConfig) -> None:
146
+ """
147
+ Register a network configuration.
148
+
149
+ This allows adding custom networks or overriding built-in configurations.
150
+
151
+ Args:
152
+ config: NetworkConfig instance to register
153
+
154
+ Example:
155
+ >>> from uvd_x402_sdk.networks import register_network, NetworkConfig, NetworkType
156
+ >>> custom_network = NetworkConfig(
157
+ ... name="mychain",
158
+ ... display_name="My Custom Chain",
159
+ ... network_type=NetworkType.EVM,
160
+ ... chain_id=12345,
161
+ ... usdc_address="0x...",
162
+ ... )
163
+ >>> register_network(custom_network)
164
+ """
165
+ _NETWORK_REGISTRY[config.name.lower()] = config
166
+
167
+
168
+ def get_network(name: str) -> Optional[NetworkConfig]:
169
+ """
170
+ Get network configuration by name.
171
+
172
+ Args:
173
+ name: Network identifier (case-insensitive)
174
+
175
+ Returns:
176
+ NetworkConfig if found, None otherwise
177
+ """
178
+ return _NETWORK_REGISTRY.get(name.lower())
179
+
180
+
181
+ def get_network_by_chain_id(chain_id: int) -> Optional[NetworkConfig]:
182
+ """
183
+ Get network configuration by EVM chain ID.
184
+
185
+ Args:
186
+ chain_id: EVM chain ID
187
+
188
+ Returns:
189
+ NetworkConfig if found, None otherwise
190
+ """
191
+ for config in _NETWORK_REGISTRY.values():
192
+ if config.chain_id == chain_id and config.network_type == NetworkType.EVM:
193
+ return config
194
+ return None
195
+
196
+
197
+ def list_networks(
198
+ enabled_only: bool = True,
199
+ network_type: Optional[NetworkType] = None,
200
+ ) -> List[NetworkConfig]:
201
+ """
202
+ List all registered networks.
203
+
204
+ Args:
205
+ enabled_only: Only return enabled networks
206
+ network_type: Filter by network type
207
+
208
+ Returns:
209
+ List of matching NetworkConfig instances
210
+ """
211
+ networks = list(_NETWORK_REGISTRY.values())
212
+
213
+ if enabled_only:
214
+ networks = [n for n in networks if n.enabled]
215
+
216
+ if network_type:
217
+ networks = [n for n in networks if n.network_type == network_type]
218
+
219
+ return networks
220
+
221
+
222
+ def get_supported_chain_ids() -> List[int]:
223
+ """
224
+ Get list of supported EVM chain IDs.
225
+
226
+ Returns:
227
+ List of chain IDs for enabled EVM networks
228
+ """
229
+ return [
230
+ n.chain_id
231
+ for n in _NETWORK_REGISTRY.values()
232
+ if n.enabled and n.network_type == NetworkType.EVM and n.chain_id > 0
233
+ ]
234
+
235
+
236
+ def get_supported_network_names() -> List[str]:
237
+ """
238
+ Get list of supported network names.
239
+
240
+ Returns:
241
+ List of network names for enabled networks
242
+ """
243
+ return [n.name for n in _NETWORK_REGISTRY.values() if n.enabled]
244
+
245
+
246
+ # Expose registry for inspection
247
+ SUPPORTED_NETWORKS = _NETWORK_REGISTRY
248
+
249
+
250
+ # =============================================================================
251
+ # Token Helper Functions (Multi-Stablecoin Support)
252
+ # =============================================================================
253
+
254
+
255
+ def get_token_config(network_name: str, token_type: TokenType = "usdc") -> Optional[TokenConfig]:
256
+ """
257
+ Get token configuration for a specific network and token type.
258
+
259
+ Args:
260
+ network_name: Network identifier (e.g., 'base', 'ethereum')
261
+ token_type: Token type (defaults to 'usdc')
262
+
263
+ Returns:
264
+ TokenConfig if the token is supported on this network, None otherwise
265
+
266
+ Example:
267
+ >>> config = get_token_config('ethereum', 'eurc')
268
+ >>> if config:
269
+ ... print(f"EURC address: {config.address}")
270
+ """
271
+ network = get_network(network_name)
272
+ if not network:
273
+ return None
274
+
275
+ # Check tokens dict first (multi-token support)
276
+ if token_type in network.tokens:
277
+ return network.tokens[token_type]
278
+
279
+ # Fall back to USDC config for backward compatibility
280
+ if token_type == "usdc":
281
+ return TokenConfig(
282
+ address=network.usdc_address,
283
+ decimals=network.usdc_decimals,
284
+ name=network.usdc_domain_name,
285
+ version=network.usdc_domain_version,
286
+ )
287
+
288
+ return None
289
+
290
+
291
+ def get_supported_tokens(network_name: str) -> List[TokenType]:
292
+ """
293
+ Get list of supported token types for a network.
294
+
295
+ Args:
296
+ network_name: Network identifier
297
+
298
+ Returns:
299
+ List of supported TokenType values
300
+
301
+ Example:
302
+ >>> tokens = get_supported_tokens('ethereum')
303
+ >>> print(tokens) # ['usdc', 'eurc', 'ausd', 'pyusd', 'gho', 'crvusd']
304
+ """
305
+ network = get_network(network_name)
306
+ if not network:
307
+ return []
308
+
309
+ # Get tokens from the tokens dict
310
+ tokens: List[TokenType] = list(network.tokens.keys())
311
+
312
+ # Always include 'usdc' if the network has USDC configured
313
+ if "usdc" not in tokens and network.usdc_address:
314
+ tokens.insert(0, "usdc")
315
+
316
+ return tokens
317
+
318
+
319
+ def is_token_supported(network_name: str, token_type: TokenType) -> bool:
320
+ """
321
+ Check if a specific token is supported on a network.
322
+
323
+ Args:
324
+ network_name: Network identifier
325
+ token_type: Token type to check
326
+
327
+ Returns:
328
+ True if token is supported, False otherwise
329
+
330
+ Example:
331
+ >>> is_token_supported('ethereum', 'eurc')
332
+ True
333
+ >>> is_token_supported('celo', 'eurc')
334
+ False
335
+ """
336
+ return get_token_config(network_name, token_type) is not None
337
+
338
+
339
+ def get_networks_by_token(token_type: TokenType) -> List[NetworkConfig]:
340
+ """
341
+ Get all networks that support a specific token type.
342
+
343
+ Args:
344
+ token_type: Token type to search for
345
+
346
+ Returns:
347
+ List of NetworkConfig instances that support the token
348
+
349
+ Example:
350
+ >>> networks = get_networks_by_token('eurc')
351
+ >>> for n in networks:
352
+ ... print(n.name) # ethereum, base, avalanche
353
+ """
354
+ result = []
355
+ for network in _NETWORK_REGISTRY.values():
356
+ if not network.enabled:
357
+ continue
358
+ if is_token_supported(network.name, token_type):
359
+ result.append(network)
360
+ return result
361
+
362
+
363
+ # =============================================================================
364
+ # CAIP-2 Utilities (x402 v2 support)
365
+ # =============================================================================
366
+
367
+ # CAIP-2 namespace to network mapping
368
+ _CAIP2_NAMESPACE_MAP = {
369
+ "eip155": NetworkType.EVM,
370
+ "solana": NetworkType.SVM,
371
+ "near": NetworkType.NEAR,
372
+ "stellar": NetworkType.STELLAR,
373
+ }
374
+
375
+ # Network name to CAIP-2 format
376
+ _NETWORK_TO_CAIP2 = {
377
+ # EVM chains (eip155:chainId)
378
+ "base": "eip155:8453",
379
+ "ethereum": "eip155:1",
380
+ "polygon": "eip155:137",
381
+ "arbitrum": "eip155:42161",
382
+ "optimism": "eip155:10",
383
+ "avalanche": "eip155:43114",
384
+ "celo": "eip155:42220",
385
+ "hyperevm": "eip155:999",
386
+ "unichain": "eip155:130",
387
+ "monad": "eip155:143",
388
+ # SVM chains (solana:genesisHash first 32 chars)
389
+ "solana": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
390
+ "fogo": "solana:fogo", # Placeholder - update when known
391
+ # NEAR
392
+ "near": "near:mainnet",
393
+ # Stellar
394
+ "stellar": "stellar:pubnet",
395
+ }
396
+
397
+ # CAIP-2 to network name mapping (reverse of above)
398
+ _CAIP2_TO_NETWORK = {v: k for k, v in _NETWORK_TO_CAIP2.items()}
399
+
400
+
401
+ def parse_caip2_network(caip2_id: str) -> Optional[str]:
402
+ """
403
+ Parse a CAIP-2 network identifier to network name.
404
+
405
+ CAIP-2 format: namespace:reference
406
+ Examples:
407
+ - "eip155:8453" -> "base"
408
+ - "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" -> "solana"
409
+ - "near:mainnet" -> "near"
410
+
411
+ Args:
412
+ caip2_id: CAIP-2 format network identifier
413
+
414
+ Returns:
415
+ Network name if recognized, None otherwise
416
+ """
417
+ if not caip2_id or ":" not in caip2_id:
418
+ return None
419
+
420
+ # Direct lookup first
421
+ if caip2_id in _CAIP2_TO_NETWORK:
422
+ return _CAIP2_TO_NETWORK[caip2_id]
423
+
424
+ # Parse namespace and reference
425
+ parts = caip2_id.split(":", 1)
426
+ if len(parts) != 2:
427
+ return None
428
+
429
+ namespace, reference = parts
430
+
431
+ # For EIP-155 (EVM), the reference is the chain ID
432
+ if namespace == "eip155":
433
+ try:
434
+ chain_id = int(reference)
435
+ network = get_network_by_chain_id(chain_id)
436
+ return network.name if network else None
437
+ except ValueError:
438
+ return None
439
+
440
+ # For other namespaces, check if reference matches known patterns
441
+ # This handles cases like "solana:mainnet" or "near:mainnet"
442
+ if namespace == "solana" and reference in ("mainnet", "mainnet-beta"):
443
+ return "solana"
444
+ if namespace == "near" and reference == "mainnet":
445
+ return "near"
446
+ if namespace == "stellar" and reference in ("pubnet", "mainnet"):
447
+ return "stellar"
448
+
449
+ return None
450
+
451
+
452
+ def to_caip2_network(network_name: str) -> Optional[str]:
453
+ """
454
+ Convert network name to CAIP-2 format.
455
+
456
+ Args:
457
+ network_name: Network identifier (e.g., 'base', 'solana')
458
+
459
+ Returns:
460
+ CAIP-2 format string (e.g., 'eip155:8453'), or None if unknown
461
+ """
462
+ return _NETWORK_TO_CAIP2.get(network_name.lower())
463
+
464
+
465
+ def is_caip2_format(network: str) -> bool:
466
+ """
467
+ Check if a network identifier is in CAIP-2 format.
468
+
469
+ Args:
470
+ network: Network identifier to check
471
+
472
+ Returns:
473
+ True if CAIP-2 format (contains colon), False if v1 format
474
+ """
475
+ return ":" in network
476
+
477
+
478
+ def normalize_network(network: str) -> str:
479
+ """
480
+ Normalize a network identifier to v1 format (network name).
481
+
482
+ Handles both v1 ("base") and v2 CAIP-2 ("eip155:8453") formats.
483
+
484
+ Args:
485
+ network: Network identifier in either format
486
+
487
+ Returns:
488
+ Normalized network name (v1 format)
489
+
490
+ Raises:
491
+ ValueError: If network cannot be parsed
492
+ """
493
+ if is_caip2_format(network):
494
+ normalized = parse_caip2_network(network)
495
+ if normalized is None:
496
+ raise ValueError(f"Unknown CAIP-2 network: {network}")
497
+ return normalized
498
+ return network.lower()