uvd-x402-sdk 0.4.2__py3-none-any.whl → 0.5.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 +48 -4
- uvd_x402_sdk/facilitator.py +384 -0
- uvd_x402_sdk/networks/algorand.py +47 -0
- uvd_x402_sdk/networks/near.py +46 -0
- uvd_x402_sdk/networks/solana.py +58 -0
- uvd_x402_sdk/networks/stellar.py +46 -0
- {uvd_x402_sdk-0.4.2.dist-info → uvd_x402_sdk-0.5.0.dist-info}/METADATA +1 -1
- {uvd_x402_sdk-0.4.2.dist-info → uvd_x402_sdk-0.5.0.dist-info}/RECORD +11 -10
- {uvd_x402_sdk-0.4.2.dist-info → uvd_x402_sdk-0.5.0.dist-info}/LICENSE +0 -0
- {uvd_x402_sdk-0.4.2.dist-info → uvd_x402_sdk-0.5.0.dist-info}/WHEEL +0 -0
- {uvd_x402_sdk-0.4.2.dist-info → uvd_x402_sdk-0.5.0.dist-info}/top_level.txt +0 -0
uvd_x402_sdk/__init__.py
CHANGED
|
@@ -2,8 +2,11 @@
|
|
|
2
2
|
uvd-x402-sdk: Python SDK for x402 payments via Ultravioleta DAO facilitator.
|
|
3
3
|
|
|
4
4
|
This SDK enables developers to easily integrate x402 cryptocurrency payments
|
|
5
|
-
into their Python applications with support for
|
|
6
|
-
|
|
5
|
+
into their Python applications with support for 16 blockchain networks across
|
|
6
|
+
5 network types (EVM, SVM, NEAR, Stellar, Algorand).
|
|
7
|
+
|
|
8
|
+
The SDK automatically handles facilitator configuration - users don't need to
|
|
9
|
+
configure fee payer addresses or other facilitator details manually.
|
|
7
10
|
|
|
8
11
|
Supports both x402 v1 and v2 protocols:
|
|
9
12
|
- v1: network as string ("base", "solana")
|
|
@@ -29,15 +32,16 @@ Example usage:
|
|
|
29
32
|
def protected_endpoint():
|
|
30
33
|
return {"message": "Payment verified!"}
|
|
31
34
|
|
|
32
|
-
Supported Networks (
|
|
35
|
+
Supported Networks (16 total):
|
|
33
36
|
- EVM (10): Base, Ethereum, Polygon, Arbitrum, Optimism, Avalanche, Celo,
|
|
34
37
|
HyperEVM, Unichain, Monad
|
|
35
38
|
- SVM (2): Solana, Fogo
|
|
36
39
|
- NEAR (1): NEAR Protocol
|
|
37
40
|
- Stellar (1): Stellar
|
|
41
|
+
- Algorand (2): Algorand mainnet, Algorand testnet
|
|
38
42
|
"""
|
|
39
43
|
|
|
40
|
-
__version__ = "0.
|
|
44
|
+
__version__ = "0.5.0"
|
|
41
45
|
__author__ = "Ultravioleta DAO"
|
|
42
46
|
|
|
43
47
|
from uvd_x402_sdk.client import X402Client
|
|
@@ -109,6 +113,28 @@ from uvd_x402_sdk.response import (
|
|
|
109
113
|
payment_required_response_v2,
|
|
110
114
|
Payment402BuilderV2,
|
|
111
115
|
)
|
|
116
|
+
from uvd_x402_sdk.facilitator import (
|
|
117
|
+
# Facilitator URL and constants
|
|
118
|
+
DEFAULT_FACILITATOR_URL,
|
|
119
|
+
get_facilitator_url,
|
|
120
|
+
# Fee payer addresses by chain
|
|
121
|
+
ALGORAND_FEE_PAYER_MAINNET,
|
|
122
|
+
ALGORAND_FEE_PAYER_TESTNET,
|
|
123
|
+
SOLANA_FEE_PAYER_MAINNET,
|
|
124
|
+
SOLANA_FEE_PAYER_DEVNET,
|
|
125
|
+
FOGO_FEE_PAYER_MAINNET,
|
|
126
|
+
FOGO_FEE_PAYER_TESTNET,
|
|
127
|
+
NEAR_FEE_PAYER_MAINNET,
|
|
128
|
+
NEAR_FEE_PAYER_TESTNET,
|
|
129
|
+
STELLAR_FEE_PAYER_MAINNET,
|
|
130
|
+
STELLAR_FEE_PAYER_TESTNET,
|
|
131
|
+
# Helper functions
|
|
132
|
+
get_fee_payer,
|
|
133
|
+
get_facilitator_address,
|
|
134
|
+
requires_fee_payer,
|
|
135
|
+
get_all_fee_payers,
|
|
136
|
+
build_payment_info,
|
|
137
|
+
)
|
|
112
138
|
|
|
113
139
|
__all__ = [
|
|
114
140
|
# Version
|
|
@@ -182,4 +208,22 @@ __all__ = [
|
|
|
182
208
|
"create_402_headers_v2",
|
|
183
209
|
"payment_required_response_v2",
|
|
184
210
|
"Payment402BuilderV2",
|
|
211
|
+
# Facilitator constants and helpers
|
|
212
|
+
"DEFAULT_FACILITATOR_URL",
|
|
213
|
+
"get_facilitator_url",
|
|
214
|
+
"ALGORAND_FEE_PAYER_MAINNET",
|
|
215
|
+
"ALGORAND_FEE_PAYER_TESTNET",
|
|
216
|
+
"SOLANA_FEE_PAYER_MAINNET",
|
|
217
|
+
"SOLANA_FEE_PAYER_DEVNET",
|
|
218
|
+
"FOGO_FEE_PAYER_MAINNET",
|
|
219
|
+
"FOGO_FEE_PAYER_TESTNET",
|
|
220
|
+
"NEAR_FEE_PAYER_MAINNET",
|
|
221
|
+
"NEAR_FEE_PAYER_TESTNET",
|
|
222
|
+
"STELLAR_FEE_PAYER_MAINNET",
|
|
223
|
+
"STELLAR_FEE_PAYER_TESTNET",
|
|
224
|
+
"get_fee_payer",
|
|
225
|
+
"get_facilitator_address",
|
|
226
|
+
"requires_fee_payer",
|
|
227
|
+
"get_all_fee_payers",
|
|
228
|
+
"build_payment_info",
|
|
185
229
|
]
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Facilitator configuration and constants.
|
|
3
|
+
|
|
4
|
+
This module provides:
|
|
5
|
+
- Default facilitator URL
|
|
6
|
+
- Fee payer addresses for all supported non-EVM networks
|
|
7
|
+
- Helper functions to get the appropriate facilitator address for any network
|
|
8
|
+
|
|
9
|
+
Users of this SDK should NOT need to configure facilitator details manually.
|
|
10
|
+
All addresses are embedded as constants, extracted from the official facilitator.
|
|
11
|
+
|
|
12
|
+
Facilitator URL: https://facilitator.ultravioletadao.xyz
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Dict, Optional
|
|
16
|
+
|
|
17
|
+
from uvd_x402_sdk.networks.base import (
|
|
18
|
+
NetworkType,
|
|
19
|
+
get_network,
|
|
20
|
+
is_caip2_format,
|
|
21
|
+
normalize_network,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# =============================================================================
|
|
26
|
+
# Facilitator URL
|
|
27
|
+
# =============================================================================
|
|
28
|
+
|
|
29
|
+
DEFAULT_FACILITATOR_URL = "https://facilitator.ultravioletadao.xyz"
|
|
30
|
+
|
|
31
|
+
# Alternative facilitator URLs (for future use)
|
|
32
|
+
FACILITATOR_URLS = {
|
|
33
|
+
"production": "https://facilitator.ultravioletadao.xyz",
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# =============================================================================
|
|
38
|
+
# Fee Payer Addresses (Non-EVM chains require fee payer for gasless payments)
|
|
39
|
+
# =============================================================================
|
|
40
|
+
|
|
41
|
+
# Algorand fee payer addresses
|
|
42
|
+
ALGORAND_FEE_PAYER_MAINNET = "KIMS5H6QLCUDL65L5UBTOXDPWLMTS7N3AAC3I6B2NCONEI5QIVK7LH2C2I"
|
|
43
|
+
ALGORAND_FEE_PAYER_TESTNET = "5DPPDQNYUPCTXRZWRYSF3WPYU6RKAUR25F3YG4EKXQRHV5AUAI62H5GXL4"
|
|
44
|
+
|
|
45
|
+
# Solana fee payer addresses (also used for Fogo mainnet)
|
|
46
|
+
SOLANA_FEE_PAYER_MAINNET = "F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq"
|
|
47
|
+
SOLANA_FEE_PAYER_DEVNET = "6xNPewUdKRbEZDReQdpyfNUdgNg8QRc8Mt263T5GZSRv"
|
|
48
|
+
|
|
49
|
+
# Fogo (SVM) fee payer addresses
|
|
50
|
+
FOGO_FEE_PAYER_MAINNET = "F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq"
|
|
51
|
+
FOGO_FEE_PAYER_TESTNET = "6xNPewUdKRbEZDReQdpyfNUdgNg8QRc8Mt263T5GZSRv"
|
|
52
|
+
|
|
53
|
+
# NEAR fee payer addresses (account IDs)
|
|
54
|
+
NEAR_FEE_PAYER_MAINNET = "uvd-facilitator.near"
|
|
55
|
+
NEAR_FEE_PAYER_TESTNET = "uvd-facilitator.testnet"
|
|
56
|
+
|
|
57
|
+
# Stellar fee payer addresses (public keys)
|
|
58
|
+
STELLAR_FEE_PAYER_MAINNET = "GCHPGXJT2WFFRFCA5TV4G4E3PMMXLNIDUH27PKDYA4QJ2XGYZWGFZNHB"
|
|
59
|
+
STELLAR_FEE_PAYER_TESTNET = "GBBFZMLUJEZVI32EN4XA2KPP445XIBTMTRBLYWFIL556RDTHS2OWFQ2Z"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# =============================================================================
|
|
63
|
+
# Network to Fee Payer Mapping
|
|
64
|
+
# =============================================================================
|
|
65
|
+
|
|
66
|
+
# Maps network names to their fee payer addresses
|
|
67
|
+
# EVM networks don't have fee payers (they use EIP-3009 transferWithAuthorization)
|
|
68
|
+
_FEE_PAYER_BY_NETWORK: Dict[str, str] = {
|
|
69
|
+
# Algorand
|
|
70
|
+
"algorand": ALGORAND_FEE_PAYER_MAINNET,
|
|
71
|
+
"algorand-mainnet": ALGORAND_FEE_PAYER_MAINNET,
|
|
72
|
+
"algorand-testnet": ALGORAND_FEE_PAYER_TESTNET,
|
|
73
|
+
# Solana
|
|
74
|
+
"solana": SOLANA_FEE_PAYER_MAINNET,
|
|
75
|
+
"solana-mainnet": SOLANA_FEE_PAYER_MAINNET,
|
|
76
|
+
"solana-devnet": SOLANA_FEE_PAYER_DEVNET,
|
|
77
|
+
# Fogo (SVM)
|
|
78
|
+
"fogo": FOGO_FEE_PAYER_MAINNET,
|
|
79
|
+
"fogo-mainnet": FOGO_FEE_PAYER_MAINNET,
|
|
80
|
+
"fogo-testnet": FOGO_FEE_PAYER_TESTNET,
|
|
81
|
+
# NEAR
|
|
82
|
+
"near": NEAR_FEE_PAYER_MAINNET,
|
|
83
|
+
"near-mainnet": NEAR_FEE_PAYER_MAINNET,
|
|
84
|
+
"near-testnet": NEAR_FEE_PAYER_TESTNET,
|
|
85
|
+
# Stellar
|
|
86
|
+
"stellar": STELLAR_FEE_PAYER_MAINNET,
|
|
87
|
+
"stellar-mainnet": STELLAR_FEE_PAYER_MAINNET,
|
|
88
|
+
"stellar-testnet": STELLAR_FEE_PAYER_TESTNET,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
# CAIP-2 format mappings (x402 v2)
|
|
92
|
+
_FEE_PAYER_BY_CAIP2: Dict[str, str] = {
|
|
93
|
+
# Algorand
|
|
94
|
+
"algorand:mainnet": ALGORAND_FEE_PAYER_MAINNET,
|
|
95
|
+
"algorand:testnet": ALGORAND_FEE_PAYER_TESTNET,
|
|
96
|
+
# Solana
|
|
97
|
+
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp": SOLANA_FEE_PAYER_MAINNET,
|
|
98
|
+
"solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1": SOLANA_FEE_PAYER_DEVNET,
|
|
99
|
+
# Fogo (SVM)
|
|
100
|
+
"fogo:mainnet": FOGO_FEE_PAYER_MAINNET,
|
|
101
|
+
"fogo:testnet": FOGO_FEE_PAYER_TESTNET,
|
|
102
|
+
# NEAR
|
|
103
|
+
"near:mainnet": NEAR_FEE_PAYER_MAINNET,
|
|
104
|
+
"near:testnet": NEAR_FEE_PAYER_TESTNET,
|
|
105
|
+
# Stellar
|
|
106
|
+
"stellar:pubnet": STELLAR_FEE_PAYER_MAINNET,
|
|
107
|
+
"stellar:testnet": STELLAR_FEE_PAYER_TESTNET,
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# =============================================================================
|
|
112
|
+
# Helper Functions
|
|
113
|
+
# =============================================================================
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def get_fee_payer(network: str) -> Optional[str]:
|
|
117
|
+
"""
|
|
118
|
+
Get the fee payer address for a network.
|
|
119
|
+
|
|
120
|
+
Fee payers are only needed for non-EVM chains (Algorand, Solana, NEAR, Stellar).
|
|
121
|
+
EVM chains use EIP-3009 transferWithAuthorization which is gasless by design.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
network: Network identifier (v1 name or CAIP-2 format)
|
|
125
|
+
Examples: "algorand", "algorand-mainnet", "algorand:mainnet"
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
Fee payer address if applicable, None for EVM chains
|
|
129
|
+
|
|
130
|
+
Example:
|
|
131
|
+
>>> get_fee_payer("algorand")
|
|
132
|
+
'KIMS5H6QLCUDL65L5UBTOXDPWLMTS7N3AAC3I6B2NCONEI5QIVK7LH2C2I'
|
|
133
|
+
>>> get_fee_payer("solana")
|
|
134
|
+
'F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq'
|
|
135
|
+
>>> get_fee_payer("base") # EVM chain
|
|
136
|
+
None
|
|
137
|
+
"""
|
|
138
|
+
# Check CAIP-2 format first
|
|
139
|
+
if is_caip2_format(network):
|
|
140
|
+
return _FEE_PAYER_BY_CAIP2.get(network)
|
|
141
|
+
|
|
142
|
+
# Check v1 network name
|
|
143
|
+
network_lower = network.lower()
|
|
144
|
+
return _FEE_PAYER_BY_NETWORK.get(network_lower)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def get_facilitator_address(network: str) -> Optional[str]:
|
|
148
|
+
"""
|
|
149
|
+
Alias for get_fee_payer() for backward compatibility.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
network: Network identifier
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Facilitator/fee payer address if applicable
|
|
156
|
+
"""
|
|
157
|
+
return get_fee_payer(network)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def requires_fee_payer(network: str) -> bool:
|
|
161
|
+
"""
|
|
162
|
+
Check if a network requires a fee payer address.
|
|
163
|
+
|
|
164
|
+
Args:
|
|
165
|
+
network: Network identifier (v1 name or CAIP-2 format)
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
True if network requires fee payer (non-EVM), False otherwise
|
|
169
|
+
|
|
170
|
+
Example:
|
|
171
|
+
>>> requires_fee_payer("algorand")
|
|
172
|
+
True
|
|
173
|
+
>>> requires_fee_payer("base")
|
|
174
|
+
False
|
|
175
|
+
"""
|
|
176
|
+
# Try to get network config
|
|
177
|
+
try:
|
|
178
|
+
normalized = normalize_network(network)
|
|
179
|
+
net_config = get_network(normalized)
|
|
180
|
+
if net_config:
|
|
181
|
+
return net_config.network_type != NetworkType.EVM
|
|
182
|
+
except ValueError:
|
|
183
|
+
pass
|
|
184
|
+
|
|
185
|
+
# Fall back to checking if we have a fee payer registered
|
|
186
|
+
return get_fee_payer(network) is not None
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def get_network_type_from_fee_payer(address: str) -> Optional[NetworkType]:
|
|
190
|
+
"""
|
|
191
|
+
Determine network type from a fee payer address format.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
address: Fee payer address
|
|
195
|
+
|
|
196
|
+
Returns:
|
|
197
|
+
NetworkType if recognizable, None otherwise
|
|
198
|
+
|
|
199
|
+
Example:
|
|
200
|
+
>>> get_network_type_from_fee_payer("KIMS5H6QLCUDL65L5UBTOXDPWLMTS7N3AAC3I6B2NCONEI5QIVK7LH2C2I")
|
|
201
|
+
NetworkType.ALGORAND
|
|
202
|
+
>>> get_network_type_from_fee_payer("F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq")
|
|
203
|
+
NetworkType.SVM
|
|
204
|
+
"""
|
|
205
|
+
if not address:
|
|
206
|
+
return None
|
|
207
|
+
|
|
208
|
+
# Algorand: 58 characters, base32 (A-Z2-7)
|
|
209
|
+
if len(address) == 58 and address.isalnum():
|
|
210
|
+
import re
|
|
211
|
+
if re.match(r'^[A-Z2-7]+$', address):
|
|
212
|
+
return NetworkType.ALGORAND
|
|
213
|
+
|
|
214
|
+
# Solana/Fogo: 32-44 characters, base58
|
|
215
|
+
if 32 <= len(address) <= 44:
|
|
216
|
+
# Base58 uses alphanumeric chars except 0, O, I, l
|
|
217
|
+
import re
|
|
218
|
+
if re.match(r'^[A-HJ-NP-Za-km-z1-9]+$', address):
|
|
219
|
+
return NetworkType.SVM
|
|
220
|
+
|
|
221
|
+
# NEAR: ends with .near or .testnet
|
|
222
|
+
if address.endswith('.near') or address.endswith('.testnet'):
|
|
223
|
+
return NetworkType.NEAR
|
|
224
|
+
|
|
225
|
+
# Stellar: starts with G, 56 characters
|
|
226
|
+
if len(address) == 56 and address.startswith('G'):
|
|
227
|
+
return NetworkType.STELLAR
|
|
228
|
+
|
|
229
|
+
return None
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def validate_fee_payer_for_network(network: str, address: str) -> bool:
|
|
233
|
+
"""
|
|
234
|
+
Validate that a fee payer address format matches the network type.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
network: Network identifier
|
|
238
|
+
address: Fee payer address to validate
|
|
239
|
+
|
|
240
|
+
Returns:
|
|
241
|
+
True if address format is valid for network
|
|
242
|
+
|
|
243
|
+
Example:
|
|
244
|
+
>>> validate_fee_payer_for_network("algorand", "KIMS5H6...")
|
|
245
|
+
True
|
|
246
|
+
>>> validate_fee_payer_for_network("algorand", "F742C4V...") # Solana address
|
|
247
|
+
False
|
|
248
|
+
"""
|
|
249
|
+
try:
|
|
250
|
+
normalized = normalize_network(network)
|
|
251
|
+
net_config = get_network(normalized)
|
|
252
|
+
if not net_config:
|
|
253
|
+
return False
|
|
254
|
+
|
|
255
|
+
detected_type = get_network_type_from_fee_payer(address)
|
|
256
|
+
if detected_type is None:
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
# SVM includes both SOLANA and SVM types
|
|
260
|
+
if net_config.network_type in (NetworkType.SVM, NetworkType.SOLANA):
|
|
261
|
+
return detected_type == NetworkType.SVM
|
|
262
|
+
|
|
263
|
+
return net_config.network_type == detected_type
|
|
264
|
+
except ValueError:
|
|
265
|
+
return False
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def get_all_fee_payers() -> Dict[str, str]:
|
|
269
|
+
"""
|
|
270
|
+
Get all registered fee payer addresses.
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Dictionary mapping network names to fee payer addresses
|
|
274
|
+
|
|
275
|
+
Example:
|
|
276
|
+
>>> payers = get_all_fee_payers()
|
|
277
|
+
>>> for network, address in payers.items():
|
|
278
|
+
... print(f"{network}: {address}")
|
|
279
|
+
"""
|
|
280
|
+
return dict(_FEE_PAYER_BY_NETWORK)
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def get_facilitator_url() -> str:
|
|
284
|
+
"""
|
|
285
|
+
Get the default facilitator URL.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
Facilitator API URL
|
|
289
|
+
|
|
290
|
+
Example:
|
|
291
|
+
>>> url = get_facilitator_url()
|
|
292
|
+
>>> print(url)
|
|
293
|
+
'https://facilitator.ultravioletadao.xyz'
|
|
294
|
+
"""
|
|
295
|
+
return DEFAULT_FACILITATOR_URL
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# =============================================================================
|
|
299
|
+
# Payment Info Builder
|
|
300
|
+
# =============================================================================
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def build_payment_info(
|
|
304
|
+
network: str,
|
|
305
|
+
pay_to: str,
|
|
306
|
+
max_amount_required: str,
|
|
307
|
+
description: str = "",
|
|
308
|
+
resource: str = "",
|
|
309
|
+
asset: Optional[str] = None,
|
|
310
|
+
token_type: str = "usdc",
|
|
311
|
+
extra: Optional[Dict] = None,
|
|
312
|
+
) -> Dict:
|
|
313
|
+
"""
|
|
314
|
+
Build a payment info dict with all facilitator details pre-configured.
|
|
315
|
+
|
|
316
|
+
This function automatically includes the correct fee payer address for
|
|
317
|
+
non-EVM networks, so SDK users don't need to configure this manually.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
network: Network identifier (e.g., "algorand", "solana", "base")
|
|
321
|
+
pay_to: Recipient address
|
|
322
|
+
max_amount_required: Maximum amount in token units (string)
|
|
323
|
+
description: Optional payment description
|
|
324
|
+
resource: Optional resource being purchased
|
|
325
|
+
asset: Optional token address (defaults to USDC for network)
|
|
326
|
+
token_type: Token type (default "usdc")
|
|
327
|
+
extra: Additional extra fields to include
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Payment info dictionary ready for 402 response
|
|
331
|
+
|
|
332
|
+
Example:
|
|
333
|
+
>>> info = build_payment_info(
|
|
334
|
+
... network="algorand",
|
|
335
|
+
... pay_to="MERCHANT_ADDRESS...",
|
|
336
|
+
... max_amount_required="1000000",
|
|
337
|
+
... description="API access"
|
|
338
|
+
... )
|
|
339
|
+
>>> # info includes facilitator fee payer automatically
|
|
340
|
+
"""
|
|
341
|
+
# Get network config for defaults
|
|
342
|
+
try:
|
|
343
|
+
normalized = normalize_network(network)
|
|
344
|
+
net_config = get_network(normalized)
|
|
345
|
+
except ValueError:
|
|
346
|
+
net_config = None
|
|
347
|
+
|
|
348
|
+
# Build base payment info
|
|
349
|
+
payment_info: Dict = {
|
|
350
|
+
"network": network,
|
|
351
|
+
"payTo": pay_to,
|
|
352
|
+
"maxAmountRequired": max_amount_required,
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
if description:
|
|
356
|
+
payment_info["description"] = description
|
|
357
|
+
if resource:
|
|
358
|
+
payment_info["resource"] = resource
|
|
359
|
+
|
|
360
|
+
# Set asset (token address)
|
|
361
|
+
if asset:
|
|
362
|
+
payment_info["asset"] = asset
|
|
363
|
+
elif net_config:
|
|
364
|
+
payment_info["asset"] = net_config.usdc_address
|
|
365
|
+
|
|
366
|
+
# Build extra field
|
|
367
|
+
payment_extra: Dict = {}
|
|
368
|
+
|
|
369
|
+
# Add token type info
|
|
370
|
+
payment_extra["token"] = token_type
|
|
371
|
+
|
|
372
|
+
# Add fee payer for non-EVM networks
|
|
373
|
+
fee_payer = get_fee_payer(network)
|
|
374
|
+
if fee_payer:
|
|
375
|
+
payment_extra["feePayer"] = fee_payer
|
|
376
|
+
|
|
377
|
+
# Merge user-provided extra
|
|
378
|
+
if extra:
|
|
379
|
+
payment_extra.update(extra)
|
|
380
|
+
|
|
381
|
+
if payment_extra:
|
|
382
|
+
payment_info["extra"] = payment_extra
|
|
383
|
+
|
|
384
|
+
return payment_info
|
|
@@ -53,6 +53,20 @@ from uvd_x402_sdk.networks.base import (
|
|
|
53
53
|
register_network,
|
|
54
54
|
)
|
|
55
55
|
|
|
56
|
+
# Algorand fee payer addresses are defined in uvd_x402_sdk.facilitator
|
|
57
|
+
# Import here for convenience
|
|
58
|
+
try:
|
|
59
|
+
from uvd_x402_sdk.facilitator import (
|
|
60
|
+
ALGORAND_FEE_PAYER_MAINNET,
|
|
61
|
+
ALGORAND_FEE_PAYER_TESTNET,
|
|
62
|
+
get_fee_payer,
|
|
63
|
+
)
|
|
64
|
+
except ImportError:
|
|
65
|
+
# Fallback if facilitator module not loaded yet
|
|
66
|
+
ALGORAND_FEE_PAYER_MAINNET = "KIMS5H6QLCUDL65L5UBTOXDPWLMTS7N3AAC3I6B2NCONEI5QIVK7LH2C2I"
|
|
67
|
+
ALGORAND_FEE_PAYER_TESTNET = "5DPPDQNYUPCTXRZWRYSF3WPYU6RKAUR25F3YG4EKXQRHV5AUAI62H5GXL4"
|
|
68
|
+
get_fee_payer = None # type: ignore
|
|
69
|
+
|
|
56
70
|
|
|
57
71
|
# =============================================================================
|
|
58
72
|
# Algorand Networks Configuration
|
|
@@ -548,3 +562,36 @@ def build_x402_payment_request(
|
|
|
548
562
|
"network": network,
|
|
549
563
|
"payload": payload.to_dict(),
|
|
550
564
|
}
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
def get_algorand_fee_payer(network_name: str = "algorand") -> str:
|
|
568
|
+
"""
|
|
569
|
+
Get the fee payer address for an Algorand network.
|
|
570
|
+
|
|
571
|
+
The fee payer is the facilitator address that pays transaction fees
|
|
572
|
+
for the atomic group. This address is used to construct Transaction 0
|
|
573
|
+
(the fee payment transaction).
|
|
574
|
+
|
|
575
|
+
Args:
|
|
576
|
+
network_name: Network name ('algorand' or 'algorand-testnet')
|
|
577
|
+
|
|
578
|
+
Returns:
|
|
579
|
+
Fee payer address for the specified network
|
|
580
|
+
|
|
581
|
+
Example:
|
|
582
|
+
>>> get_algorand_fee_payer("algorand")
|
|
583
|
+
'KIMS5H6QLCUDL65L5UBTOXDPWLMTS7N3AAC3I6B2NCONEI5QIVK7LH2C2I'
|
|
584
|
+
>>> get_algorand_fee_payer("algorand-testnet")
|
|
585
|
+
'5DPPDQNYUPCTXRZWRYSF3WPYU6RKAUR25F3YG4EKXQRHV5AUAI62H5GXL4'
|
|
586
|
+
"""
|
|
587
|
+
# Use facilitator module if available
|
|
588
|
+
if get_fee_payer is not None:
|
|
589
|
+
fee_payer = get_fee_payer(network_name)
|
|
590
|
+
if fee_payer:
|
|
591
|
+
return fee_payer
|
|
592
|
+
|
|
593
|
+
# Fallback to direct lookup
|
|
594
|
+
network_lower = network_name.lower()
|
|
595
|
+
if "testnet" in network_lower:
|
|
596
|
+
return ALGORAND_FEE_PAYER_TESTNET
|
|
597
|
+
return ALGORAND_FEE_PAYER_MAINNET
|
uvd_x402_sdk/networks/near.py
CHANGED
|
@@ -32,6 +32,20 @@ from uvd_x402_sdk.networks.base import (
|
|
|
32
32
|
register_network,
|
|
33
33
|
)
|
|
34
34
|
|
|
35
|
+
# NEAR fee payer addresses are defined in uvd_x402_sdk.facilitator
|
|
36
|
+
# Import here for convenience
|
|
37
|
+
try:
|
|
38
|
+
from uvd_x402_sdk.facilitator import (
|
|
39
|
+
NEAR_FEE_PAYER_MAINNET,
|
|
40
|
+
NEAR_FEE_PAYER_TESTNET,
|
|
41
|
+
get_fee_payer,
|
|
42
|
+
)
|
|
43
|
+
except ImportError:
|
|
44
|
+
# Fallback if facilitator module not loaded yet
|
|
45
|
+
NEAR_FEE_PAYER_MAINNET = "uvd-facilitator.near"
|
|
46
|
+
NEAR_FEE_PAYER_TESTNET = "uvd-facilitator.testnet"
|
|
47
|
+
get_fee_payer = None # type: ignore
|
|
48
|
+
|
|
35
49
|
# NEP-366 hash prefix: (2^30 + 366) = 1073742190
|
|
36
50
|
NEP366_PREFIX = ((2**30) + 366).to_bytes(4, 'little')
|
|
37
51
|
|
|
@@ -395,3 +409,35 @@ def is_valid_near_account_id(account_id: str) -> bool:
|
|
|
395
409
|
|
|
396
410
|
allowed = set('abcdefghijklmnopqrstuvwxyz0123456789_-.')
|
|
397
411
|
return all(c in allowed for c in account_id)
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def get_near_fee_payer(network_name: str = "near") -> str:
|
|
415
|
+
"""
|
|
416
|
+
Get the fee payer account ID for a NEAR network.
|
|
417
|
+
|
|
418
|
+
The fee payer is the facilitator account that pays gas fees.
|
|
419
|
+
This account wraps the SignedDelegateAction and submits it.
|
|
420
|
+
|
|
421
|
+
Args:
|
|
422
|
+
network_name: Network name ('near' or 'near-testnet')
|
|
423
|
+
|
|
424
|
+
Returns:
|
|
425
|
+
Fee payer account ID for the specified network
|
|
426
|
+
|
|
427
|
+
Example:
|
|
428
|
+
>>> get_near_fee_payer("near")
|
|
429
|
+
'uvd-facilitator.near'
|
|
430
|
+
>>> get_near_fee_payer("near-testnet")
|
|
431
|
+
'uvd-facilitator.testnet'
|
|
432
|
+
"""
|
|
433
|
+
# Use facilitator module if available
|
|
434
|
+
if get_fee_payer is not None:
|
|
435
|
+
fee_payer = get_fee_payer(network_name)
|
|
436
|
+
if fee_payer:
|
|
437
|
+
return fee_payer
|
|
438
|
+
|
|
439
|
+
# Fallback to direct lookup
|
|
440
|
+
network_lower = network_name.lower()
|
|
441
|
+
if "testnet" in network_lower:
|
|
442
|
+
return NEAR_FEE_PAYER_TESTNET
|
|
443
|
+
return NEAR_FEE_PAYER_MAINNET
|
uvd_x402_sdk/networks/solana.py
CHANGED
|
@@ -38,6 +38,24 @@ from uvd_x402_sdk.networks.base import (
|
|
|
38
38
|
register_network,
|
|
39
39
|
)
|
|
40
40
|
|
|
41
|
+
# SVM fee payer addresses are defined in uvd_x402_sdk.facilitator
|
|
42
|
+
# Import here for convenience
|
|
43
|
+
try:
|
|
44
|
+
from uvd_x402_sdk.facilitator import (
|
|
45
|
+
SOLANA_FEE_PAYER_MAINNET,
|
|
46
|
+
SOLANA_FEE_PAYER_DEVNET,
|
|
47
|
+
FOGO_FEE_PAYER_MAINNET,
|
|
48
|
+
FOGO_FEE_PAYER_TESTNET,
|
|
49
|
+
get_fee_payer,
|
|
50
|
+
)
|
|
51
|
+
except ImportError:
|
|
52
|
+
# Fallback if facilitator module not loaded yet
|
|
53
|
+
SOLANA_FEE_PAYER_MAINNET = "F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq"
|
|
54
|
+
SOLANA_FEE_PAYER_DEVNET = "6xNPewUdKRbEZDReQdpyfNUdgNg8QRc8Mt263T5GZSRv"
|
|
55
|
+
FOGO_FEE_PAYER_MAINNET = "F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq"
|
|
56
|
+
FOGO_FEE_PAYER_TESTNET = "6xNPewUdKRbEZDReQdpyfNUdgNg8QRc8Mt263T5GZSRv"
|
|
57
|
+
get_fee_payer = None # type: ignore
|
|
58
|
+
|
|
41
59
|
|
|
42
60
|
# =============================================================================
|
|
43
61
|
# SVM Networks Configuration
|
|
@@ -351,3 +369,43 @@ def is_token_2022(token_type: str) -> bool:
|
|
|
351
369
|
True if token uses Token2022, False for standard SPL
|
|
352
370
|
"""
|
|
353
371
|
return token_type.lower() in TOKEN_2022_TOKENS
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def get_svm_fee_payer(network_name: str = "solana") -> str:
|
|
375
|
+
"""
|
|
376
|
+
Get the fee payer address for an SVM network.
|
|
377
|
+
|
|
378
|
+
The fee payer is the facilitator address that pays transaction fees.
|
|
379
|
+
This address should be used as the fee payer in VersionedTransaction.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
network_name: Network name ('solana', 'solana-devnet', 'fogo', 'fogo-testnet')
|
|
383
|
+
|
|
384
|
+
Returns:
|
|
385
|
+
Fee payer address for the specified network
|
|
386
|
+
|
|
387
|
+
Example:
|
|
388
|
+
>>> get_svm_fee_payer("solana")
|
|
389
|
+
'F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq'
|
|
390
|
+
>>> get_svm_fee_payer("solana-devnet")
|
|
391
|
+
'6xNPewUdKRbEZDReQdpyfNUdgNg8QRc8Mt263T5GZSRv'
|
|
392
|
+
"""
|
|
393
|
+
# Use facilitator module if available
|
|
394
|
+
if get_fee_payer is not None:
|
|
395
|
+
fee_payer = get_fee_payer(network_name)
|
|
396
|
+
if fee_payer:
|
|
397
|
+
return fee_payer
|
|
398
|
+
|
|
399
|
+
# Fallback to direct lookup
|
|
400
|
+
network_lower = network_name.lower()
|
|
401
|
+
if "fogo" in network_lower:
|
|
402
|
+
if "testnet" in network_lower:
|
|
403
|
+
return FOGO_FEE_PAYER_TESTNET
|
|
404
|
+
return FOGO_FEE_PAYER_MAINNET
|
|
405
|
+
elif "devnet" in network_lower:
|
|
406
|
+
return SOLANA_FEE_PAYER_DEVNET
|
|
407
|
+
return SOLANA_FEE_PAYER_MAINNET
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
# Alias for backward compatibility
|
|
411
|
+
get_solana_fee_payer = get_svm_fee_payer
|
uvd_x402_sdk/networks/stellar.py
CHANGED
|
@@ -19,6 +19,20 @@ from uvd_x402_sdk.networks.base import (
|
|
|
19
19
|
register_network,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
+
# Stellar fee payer addresses are defined in uvd_x402_sdk.facilitator
|
|
23
|
+
# Import here for convenience
|
|
24
|
+
try:
|
|
25
|
+
from uvd_x402_sdk.facilitator import (
|
|
26
|
+
STELLAR_FEE_PAYER_MAINNET,
|
|
27
|
+
STELLAR_FEE_PAYER_TESTNET,
|
|
28
|
+
get_fee_payer,
|
|
29
|
+
)
|
|
30
|
+
except ImportError:
|
|
31
|
+
# Fallback if facilitator module not loaded yet
|
|
32
|
+
STELLAR_FEE_PAYER_MAINNET = "GCHPGXJT2WFFRFCA5TV4G4E3PMMXLNIDUH27PKDYA4QJ2XGYZWGFZNHB"
|
|
33
|
+
STELLAR_FEE_PAYER_TESTNET = "GBBFZMLUJEZVI32EN4XA2KPP445XIBTMTRBLYWFIL556RDTHS2OWFQ2Z"
|
|
34
|
+
get_fee_payer = None # type: ignore
|
|
35
|
+
|
|
22
36
|
# Stellar Mainnet
|
|
23
37
|
STELLAR = NetworkConfig(
|
|
24
38
|
name="stellar",
|
|
@@ -127,3 +141,35 @@ def calculate_expiration_ledger(current_ledger: int, validity_ledgers: int = 60)
|
|
|
127
141
|
Expiration ledger number
|
|
128
142
|
"""
|
|
129
143
|
return current_ledger + validity_ledgers
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def get_stellar_fee_payer(network_name: str = "stellar") -> str:
|
|
147
|
+
"""
|
|
148
|
+
Get the fee payer address for a Stellar network.
|
|
149
|
+
|
|
150
|
+
The fee payer is the facilitator address that pays XLM transaction fees.
|
|
151
|
+
This address wraps the SorobanAuthorizationEntry in a fee-bump transaction.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
network_name: Network name ('stellar' or 'stellar-testnet')
|
|
155
|
+
|
|
156
|
+
Returns:
|
|
157
|
+
Fee payer public key (G... address) for the specified network
|
|
158
|
+
|
|
159
|
+
Example:
|
|
160
|
+
>>> get_stellar_fee_payer("stellar")
|
|
161
|
+
'GCHPGXJT2WFFRFCA5TV4G4E3PMMXLNIDUH27PKDYA4QJ2XGYZWGFZNHB'
|
|
162
|
+
>>> get_stellar_fee_payer("stellar-testnet")
|
|
163
|
+
'GBBFZMLUJEZVI32EN4XA2KPP445XIBTMTRBLYWFIL556RDTHS2OWFQ2Z'
|
|
164
|
+
"""
|
|
165
|
+
# Use facilitator module if available
|
|
166
|
+
if get_fee_payer is not None:
|
|
167
|
+
fee_payer = get_fee_payer(network_name)
|
|
168
|
+
if fee_payer:
|
|
169
|
+
return fee_payer
|
|
170
|
+
|
|
171
|
+
# Fallback to direct lookup
|
|
172
|
+
network_lower = network_name.lower()
|
|
173
|
+
if "testnet" in network_lower:
|
|
174
|
+
return STELLAR_FEE_PAYER_TESTNET
|
|
175
|
+
return STELLAR_FEE_PAYER_MAINNET
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: uvd-x402-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Python SDK for x402 payments - gasless crypto payments across 16 blockchains with multi-stablecoin support (USDC, EURC, AUSD, PYUSD, USDT)
|
|
5
5
|
Author-email: Ultravioleta DAO <dev@ultravioletadao.xyz>
|
|
6
6
|
Project-URL: Homepage, https://github.com/UltravioletaDAO/uvd-x402-sdk-python
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
uvd_x402_sdk/__init__.py,sha256=
|
|
1
|
+
uvd_x402_sdk/__init__.py,sha256=S_oA8b99W7CP6_RPtOlc7ZotYLvdTZJM9Sqc2s3O6zo,6261
|
|
2
2
|
uvd_x402_sdk/client.py,sha256=QbK22DtC3HmvvCezphQ-UsYX468vKrIN-M_wF4pv9cM,18389
|
|
3
3
|
uvd_x402_sdk/config.py,sha256=BNGnX2RwZ_ELIcSKU7RkwTUcln4LMFZdCwG1ptASKN8,8644
|
|
4
4
|
uvd_x402_sdk/decorators.py,sha256=XJ7V4554hsa-AVDrizF1oKmeTysg5zlkQRcaeGBI73E,9767
|
|
5
5
|
uvd_x402_sdk/exceptions.py,sha256=kzYNHFn41dSOZ5HaBDrbf4tdwJYQs1YcU9YybTrGZxo,6527
|
|
6
|
+
uvd_x402_sdk/facilitator.py,sha256=kX--Uk6BYlDCalKD-2LpPOgEi-t26C-sjcxgalphJp8,11859
|
|
6
7
|
uvd_x402_sdk/models.py,sha256=nfA8Ak0pyIveiNpstjqxnQ6-aPQ8TRJzJq2LJwlJc-E,14282
|
|
7
8
|
uvd_x402_sdk/response.py,sha256=4wxH4kWg1F8pokKEbN1kDvDF55cQxmQUN88HTxXht8g,13608
|
|
8
9
|
uvd_x402_sdk/integrations/__init__.py,sha256=Hq1Y0YIMYWBAtmbOLXDC40KQuCrbSpQVjAqEsbjH56s,1912
|
|
@@ -11,14 +12,14 @@ uvd_x402_sdk/integrations/fastapi_integration.py,sha256=j5h1IJwFLBBoWov7ANLCFaxe
|
|
|
11
12
|
uvd_x402_sdk/integrations/flask_integration.py,sha256=0iQKO5-WRxE76Pv-1jEl4lYhjCLmq_R-jxR5g9xIcKw,8825
|
|
12
13
|
uvd_x402_sdk/integrations/lambda_integration.py,sha256=nRf4o3nS6Syx-d5P0kEhz66y7jb_S4w-mwaIazgiA9c,10184
|
|
13
14
|
uvd_x402_sdk/networks/__init__.py,sha256=LKl_TljVoCDb27YB4X_VbQN8XKbdwWFAsCwgiqQtlgo,2092
|
|
14
|
-
uvd_x402_sdk/networks/algorand.py,sha256=
|
|
15
|
+
uvd_x402_sdk/networks/algorand.py,sha256=_eQfR8xguvTloJBR7_yGbWjC2KgmPux44pXeDVM7b0w,19458
|
|
15
16
|
uvd_x402_sdk/networks/base.py,sha256=gOPWfqasGbgtg9w2uG5pWnfjdOEain92L2egnDSBguc,14863
|
|
16
17
|
uvd_x402_sdk/networks/evm.py,sha256=4IbeaMH2I1c9DYCijghys0qYNeL2Nl92IMKLwq-b0Zg,10065
|
|
17
|
-
uvd_x402_sdk/networks/near.py,sha256=
|
|
18
|
-
uvd_x402_sdk/networks/solana.py,sha256
|
|
19
|
-
uvd_x402_sdk/networks/stellar.py,sha256=
|
|
20
|
-
uvd_x402_sdk-0.
|
|
21
|
-
uvd_x402_sdk-0.
|
|
22
|
-
uvd_x402_sdk-0.
|
|
23
|
-
uvd_x402_sdk-0.
|
|
24
|
-
uvd_x402_sdk-0.
|
|
18
|
+
uvd_x402_sdk/networks/near.py,sha256=HMMWJr-Jckj3YQTbSXkavlJWZB7ZV8OQX0bq8DuVxIg,12990
|
|
19
|
+
uvd_x402_sdk/networks/solana.py,sha256=GVUeh0O2W6f8Vbgoom2UQSGI8joZV68Pnpzhh87Bnqg,13640
|
|
20
|
+
uvd_x402_sdk/networks/stellar.py,sha256=ZuF-crx41N9MxSsgf2kAy88fsM-xvDyXY_D2ND6M71Y,5142
|
|
21
|
+
uvd_x402_sdk-0.5.0.dist-info/LICENSE,sha256=OcLzB_iSgMbvk7b0dlyvleY_IbL2WUaPxvn1CHw2uAc,1073
|
|
22
|
+
uvd_x402_sdk-0.5.0.dist-info/METADATA,sha256=2BkT_uVVDdWwuAnmjgS61pNXJWKP1e2wjWGf9R8VAEw,29711
|
|
23
|
+
uvd_x402_sdk-0.5.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
24
|
+
uvd_x402_sdk-0.5.0.dist-info/top_level.txt,sha256=Exyjj_Kl7CDAGFMi72lT9oFPOYiRNZb3l8tr906mMmc,13
|
|
25
|
+
uvd_x402_sdk-0.5.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|