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.
- uvd_x402_sdk/__init__.py +185 -169
- uvd_x402_sdk/client.py +527 -527
- uvd_x402_sdk/config.py +248 -248
- uvd_x402_sdk/decorators.py +325 -325
- uvd_x402_sdk/exceptions.py +254 -254
- uvd_x402_sdk/integrations/__init__.py +74 -74
- uvd_x402_sdk/integrations/django_integration.py +237 -237
- uvd_x402_sdk/integrations/fastapi_integration.py +330 -330
- uvd_x402_sdk/integrations/flask_integration.py +259 -259
- uvd_x402_sdk/integrations/lambda_integration.py +320 -320
- uvd_x402_sdk/models.py +397 -397
- uvd_x402_sdk/networks/__init__.py +80 -54
- uvd_x402_sdk/networks/base.py +498 -347
- uvd_x402_sdk/networks/evm.py +356 -215
- uvd_x402_sdk/networks/near.py +397 -397
- uvd_x402_sdk/networks/solana.py +282 -282
- uvd_x402_sdk/networks/stellar.py +129 -129
- uvd_x402_sdk/response.py +439 -439
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/LICENSE +21 -21
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/METADATA +927 -778
- uvd_x402_sdk-0.3.0.dist-info/RECORD +23 -0
- uvd_x402_sdk-0.2.2.dist-info/RECORD +0 -23
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/WHEEL +0 -0
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/top_level.txt +0 -0
uvd_x402_sdk/networks/base.py
CHANGED
|
@@ -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
|
-
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
from
|
|
12
|
-
from
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
def
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
Returns:
|
|
189
|
-
|
|
190
|
-
"""
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
"
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
#
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
Returns:
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
network
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
+
- 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()
|