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.
- uvd_x402_sdk/__init__.py +169 -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 +54 -54
- uvd_x402_sdk/networks/base.py +347 -347
- uvd_x402_sdk/networks/evm.py +215 -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.2.3.dist-info}/LICENSE +21 -21
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.2.3.dist-info}/METADATA +814 -778
- uvd_x402_sdk-0.2.3.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.2.3.dist-info}/WHEEL +0 -0
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.2.3.dist-info}/top_level.txt +0 -0
|
@@ -1,778 +1,814 @@
|
|
|
1
|
-
Metadata-Version: 2.1
|
|
2
|
-
Name: uvd-x402-sdk
|
|
3
|
-
Version: 0.2.
|
|
4
|
-
Summary: Python SDK for integrating x402 payments via the Ultravioleta DAO facilitator
|
|
5
|
-
Author-email: Ultravioleta DAO <dev@ultravioletadao.xyz>
|
|
6
|
-
Project-URL: Homepage, https://github.com/UltravioletaDAO/uvd-x402-sdk-python
|
|
7
|
-
Project-URL: Documentation, https://docs.ultravioletadao.xyz/x402-sdk
|
|
8
|
-
Project-URL: Repository, https://github.com/UltravioletaDAO/uvd-x402-sdk-python
|
|
9
|
-
Project-URL: Issues, https://github.com/UltravioletaDAO/uvd-x402-sdk-python/issues
|
|
10
|
-
Keywords: x402,payments,crypto,usdc,web3,evm,solana,near,stellar,facilitator
|
|
11
|
-
Classifier: Development Status :: 4 - Beta
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
-
Classifier: Topic :: Office/Business :: Financial :: Point-Of-Sale
|
|
22
|
-
Requires-Python: >=3.9
|
|
23
|
-
Description-Content-Type: text/markdown
|
|
24
|
-
License-File: LICENSE
|
|
25
|
-
Requires-Dist: httpx>=0.24.0
|
|
26
|
-
Requires-Dist: pydantic>=2.0.0
|
|
27
|
-
Provides-Extra: all
|
|
28
|
-
Requires-Dist: uvd-x402-sdk[aws,django,fastapi,flask,web3]; extra == "all"
|
|
29
|
-
Provides-Extra: aws
|
|
30
|
-
Requires-Dist: boto3>=1.26.0; extra == "aws"
|
|
31
|
-
Provides-Extra: dev
|
|
32
|
-
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
|
-
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
34
|
-
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
35
|
-
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
36
|
-
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
37
|
-
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
38
|
-
Requires-Dist: httpx>=0.24.0; extra == "dev"
|
|
39
|
-
Provides-Extra: django
|
|
40
|
-
Requires-Dist: django>=4.0.0; extra == "django"
|
|
41
|
-
Provides-Extra: fastapi
|
|
42
|
-
Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
|
|
43
|
-
Requires-Dist: starlette>=0.27.0; extra == "fastapi"
|
|
44
|
-
Provides-Extra: flask
|
|
45
|
-
Requires-Dist: flask>=2.0.0; extra == "flask"
|
|
46
|
-
Provides-Extra: web3
|
|
47
|
-
Requires-Dist: web3>=6.0.0; extra == "web3"
|
|
48
|
-
Requires-Dist: eth-account>=0.10.0; extra == "web3"
|
|
49
|
-
|
|
50
|
-
# uvd-x402-sdk
|
|
51
|
-
|
|
52
|
-
Python SDK for integrating **x402 cryptocurrency payments** via the Ultravioleta DAO facilitator.
|
|
53
|
-
|
|
54
|
-
Accept USDC payments across **14 blockchain networks** with a single integration. The SDK handles signature verification, on-chain settlement, and all the complexity of multi-chain payments.
|
|
55
|
-
|
|
56
|
-
## Features
|
|
57
|
-
|
|
58
|
-
- **14 Networks**: EVM chains (Base, Ethereum, Polygon, etc.), SVM chains (Solana, Fogo), NEAR, and Stellar
|
|
59
|
-
- **x402 v1 & v2**: Full support for both protocol versions with auto-detection
|
|
60
|
-
- **Framework Integrations**: Flask, FastAPI, Django, AWS Lambda
|
|
61
|
-
- **Gasless Payments**: Users sign authorizations, facilitator pays all network fees
|
|
62
|
-
- **Simple API**: Decorators and middleware for quick integration
|
|
63
|
-
- **Type Safety**: Full Pydantic models and type hints
|
|
64
|
-
- **Extensible**: Register custom networks easily
|
|
65
|
-
|
|
66
|
-
## Quick Start (5 Lines)
|
|
67
|
-
|
|
68
|
-
```python
|
|
69
|
-
from decimal import Decimal
|
|
70
|
-
from uvd_x402_sdk import X402Client
|
|
71
|
-
|
|
72
|
-
client = X402Client(recipient_address="0xYourWallet...")
|
|
73
|
-
result = client.process_payment(request.headers["X-PAYMENT"], Decimal("10.00"))
|
|
74
|
-
print(f"Paid by {result.payer_address}, tx: {result.transaction_hash}")
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Supported Networks
|
|
78
|
-
|
|
79
|
-
| Network | Type | Chain ID | CAIP-2 | Status |
|
|
80
|
-
|---------|------|----------|--------|--------|
|
|
81
|
-
| Base | EVM | 8453 | `eip155:8453` | Active |
|
|
82
|
-
| Ethereum | EVM | 1 | `eip155:1` | Active |
|
|
83
|
-
| Polygon | EVM | 137 | `eip155:137` | Active |
|
|
84
|
-
| Arbitrum | EVM | 42161 | `eip155:42161` | Active |
|
|
85
|
-
| Optimism | EVM | 10 | `eip155:10` | Active |
|
|
86
|
-
| Avalanche | EVM | 43114 | `eip155:43114` | Active |
|
|
87
|
-
| Celo | EVM | 42220 | `eip155:42220` | Active |
|
|
88
|
-
| HyperEVM | EVM | 999 | `eip155:999` | Active |
|
|
89
|
-
| Unichain | EVM | 130 | `eip155:130` | Active |
|
|
90
|
-
| Monad | EVM | 143 | `eip155:143` | Active |
|
|
91
|
-
| Solana | SVM | - | `solana:5eykt...` | Active |
|
|
92
|
-
| Fogo | SVM | - | `solana:fogo` | Active |
|
|
93
|
-
| NEAR | NEAR | - | `near:mainnet` | Active |
|
|
94
|
-
| Stellar | Stellar | - | `stellar:pubnet` | Active |
|
|
95
|
-
|
|
96
|
-
## Installation
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
# Core SDK (minimal dependencies)
|
|
100
|
-
pip install uvd-x402-sdk
|
|
101
|
-
|
|
102
|
-
# With framework support
|
|
103
|
-
pip install uvd-x402-sdk[flask] # Flask integration
|
|
104
|
-
pip install uvd-x402-sdk[fastapi] # FastAPI/Starlette integration
|
|
105
|
-
pip install uvd-x402-sdk[django] # Django integration
|
|
106
|
-
pip install uvd-x402-sdk[aws] # AWS Lambda helpers
|
|
107
|
-
|
|
108
|
-
# All integrations
|
|
109
|
-
pip install uvd-x402-sdk[all]
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
---
|
|
113
|
-
|
|
114
|
-
## Framework Examples
|
|
115
|
-
|
|
116
|
-
### Flask
|
|
117
|
-
|
|
118
|
-
```python
|
|
119
|
-
from decimal import Decimal
|
|
120
|
-
from flask import Flask, g, jsonify
|
|
121
|
-
from uvd_x402_sdk.integrations import FlaskX402
|
|
122
|
-
|
|
123
|
-
app = Flask(__name__)
|
|
124
|
-
x402 = FlaskX402(
|
|
125
|
-
app,
|
|
126
|
-
recipient_address="0xYourEVMWallet...",
|
|
127
|
-
recipient_solana="YourSolanaAddress...",
|
|
128
|
-
recipient_near="your-account.near",
|
|
129
|
-
recipient_stellar="G...YourStellarAddress",
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
@app.route("/api/premium")
|
|
133
|
-
@x402.require_payment(amount_usd=Decimal("5.00"))
|
|
134
|
-
def premium():
|
|
135
|
-
return jsonify({
|
|
136
|
-
"message": "Premium content!",
|
|
137
|
-
"payer": g.payment_result.payer_address,
|
|
138
|
-
"tx": g.payment_result.transaction_hash,
|
|
139
|
-
"network": g.payment_result.network,
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
@app.route("/api/basic")
|
|
143
|
-
@x402.require_payment(amount_usd=Decimal("0.10"))
|
|
144
|
-
def basic():
|
|
145
|
-
return jsonify({"data": "Basic tier data"})
|
|
146
|
-
|
|
147
|
-
if __name__ == "__main__":
|
|
148
|
-
app.run(debug=True)
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### FastAPI
|
|
152
|
-
|
|
153
|
-
```python
|
|
154
|
-
from decimal import Decimal
|
|
155
|
-
from fastapi import FastAPI, Depends
|
|
156
|
-
from uvd_x402_sdk.config import X402Config
|
|
157
|
-
from uvd_x402_sdk.models import PaymentResult
|
|
158
|
-
from uvd_x402_sdk.integrations import FastAPIX402
|
|
159
|
-
|
|
160
|
-
app = FastAPI()
|
|
161
|
-
x402 = FastAPIX402(
|
|
162
|
-
app,
|
|
163
|
-
recipient_address="0xYourEVMWallet...",
|
|
164
|
-
recipient_solana="YourSolanaAddress...",
|
|
165
|
-
recipient_near="your-account.near",
|
|
166
|
-
recipient_stellar="G...YourStellarAddress",
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
@app.get("/api/premium")
|
|
170
|
-
async def premium(
|
|
171
|
-
payment: PaymentResult = Depends(x402.require_payment(amount_usd="5.00"))
|
|
172
|
-
):
|
|
173
|
-
return {
|
|
174
|
-
"message": "Premium content!",
|
|
175
|
-
"payer": payment.payer_address,
|
|
176
|
-
"network": payment.network,
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
@app.post("/api/generate")
|
|
180
|
-
async def generate(
|
|
181
|
-
body: dict,
|
|
182
|
-
payment: PaymentResult = Depends(x402.require_payment(amount_usd="1.00"))
|
|
183
|
-
):
|
|
184
|
-
# Dynamic processing based on request
|
|
185
|
-
return {"result": "generated", "payer": payment.payer_address}
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Django
|
|
189
|
-
|
|
190
|
-
```python
|
|
191
|
-
# settings.py
|
|
192
|
-
X402_FACILITATOR_URL = "https://facilitator.ultravioletadao.xyz"
|
|
193
|
-
X402_RECIPIENT_EVM = "0xYourEVMWallet..."
|
|
194
|
-
X402_RECIPIENT_SOLANA = "YourSolanaAddress..."
|
|
195
|
-
X402_RECIPIENT_NEAR = "your-account.near"
|
|
196
|
-
X402_RECIPIENT_STELLAR = "G...YourStellarAddress"
|
|
197
|
-
X402_PROTECTED_PATHS = {
|
|
198
|
-
"/api/premium/": "5.00",
|
|
199
|
-
"/api/basic/": "1.00",
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
MIDDLEWARE = [
|
|
203
|
-
# ...other middleware...
|
|
204
|
-
"uvd_x402_sdk.integrations.django_integration.DjangoX402Middleware",
|
|
205
|
-
]
|
|
206
|
-
|
|
207
|
-
# views.py
|
|
208
|
-
from django.http import JsonResponse
|
|
209
|
-
from uvd_x402_sdk.integrations import django_x402_required
|
|
210
|
-
|
|
211
|
-
@django_x402_required(amount_usd="5.00")
|
|
212
|
-
def premium_view(request):
|
|
213
|
-
payment = request.payment_result
|
|
214
|
-
return JsonResponse({
|
|
215
|
-
"message": "Premium content!",
|
|
216
|
-
"payer": payment.payer_address,
|
|
217
|
-
})
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
### AWS Lambda
|
|
221
|
-
|
|
222
|
-
```python
|
|
223
|
-
import json
|
|
224
|
-
from decimal import Decimal
|
|
225
|
-
from uvd_x402_sdk.config import X402Config
|
|
226
|
-
from uvd_x402_sdk.integrations import LambdaX402
|
|
227
|
-
|
|
228
|
-
config = X402Config(
|
|
229
|
-
recipient_evm="0xYourEVMWallet...",
|
|
230
|
-
recipient_solana="YourSolanaAddress...",
|
|
231
|
-
recipient_near="your-account.near",
|
|
232
|
-
recipient_stellar="G...YourStellarAddress",
|
|
233
|
-
)
|
|
234
|
-
x402 = LambdaX402(config=config)
|
|
235
|
-
|
|
236
|
-
def handler(event, context):
|
|
237
|
-
# Calculate price based on request
|
|
238
|
-
body = json.loads(event.get("body", "{}"))
|
|
239
|
-
quantity = body.get("quantity", 1)
|
|
240
|
-
price = Decimal(str(quantity * 0.01))
|
|
241
|
-
|
|
242
|
-
# Process payment or return 402
|
|
243
|
-
result = x402.process_or_require(event, price)
|
|
244
|
-
|
|
245
|
-
# If 402 response, return it
|
|
246
|
-
if isinstance(result, dict) and "statusCode" in result:
|
|
247
|
-
return result
|
|
248
|
-
|
|
249
|
-
# Payment verified!
|
|
250
|
-
return {
|
|
251
|
-
"statusCode": 200,
|
|
252
|
-
"headers": {"Content-Type": "application/json"},
|
|
253
|
-
"body": json.dumps({
|
|
254
|
-
"success": True,
|
|
255
|
-
"payer": result.payer_address,
|
|
256
|
-
"tx": result.transaction_hash,
|
|
257
|
-
"network": result.network,
|
|
258
|
-
"quantity": quantity,
|
|
259
|
-
})
|
|
260
|
-
}
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
---
|
|
264
|
-
|
|
265
|
-
## Network-Specific Examples
|
|
266
|
-
|
|
267
|
-
### EVM Chains (Base, Ethereum, Polygon, etc.)
|
|
268
|
-
|
|
269
|
-
EVM chains use ERC-3009 `TransferWithAuthorization` with EIP-712 signatures.
|
|
270
|
-
|
|
271
|
-
```python
|
|
272
|
-
from uvd_x402_sdk import X402Client, X402Config
|
|
273
|
-
|
|
274
|
-
# Accept payments on Base and Ethereum only
|
|
275
|
-
config = X402Config(
|
|
276
|
-
recipient_evm="0xYourEVMWallet...",
|
|
277
|
-
supported_networks=["base", "ethereum"],
|
|
278
|
-
)
|
|
279
|
-
|
|
280
|
-
client = X402Client(config=config)
|
|
281
|
-
result = client.process_payment(x_payment_header, Decimal("10.00"))
|
|
282
|
-
|
|
283
|
-
# The payload contains EIP-712 signature + authorization
|
|
284
|
-
payload = client.extract_payload(x_payment_header)
|
|
285
|
-
evm_data = payload.get_evm_payload()
|
|
286
|
-
print(f"From: {evm_data.authorization.from_address}")
|
|
287
|
-
print(f"To: {evm_data.authorization.to}")
|
|
288
|
-
print(f"Value: {evm_data.authorization.value}")
|
|
289
|
-
```
|
|
290
|
-
|
|
291
|
-
### Solana & Fogo (SVM Chains)
|
|
292
|
-
|
|
293
|
-
SVM chains use partially-signed VersionedTransactions with SPL token transfers.
|
|
294
|
-
|
|
295
|
-
```python
|
|
296
|
-
from uvd_x402_sdk import X402Client, X402Config
|
|
297
|
-
|
|
298
|
-
# Accept payments on Solana and Fogo
|
|
299
|
-
config = X402Config(
|
|
300
|
-
recipient_solana="YourSolanaAddress...",
|
|
301
|
-
supported_networks=["solana", "fogo"],
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
client = X402Client(config=config)
|
|
305
|
-
result = client.process_payment(x_payment_header, Decimal("5.00"))
|
|
306
|
-
|
|
307
|
-
# The payload contains a base64-encoded VersionedTransaction
|
|
308
|
-
payload = client.extract_payload(x_payment_header)
|
|
309
|
-
svm_data = payload.get_svm_payload()
|
|
310
|
-
print(f"Transaction: {svm_data.transaction[:50]}...")
|
|
311
|
-
|
|
312
|
-
# Fogo has ultra-fast finality (~400ms)
|
|
313
|
-
if result.network == "fogo":
|
|
314
|
-
print("Payment confirmed in ~400ms!")
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
### Stellar
|
|
318
|
-
|
|
319
|
-
Stellar uses Soroban Authorization Entries with fee-bump transactions.
|
|
320
|
-
|
|
321
|
-
```python
|
|
322
|
-
from uvd_x402_sdk import X402Client, X402Config
|
|
323
|
-
|
|
324
|
-
config = X402Config(
|
|
325
|
-
recipient_stellar="G...YourStellarAddress",
|
|
326
|
-
supported_networks=["stellar"],
|
|
327
|
-
)
|
|
328
|
-
|
|
329
|
-
client = X402Client(config=config)
|
|
330
|
-
result = client.process_payment(x_payment_header, Decimal("1.00"))
|
|
331
|
-
|
|
332
|
-
# Stellar uses 7 decimals (stroops)
|
|
333
|
-
payload = client.extract_payload(x_payment_header)
|
|
334
|
-
stellar_data = payload.get_stellar_payload()
|
|
335
|
-
print(f"From: {stellar_data.from_address}")
|
|
336
|
-
print(f"Amount (stroops): {stellar_data.amount}")
|
|
337
|
-
print(f"Token Contract: {stellar_data.tokenContract}")
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
### NEAR Protocol
|
|
341
|
-
|
|
342
|
-
NEAR uses NEP-366 meta-transactions with Borsh serialization.
|
|
343
|
-
|
|
344
|
-
```python
|
|
345
|
-
from uvd_x402_sdk import X402Client, X402Config
|
|
346
|
-
|
|
347
|
-
config = X402Config(
|
|
348
|
-
recipient_near="your-recipient.near",
|
|
349
|
-
supported_networks=["near"],
|
|
350
|
-
)
|
|
351
|
-
|
|
352
|
-
client = X402Client(config=config)
|
|
353
|
-
result = client.process_payment(x_payment_header, Decimal("2.00"))
|
|
354
|
-
|
|
355
|
-
# NEAR payload contains a SignedDelegateAction
|
|
356
|
-
payload = client.extract_payload(x_payment_header)
|
|
357
|
-
near_data = payload.get_near_payload()
|
|
358
|
-
print(f"SignedDelegateAction: {near_data.signedDelegateAction[:50]}...")
|
|
359
|
-
|
|
360
|
-
# Validate NEAR payload structure
|
|
361
|
-
from uvd_x402_sdk.networks.near import validate_near_payload
|
|
362
|
-
validate_near_payload(payload.payload) # Raises ValueError if invalid
|
|
363
|
-
```
|
|
364
|
-
|
|
365
|
-
---
|
|
366
|
-
|
|
367
|
-
## x402 v1 vs v2
|
|
368
|
-
|
|
369
|
-
The SDK supports both x402 protocol versions with automatic detection.
|
|
370
|
-
|
|
371
|
-
### Version Differences
|
|
372
|
-
|
|
373
|
-
| Aspect | v1 | v2 |
|
|
374
|
-
|--------|----|----|
|
|
375
|
-
| Network ID | String (`"base"`) | CAIP-2 (`"eip155:8453"`) |
|
|
376
|
-
| Payment delivery | JSON body | `PAYMENT-REQUIRED` header |
|
|
377
|
-
| Multiple options | Limited | `accepts` array |
|
|
378
|
-
| Discovery | Implicit | Optional extension |
|
|
379
|
-
|
|
380
|
-
### Auto-Detection
|
|
381
|
-
|
|
382
|
-
```python
|
|
383
|
-
from uvd_x402_sdk import PaymentPayload
|
|
384
|
-
|
|
385
|
-
# The SDK auto-detects based on network format
|
|
386
|
-
payload = PaymentPayload(
|
|
387
|
-
x402Version=1,
|
|
388
|
-
scheme="exact",
|
|
389
|
-
network="base", # v1 format
|
|
390
|
-
payload={"signature": "...", "authorization": {...}}
|
|
391
|
-
)
|
|
392
|
-
|
|
393
|
-
print(payload.is_v2()) # False
|
|
394
|
-
|
|
395
|
-
payload_v2 = PaymentPayload(
|
|
396
|
-
x402Version=2,
|
|
397
|
-
scheme="exact",
|
|
398
|
-
network="eip155:8453", # v2 CAIP-2 format
|
|
399
|
-
payload={"signature": "...", "authorization": {...}}
|
|
400
|
-
)
|
|
401
|
-
|
|
402
|
-
print(payload_v2.is_v2()) # True
|
|
403
|
-
|
|
404
|
-
# Both work the same way
|
|
405
|
-
print(payload.get_normalized_network()) # "base"
|
|
406
|
-
print(payload_v2.get_normalized_network()) # "base"
|
|
407
|
-
```
|
|
408
|
-
|
|
409
|
-
### Creating v2 Responses
|
|
410
|
-
|
|
411
|
-
```python
|
|
412
|
-
from uvd_x402_sdk import X402Config, create_402_response_v2, Payment402BuilderV2
|
|
413
|
-
|
|
414
|
-
config = X402Config(
|
|
415
|
-
recipient_evm="0xYourEVM...",
|
|
416
|
-
recipient_solana="YourSolana...",
|
|
417
|
-
recipient_near="your.near",
|
|
418
|
-
recipient_stellar="G...Stellar",
|
|
419
|
-
)
|
|
420
|
-
|
|
421
|
-
# Simple v2 response
|
|
422
|
-
response = create_402_response_v2(
|
|
423
|
-
amount_usd=Decimal("5.00"),
|
|
424
|
-
config=config,
|
|
425
|
-
resource="/api/premium",
|
|
426
|
-
description="Premium API access",
|
|
427
|
-
)
|
|
428
|
-
# Returns:
|
|
429
|
-
# {
|
|
430
|
-
# "x402Version": 2,
|
|
431
|
-
# "scheme": "exact",
|
|
432
|
-
# "resource": "/api/premium",
|
|
433
|
-
# "accepts": [
|
|
434
|
-
# {"network": "eip155:8453", "asset": "0x833...", "amount": "5000000", "payTo": "0xYour..."},
|
|
435
|
-
# {"network": "solana:5eykt...", "asset": "EPjF...", "amount": "5000000", "payTo": "Your..."},
|
|
436
|
-
# {"network": "near:mainnet", "asset": "1720...", "amount": "5000000", "payTo": "your.near"},
|
|
437
|
-
# ...
|
|
438
|
-
# ]
|
|
439
|
-
# }
|
|
440
|
-
|
|
441
|
-
# Builder pattern for more control
|
|
442
|
-
response = (
|
|
443
|
-
Payment402BuilderV2(config)
|
|
444
|
-
.amount(Decimal("10.00"))
|
|
445
|
-
.resource("/api/generate")
|
|
446
|
-
.description("AI generation credits")
|
|
447
|
-
.networks(["base", "solana", "near"]) # Limit to specific networks
|
|
448
|
-
.build()
|
|
449
|
-
)
|
|
450
|
-
```
|
|
451
|
-
|
|
452
|
-
---
|
|
453
|
-
|
|
454
|
-
## Payload Validation
|
|
455
|
-
|
|
456
|
-
Each network type has specific payload validation:
|
|
457
|
-
|
|
458
|
-
### EVM Validation
|
|
459
|
-
|
|
460
|
-
```python
|
|
461
|
-
from uvd_x402_sdk.models import EVMPayloadContent, EVMAuthorization
|
|
462
|
-
|
|
463
|
-
# Parse and validate EVM payload
|
|
464
|
-
payload = client.extract_payload(x_payment_header)
|
|
465
|
-
evm_data = payload.get_evm_payload()
|
|
466
|
-
|
|
467
|
-
# Validate authorization fields
|
|
468
|
-
auth = evm_data.authorization
|
|
469
|
-
assert auth.from_address.startswith("0x")
|
|
470
|
-
assert auth.to.startswith("0x")
|
|
471
|
-
assert int(auth.value) > 0
|
|
472
|
-
assert int(auth.validBefore) > int(auth.validAfter)
|
|
473
|
-
```
|
|
474
|
-
|
|
475
|
-
### SVM Validation
|
|
476
|
-
|
|
477
|
-
```python
|
|
478
|
-
from uvd_x402_sdk.networks.solana import validate_svm_payload, is_valid_solana_address
|
|
479
|
-
|
|
480
|
-
# Validate SVM payload
|
|
481
|
-
payload = client.extract_payload(x_payment_header)
|
|
482
|
-
validate_svm_payload(payload.payload) # Raises ValueError if invalid
|
|
483
|
-
|
|
484
|
-
# Validate Solana addresses
|
|
485
|
-
assert is_valid_solana_address("YourSolanaAddress...")
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
### NEAR Validation
|
|
489
|
-
|
|
490
|
-
```python
|
|
491
|
-
from uvd_x402_sdk.networks.near import (
|
|
492
|
-
validate_near_payload,
|
|
493
|
-
is_valid_near_account_id,
|
|
494
|
-
BorshSerializer,
|
|
495
|
-
)
|
|
496
|
-
|
|
497
|
-
# Validate NEAR payload
|
|
498
|
-
payload = client.extract_payload(x_payment_header)
|
|
499
|
-
validate_near_payload(payload.payload) # Raises ValueError if invalid
|
|
500
|
-
|
|
501
|
-
# Validate NEAR account IDs
|
|
502
|
-
assert is_valid_near_account_id("your-account.near")
|
|
503
|
-
assert is_valid_near_account_id("0xultravioleta.near")
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
### Stellar Validation
|
|
507
|
-
|
|
508
|
-
```python
|
|
509
|
-
from uvd_x402_sdk.networks.stellar import (
|
|
510
|
-
is_valid_stellar_address,
|
|
511
|
-
is_valid_contract_address,
|
|
512
|
-
stroops_to_usd,
|
|
513
|
-
)
|
|
514
|
-
|
|
515
|
-
# Validate Stellar addresses
|
|
516
|
-
assert is_valid_stellar_address("G...YourStellarAddress") # G...
|
|
517
|
-
assert is_valid_contract_address("C...USDCContract") # C...
|
|
518
|
-
|
|
519
|
-
# Convert stroops to USD (7 decimals)
|
|
520
|
-
usd = stroops_to_usd(50000000) # Returns 5.0
|
|
521
|
-
```
|
|
522
|
-
|
|
523
|
-
---
|
|
524
|
-
|
|
525
|
-
## Configuration
|
|
526
|
-
|
|
527
|
-
### Environment Variables
|
|
528
|
-
|
|
529
|
-
```bash
|
|
530
|
-
# Core configuration
|
|
531
|
-
X402_FACILITATOR_URL=https://facilitator.ultravioletadao.xyz
|
|
532
|
-
X402_VERIFY_TIMEOUT=30
|
|
533
|
-
X402_SETTLE_TIMEOUT=55
|
|
534
|
-
|
|
535
|
-
# Recipient addresses (at least one required)
|
|
536
|
-
X402_RECIPIENT_EVM=0xYourEVMWallet
|
|
537
|
-
X402_RECIPIENT_SOLANA=YourSolanaAddress
|
|
538
|
-
X402_RECIPIENT_NEAR=your-account.near
|
|
539
|
-
X402_RECIPIENT_STELLAR=G...YourStellarAddress
|
|
540
|
-
|
|
541
|
-
# Optional
|
|
542
|
-
X402_FACILITATOR_SOLANA=F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq
|
|
543
|
-
X402_RESOURCE_URL=https://api.example.com
|
|
544
|
-
X402_DESCRIPTION=API access payment
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
### Programmatic Configuration
|
|
548
|
-
|
|
549
|
-
```python
|
|
550
|
-
from uvd_x402_sdk import X402Config, MultiPaymentConfig
|
|
551
|
-
|
|
552
|
-
# Full configuration
|
|
553
|
-
config = X402Config(
|
|
554
|
-
facilitator_url="https://facilitator.ultravioletadao.xyz",
|
|
555
|
-
|
|
556
|
-
# Recipients
|
|
557
|
-
recipient_evm="0xYourEVMWallet",
|
|
558
|
-
recipient_solana="YourSolanaAddress",
|
|
559
|
-
recipient_near="your-account.near",
|
|
560
|
-
recipient_stellar="G...YourStellarAddress",
|
|
561
|
-
|
|
562
|
-
# Timeouts
|
|
563
|
-
verify_timeout=30.0,
|
|
564
|
-
settle_timeout=55.0,
|
|
565
|
-
|
|
566
|
-
# Limit to specific networks
|
|
567
|
-
supported_networks=["base", "solana", "near", "stellar"],
|
|
568
|
-
|
|
569
|
-
# Metadata
|
|
570
|
-
resource_url="https://api.example.com/premium",
|
|
571
|
-
description="Premium API access",
|
|
572
|
-
|
|
573
|
-
# Protocol version (1, 2, or "auto")
|
|
574
|
-
x402_version="auto",
|
|
575
|
-
)
|
|
576
|
-
|
|
577
|
-
# From environment
|
|
578
|
-
config = X402Config.from_env()
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
---
|
|
582
|
-
|
|
583
|
-
## Registering Custom Networks
|
|
584
|
-
|
|
585
|
-
```python
|
|
586
|
-
from uvd_x402_sdk.networks import NetworkConfig, NetworkType, register_network
|
|
587
|
-
|
|
588
|
-
# Register a custom EVM network
|
|
589
|
-
custom_chain = NetworkConfig(
|
|
590
|
-
name="mychain",
|
|
591
|
-
display_name="My Custom Chain",
|
|
592
|
-
network_type=NetworkType.EVM,
|
|
593
|
-
chain_id=12345,
|
|
594
|
-
usdc_address="0xUSDCContractAddress",
|
|
595
|
-
usdc_decimals=6,
|
|
596
|
-
usdc_domain_name="USD Coin", # Check actual EIP-712 domain!
|
|
597
|
-
usdc_domain_version="2",
|
|
598
|
-
rpc_url="https://rpc.mychain.com",
|
|
599
|
-
enabled=True,
|
|
600
|
-
)
|
|
601
|
-
|
|
602
|
-
register_network(custom_chain)
|
|
603
|
-
|
|
604
|
-
# Now you can use it
|
|
605
|
-
config = X402Config(
|
|
606
|
-
recipient_evm="0xYourWallet...",
|
|
607
|
-
supported_networks=["base", "mychain"],
|
|
608
|
-
)
|
|
609
|
-
```
|
|
610
|
-
|
|
611
|
-
---
|
|
612
|
-
|
|
613
|
-
## Error Handling
|
|
614
|
-
|
|
615
|
-
```python
|
|
616
|
-
from uvd_x402_sdk.exceptions import (
|
|
617
|
-
X402Error,
|
|
618
|
-
PaymentRequiredError,
|
|
619
|
-
PaymentVerificationError,
|
|
620
|
-
PaymentSettlementError,
|
|
621
|
-
UnsupportedNetworkError,
|
|
622
|
-
InvalidPayloadError,
|
|
623
|
-
FacilitatorError,
|
|
624
|
-
X402TimeoutError,
|
|
625
|
-
)
|
|
626
|
-
|
|
627
|
-
try:
|
|
628
|
-
result = client.process_payment(header, amount)
|
|
629
|
-
except PaymentVerificationError as e:
|
|
630
|
-
# Signature invalid, amount mismatch, expired, etc.
|
|
631
|
-
print(f"Verification failed: {e.reason}")
|
|
632
|
-
print(f"Errors: {e.errors}")
|
|
633
|
-
except PaymentSettlementError as e:
|
|
634
|
-
# On-chain settlement failed (insufficient balance, nonce used, etc.)
|
|
635
|
-
print(f"Settlement failed on {e.network}: {e.message}")
|
|
636
|
-
except UnsupportedNetworkError as e:
|
|
637
|
-
# Network not recognized or disabled
|
|
638
|
-
print(f"Network {e.network} not supported")
|
|
639
|
-
print(f"Supported: {e.supported_networks}")
|
|
640
|
-
except InvalidPayloadError as e:
|
|
641
|
-
# Malformed X-PAYMENT header
|
|
642
|
-
print(f"Invalid payload: {e.message}")
|
|
643
|
-
except FacilitatorError as e:
|
|
644
|
-
# Facilitator returned error
|
|
645
|
-
print(f"Facilitator error: {e.status_code} - {e.response_body}")
|
|
646
|
-
except X402TimeoutError as e:
|
|
647
|
-
# Request timed out
|
|
648
|
-
print(f"{e.operation} timed out after {e.timeout_seconds}s")
|
|
649
|
-
except X402Error as e:
|
|
650
|
-
# Catch-all for x402 errors
|
|
651
|
-
print(f"Payment error: {e.message}")
|
|
652
|
-
```
|
|
653
|
-
|
|
654
|
-
---
|
|
655
|
-
|
|
656
|
-
## How x402 Works
|
|
657
|
-
|
|
658
|
-
The x402 protocol enables gasless USDC payments:
|
|
659
|
-
|
|
660
|
-
```
|
|
661
|
-
1. User Request --> Client sends request without payment
|
|
662
|
-
2. 402 Response <-- Server returns payment requirements
|
|
663
|
-
3. User Signs --> Wallet signs authorization (NO GAS!)
|
|
664
|
-
4. Frontend Sends --> X-PAYMENT header with signed payload
|
|
665
|
-
5. SDK Verifies --> Validates signature with facilitator
|
|
666
|
-
6. SDK Settles --> Facilitator executes on-chain transfer
|
|
667
|
-
7. Success <-- Payment confirmed, request processed
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
The facilitator (https://facilitator.ultravioletadao.xyz) handles all on-chain interactions and pays gas fees on behalf of users.
|
|
671
|
-
|
|
672
|
-
### Payment Flow by Network Type
|
|
673
|
-
|
|
674
|
-
| Network Type | User Signs | Facilitator Does |
|
|
675
|
-
|--------------|-----------|------------------|
|
|
676
|
-
| EVM | EIP-712 message | Calls `transferWithAuthorization()` |
|
|
677
|
-
| SVM | Partial transaction | Co-signs + submits transaction |
|
|
678
|
-
| NEAR | DelegateAction (Borsh) | Wraps in `Action::Delegate` |
|
|
679
|
-
| Stellar | Auth entry (XDR) | Wraps in fee-bump transaction |
|
|
680
|
-
|
|
681
|
-
---
|
|
682
|
-
|
|
683
|
-
##
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
-
|
|
701
|
-
|
|
702
|
-
**
|
|
703
|
-
-
|
|
704
|
-
-
|
|
705
|
-
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
-
|
|
769
|
-
-
|
|
770
|
-
-
|
|
771
|
-
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: uvd-x402-sdk
|
|
3
|
+
Version: 0.2.3
|
|
4
|
+
Summary: Python SDK for integrating x402 payments via the Ultravioleta DAO facilitator
|
|
5
|
+
Author-email: Ultravioleta DAO <dev@ultravioletadao.xyz>
|
|
6
|
+
Project-URL: Homepage, https://github.com/UltravioletaDAO/uvd-x402-sdk-python
|
|
7
|
+
Project-URL: Documentation, https://docs.ultravioletadao.xyz/x402-sdk
|
|
8
|
+
Project-URL: Repository, https://github.com/UltravioletaDAO/uvd-x402-sdk-python
|
|
9
|
+
Project-URL: Issues, https://github.com/UltravioletaDAO/uvd-x402-sdk-python/issues
|
|
10
|
+
Keywords: x402,payments,crypto,usdc,web3,evm,solana,near,stellar,facilitator
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Topic :: Office/Business :: Financial :: Point-Of-Sale
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: httpx>=0.24.0
|
|
26
|
+
Requires-Dist: pydantic>=2.0.0
|
|
27
|
+
Provides-Extra: all
|
|
28
|
+
Requires-Dist: uvd-x402-sdk[aws,django,fastapi,flask,web3]; extra == "all"
|
|
29
|
+
Provides-Extra: aws
|
|
30
|
+
Requires-Dist: boto3>=1.26.0; extra == "aws"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
34
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
37
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
38
|
+
Requires-Dist: httpx>=0.24.0; extra == "dev"
|
|
39
|
+
Provides-Extra: django
|
|
40
|
+
Requires-Dist: django>=4.0.0; extra == "django"
|
|
41
|
+
Provides-Extra: fastapi
|
|
42
|
+
Requires-Dist: fastapi>=0.100.0; extra == "fastapi"
|
|
43
|
+
Requires-Dist: starlette>=0.27.0; extra == "fastapi"
|
|
44
|
+
Provides-Extra: flask
|
|
45
|
+
Requires-Dist: flask>=2.0.0; extra == "flask"
|
|
46
|
+
Provides-Extra: web3
|
|
47
|
+
Requires-Dist: web3>=6.0.0; extra == "web3"
|
|
48
|
+
Requires-Dist: eth-account>=0.10.0; extra == "web3"
|
|
49
|
+
|
|
50
|
+
# uvd-x402-sdk
|
|
51
|
+
|
|
52
|
+
Python SDK for integrating **x402 cryptocurrency payments** via the Ultravioleta DAO facilitator.
|
|
53
|
+
|
|
54
|
+
Accept USDC payments across **14 blockchain networks** with a single integration. The SDK handles signature verification, on-chain settlement, and all the complexity of multi-chain payments.
|
|
55
|
+
|
|
56
|
+
## Features
|
|
57
|
+
|
|
58
|
+
- **14 Networks**: EVM chains (Base, Ethereum, Polygon, etc.), SVM chains (Solana, Fogo), NEAR, and Stellar
|
|
59
|
+
- **x402 v1 & v2**: Full support for both protocol versions with auto-detection
|
|
60
|
+
- **Framework Integrations**: Flask, FastAPI, Django, AWS Lambda
|
|
61
|
+
- **Gasless Payments**: Users sign authorizations, facilitator pays all network fees
|
|
62
|
+
- **Simple API**: Decorators and middleware for quick integration
|
|
63
|
+
- **Type Safety**: Full Pydantic models and type hints
|
|
64
|
+
- **Extensible**: Register custom networks easily
|
|
65
|
+
|
|
66
|
+
## Quick Start (5 Lines)
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
from decimal import Decimal
|
|
70
|
+
from uvd_x402_sdk import X402Client
|
|
71
|
+
|
|
72
|
+
client = X402Client(recipient_address="0xYourWallet...")
|
|
73
|
+
result = client.process_payment(request.headers["X-PAYMENT"], Decimal("10.00"))
|
|
74
|
+
print(f"Paid by {result.payer_address}, tx: {result.transaction_hash}")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Supported Networks
|
|
78
|
+
|
|
79
|
+
| Network | Type | Chain ID | CAIP-2 | Status |
|
|
80
|
+
|---------|------|----------|--------|--------|
|
|
81
|
+
| Base | EVM | 8453 | `eip155:8453` | Active |
|
|
82
|
+
| Ethereum | EVM | 1 | `eip155:1` | Active |
|
|
83
|
+
| Polygon | EVM | 137 | `eip155:137` | Active |
|
|
84
|
+
| Arbitrum | EVM | 42161 | `eip155:42161` | Active |
|
|
85
|
+
| Optimism | EVM | 10 | `eip155:10` | Active |
|
|
86
|
+
| Avalanche | EVM | 43114 | `eip155:43114` | Active |
|
|
87
|
+
| Celo | EVM | 42220 | `eip155:42220` | Active |
|
|
88
|
+
| HyperEVM | EVM | 999 | `eip155:999` | Active |
|
|
89
|
+
| Unichain | EVM | 130 | `eip155:130` | Active |
|
|
90
|
+
| Monad | EVM | 143 | `eip155:143` | Active |
|
|
91
|
+
| Solana | SVM | - | `solana:5eykt...` | Active |
|
|
92
|
+
| Fogo | SVM | - | `solana:fogo` | Active |
|
|
93
|
+
| NEAR | NEAR | - | `near:mainnet` | Active |
|
|
94
|
+
| Stellar | Stellar | - | `stellar:pubnet` | Active |
|
|
95
|
+
|
|
96
|
+
## Installation
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Core SDK (minimal dependencies)
|
|
100
|
+
pip install uvd-x402-sdk
|
|
101
|
+
|
|
102
|
+
# With framework support
|
|
103
|
+
pip install uvd-x402-sdk[flask] # Flask integration
|
|
104
|
+
pip install uvd-x402-sdk[fastapi] # FastAPI/Starlette integration
|
|
105
|
+
pip install uvd-x402-sdk[django] # Django integration
|
|
106
|
+
pip install uvd-x402-sdk[aws] # AWS Lambda helpers
|
|
107
|
+
|
|
108
|
+
# All integrations
|
|
109
|
+
pip install uvd-x402-sdk[all]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Framework Examples
|
|
115
|
+
|
|
116
|
+
### Flask
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from decimal import Decimal
|
|
120
|
+
from flask import Flask, g, jsonify
|
|
121
|
+
from uvd_x402_sdk.integrations import FlaskX402
|
|
122
|
+
|
|
123
|
+
app = Flask(__name__)
|
|
124
|
+
x402 = FlaskX402(
|
|
125
|
+
app,
|
|
126
|
+
recipient_address="0xYourEVMWallet...",
|
|
127
|
+
recipient_solana="YourSolanaAddress...",
|
|
128
|
+
recipient_near="your-account.near",
|
|
129
|
+
recipient_stellar="G...YourStellarAddress",
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
@app.route("/api/premium")
|
|
133
|
+
@x402.require_payment(amount_usd=Decimal("5.00"))
|
|
134
|
+
def premium():
|
|
135
|
+
return jsonify({
|
|
136
|
+
"message": "Premium content!",
|
|
137
|
+
"payer": g.payment_result.payer_address,
|
|
138
|
+
"tx": g.payment_result.transaction_hash,
|
|
139
|
+
"network": g.payment_result.network,
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
@app.route("/api/basic")
|
|
143
|
+
@x402.require_payment(amount_usd=Decimal("0.10"))
|
|
144
|
+
def basic():
|
|
145
|
+
return jsonify({"data": "Basic tier data"})
|
|
146
|
+
|
|
147
|
+
if __name__ == "__main__":
|
|
148
|
+
app.run(debug=True)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### FastAPI
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from decimal import Decimal
|
|
155
|
+
from fastapi import FastAPI, Depends
|
|
156
|
+
from uvd_x402_sdk.config import X402Config
|
|
157
|
+
from uvd_x402_sdk.models import PaymentResult
|
|
158
|
+
from uvd_x402_sdk.integrations import FastAPIX402
|
|
159
|
+
|
|
160
|
+
app = FastAPI()
|
|
161
|
+
x402 = FastAPIX402(
|
|
162
|
+
app,
|
|
163
|
+
recipient_address="0xYourEVMWallet...",
|
|
164
|
+
recipient_solana="YourSolanaAddress...",
|
|
165
|
+
recipient_near="your-account.near",
|
|
166
|
+
recipient_stellar="G...YourStellarAddress",
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
@app.get("/api/premium")
|
|
170
|
+
async def premium(
|
|
171
|
+
payment: PaymentResult = Depends(x402.require_payment(amount_usd="5.00"))
|
|
172
|
+
):
|
|
173
|
+
return {
|
|
174
|
+
"message": "Premium content!",
|
|
175
|
+
"payer": payment.payer_address,
|
|
176
|
+
"network": payment.network,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@app.post("/api/generate")
|
|
180
|
+
async def generate(
|
|
181
|
+
body: dict,
|
|
182
|
+
payment: PaymentResult = Depends(x402.require_payment(amount_usd="1.00"))
|
|
183
|
+
):
|
|
184
|
+
# Dynamic processing based on request
|
|
185
|
+
return {"result": "generated", "payer": payment.payer_address}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Django
|
|
189
|
+
|
|
190
|
+
```python
|
|
191
|
+
# settings.py
|
|
192
|
+
X402_FACILITATOR_URL = "https://facilitator.ultravioletadao.xyz"
|
|
193
|
+
X402_RECIPIENT_EVM = "0xYourEVMWallet..."
|
|
194
|
+
X402_RECIPIENT_SOLANA = "YourSolanaAddress..."
|
|
195
|
+
X402_RECIPIENT_NEAR = "your-account.near"
|
|
196
|
+
X402_RECIPIENT_STELLAR = "G...YourStellarAddress"
|
|
197
|
+
X402_PROTECTED_PATHS = {
|
|
198
|
+
"/api/premium/": "5.00",
|
|
199
|
+
"/api/basic/": "1.00",
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
MIDDLEWARE = [
|
|
203
|
+
# ...other middleware...
|
|
204
|
+
"uvd_x402_sdk.integrations.django_integration.DjangoX402Middleware",
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
# views.py
|
|
208
|
+
from django.http import JsonResponse
|
|
209
|
+
from uvd_x402_sdk.integrations import django_x402_required
|
|
210
|
+
|
|
211
|
+
@django_x402_required(amount_usd="5.00")
|
|
212
|
+
def premium_view(request):
|
|
213
|
+
payment = request.payment_result
|
|
214
|
+
return JsonResponse({
|
|
215
|
+
"message": "Premium content!",
|
|
216
|
+
"payer": payment.payer_address,
|
|
217
|
+
})
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### AWS Lambda
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
import json
|
|
224
|
+
from decimal import Decimal
|
|
225
|
+
from uvd_x402_sdk.config import X402Config
|
|
226
|
+
from uvd_x402_sdk.integrations import LambdaX402
|
|
227
|
+
|
|
228
|
+
config = X402Config(
|
|
229
|
+
recipient_evm="0xYourEVMWallet...",
|
|
230
|
+
recipient_solana="YourSolanaAddress...",
|
|
231
|
+
recipient_near="your-account.near",
|
|
232
|
+
recipient_stellar="G...YourStellarAddress",
|
|
233
|
+
)
|
|
234
|
+
x402 = LambdaX402(config=config)
|
|
235
|
+
|
|
236
|
+
def handler(event, context):
|
|
237
|
+
# Calculate price based on request
|
|
238
|
+
body = json.loads(event.get("body", "{}"))
|
|
239
|
+
quantity = body.get("quantity", 1)
|
|
240
|
+
price = Decimal(str(quantity * 0.01))
|
|
241
|
+
|
|
242
|
+
# Process payment or return 402
|
|
243
|
+
result = x402.process_or_require(event, price)
|
|
244
|
+
|
|
245
|
+
# If 402 response, return it
|
|
246
|
+
if isinstance(result, dict) and "statusCode" in result:
|
|
247
|
+
return result
|
|
248
|
+
|
|
249
|
+
# Payment verified!
|
|
250
|
+
return {
|
|
251
|
+
"statusCode": 200,
|
|
252
|
+
"headers": {"Content-Type": "application/json"},
|
|
253
|
+
"body": json.dumps({
|
|
254
|
+
"success": True,
|
|
255
|
+
"payer": result.payer_address,
|
|
256
|
+
"tx": result.transaction_hash,
|
|
257
|
+
"network": result.network,
|
|
258
|
+
"quantity": quantity,
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## Network-Specific Examples
|
|
266
|
+
|
|
267
|
+
### EVM Chains (Base, Ethereum, Polygon, etc.)
|
|
268
|
+
|
|
269
|
+
EVM chains use ERC-3009 `TransferWithAuthorization` with EIP-712 signatures.
|
|
270
|
+
|
|
271
|
+
```python
|
|
272
|
+
from uvd_x402_sdk import X402Client, X402Config
|
|
273
|
+
|
|
274
|
+
# Accept payments on Base and Ethereum only
|
|
275
|
+
config = X402Config(
|
|
276
|
+
recipient_evm="0xYourEVMWallet...",
|
|
277
|
+
supported_networks=["base", "ethereum"],
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
client = X402Client(config=config)
|
|
281
|
+
result = client.process_payment(x_payment_header, Decimal("10.00"))
|
|
282
|
+
|
|
283
|
+
# The payload contains EIP-712 signature + authorization
|
|
284
|
+
payload = client.extract_payload(x_payment_header)
|
|
285
|
+
evm_data = payload.get_evm_payload()
|
|
286
|
+
print(f"From: {evm_data.authorization.from_address}")
|
|
287
|
+
print(f"To: {evm_data.authorization.to}")
|
|
288
|
+
print(f"Value: {evm_data.authorization.value}")
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Solana & Fogo (SVM Chains)
|
|
292
|
+
|
|
293
|
+
SVM chains use partially-signed VersionedTransactions with SPL token transfers.
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
from uvd_x402_sdk import X402Client, X402Config
|
|
297
|
+
|
|
298
|
+
# Accept payments on Solana and Fogo
|
|
299
|
+
config = X402Config(
|
|
300
|
+
recipient_solana="YourSolanaAddress...",
|
|
301
|
+
supported_networks=["solana", "fogo"],
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
client = X402Client(config=config)
|
|
305
|
+
result = client.process_payment(x_payment_header, Decimal("5.00"))
|
|
306
|
+
|
|
307
|
+
# The payload contains a base64-encoded VersionedTransaction
|
|
308
|
+
payload = client.extract_payload(x_payment_header)
|
|
309
|
+
svm_data = payload.get_svm_payload()
|
|
310
|
+
print(f"Transaction: {svm_data.transaction[:50]}...")
|
|
311
|
+
|
|
312
|
+
# Fogo has ultra-fast finality (~400ms)
|
|
313
|
+
if result.network == "fogo":
|
|
314
|
+
print("Payment confirmed in ~400ms!")
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Stellar
|
|
318
|
+
|
|
319
|
+
Stellar uses Soroban Authorization Entries with fee-bump transactions.
|
|
320
|
+
|
|
321
|
+
```python
|
|
322
|
+
from uvd_x402_sdk import X402Client, X402Config
|
|
323
|
+
|
|
324
|
+
config = X402Config(
|
|
325
|
+
recipient_stellar="G...YourStellarAddress",
|
|
326
|
+
supported_networks=["stellar"],
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
client = X402Client(config=config)
|
|
330
|
+
result = client.process_payment(x_payment_header, Decimal("1.00"))
|
|
331
|
+
|
|
332
|
+
# Stellar uses 7 decimals (stroops)
|
|
333
|
+
payload = client.extract_payload(x_payment_header)
|
|
334
|
+
stellar_data = payload.get_stellar_payload()
|
|
335
|
+
print(f"From: {stellar_data.from_address}")
|
|
336
|
+
print(f"Amount (stroops): {stellar_data.amount}")
|
|
337
|
+
print(f"Token Contract: {stellar_data.tokenContract}")
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
### NEAR Protocol
|
|
341
|
+
|
|
342
|
+
NEAR uses NEP-366 meta-transactions with Borsh serialization.
|
|
343
|
+
|
|
344
|
+
```python
|
|
345
|
+
from uvd_x402_sdk import X402Client, X402Config
|
|
346
|
+
|
|
347
|
+
config = X402Config(
|
|
348
|
+
recipient_near="your-recipient.near",
|
|
349
|
+
supported_networks=["near"],
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
client = X402Client(config=config)
|
|
353
|
+
result = client.process_payment(x_payment_header, Decimal("2.00"))
|
|
354
|
+
|
|
355
|
+
# NEAR payload contains a SignedDelegateAction
|
|
356
|
+
payload = client.extract_payload(x_payment_header)
|
|
357
|
+
near_data = payload.get_near_payload()
|
|
358
|
+
print(f"SignedDelegateAction: {near_data.signedDelegateAction[:50]}...")
|
|
359
|
+
|
|
360
|
+
# Validate NEAR payload structure
|
|
361
|
+
from uvd_x402_sdk.networks.near import validate_near_payload
|
|
362
|
+
validate_near_payload(payload.payload) # Raises ValueError if invalid
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## x402 v1 vs v2
|
|
368
|
+
|
|
369
|
+
The SDK supports both x402 protocol versions with automatic detection.
|
|
370
|
+
|
|
371
|
+
### Version Differences
|
|
372
|
+
|
|
373
|
+
| Aspect | v1 | v2 |
|
|
374
|
+
|--------|----|----|
|
|
375
|
+
| Network ID | String (`"base"`) | CAIP-2 (`"eip155:8453"`) |
|
|
376
|
+
| Payment delivery | JSON body | `PAYMENT-REQUIRED` header |
|
|
377
|
+
| Multiple options | Limited | `accepts` array |
|
|
378
|
+
| Discovery | Implicit | Optional extension |
|
|
379
|
+
|
|
380
|
+
### Auto-Detection
|
|
381
|
+
|
|
382
|
+
```python
|
|
383
|
+
from uvd_x402_sdk import PaymentPayload
|
|
384
|
+
|
|
385
|
+
# The SDK auto-detects based on network format
|
|
386
|
+
payload = PaymentPayload(
|
|
387
|
+
x402Version=1,
|
|
388
|
+
scheme="exact",
|
|
389
|
+
network="base", # v1 format
|
|
390
|
+
payload={"signature": "...", "authorization": {...}}
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
print(payload.is_v2()) # False
|
|
394
|
+
|
|
395
|
+
payload_v2 = PaymentPayload(
|
|
396
|
+
x402Version=2,
|
|
397
|
+
scheme="exact",
|
|
398
|
+
network="eip155:8453", # v2 CAIP-2 format
|
|
399
|
+
payload={"signature": "...", "authorization": {...}}
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
print(payload_v2.is_v2()) # True
|
|
403
|
+
|
|
404
|
+
# Both work the same way
|
|
405
|
+
print(payload.get_normalized_network()) # "base"
|
|
406
|
+
print(payload_v2.get_normalized_network()) # "base"
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Creating v2 Responses
|
|
410
|
+
|
|
411
|
+
```python
|
|
412
|
+
from uvd_x402_sdk import X402Config, create_402_response_v2, Payment402BuilderV2
|
|
413
|
+
|
|
414
|
+
config = X402Config(
|
|
415
|
+
recipient_evm="0xYourEVM...",
|
|
416
|
+
recipient_solana="YourSolana...",
|
|
417
|
+
recipient_near="your.near",
|
|
418
|
+
recipient_stellar="G...Stellar",
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
# Simple v2 response
|
|
422
|
+
response = create_402_response_v2(
|
|
423
|
+
amount_usd=Decimal("5.00"),
|
|
424
|
+
config=config,
|
|
425
|
+
resource="/api/premium",
|
|
426
|
+
description="Premium API access",
|
|
427
|
+
)
|
|
428
|
+
# Returns:
|
|
429
|
+
# {
|
|
430
|
+
# "x402Version": 2,
|
|
431
|
+
# "scheme": "exact",
|
|
432
|
+
# "resource": "/api/premium",
|
|
433
|
+
# "accepts": [
|
|
434
|
+
# {"network": "eip155:8453", "asset": "0x833...", "amount": "5000000", "payTo": "0xYour..."},
|
|
435
|
+
# {"network": "solana:5eykt...", "asset": "EPjF...", "amount": "5000000", "payTo": "Your..."},
|
|
436
|
+
# {"network": "near:mainnet", "asset": "1720...", "amount": "5000000", "payTo": "your.near"},
|
|
437
|
+
# ...
|
|
438
|
+
# ]
|
|
439
|
+
# }
|
|
440
|
+
|
|
441
|
+
# Builder pattern for more control
|
|
442
|
+
response = (
|
|
443
|
+
Payment402BuilderV2(config)
|
|
444
|
+
.amount(Decimal("10.00"))
|
|
445
|
+
.resource("/api/generate")
|
|
446
|
+
.description("AI generation credits")
|
|
447
|
+
.networks(["base", "solana", "near"]) # Limit to specific networks
|
|
448
|
+
.build()
|
|
449
|
+
)
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Payload Validation
|
|
455
|
+
|
|
456
|
+
Each network type has specific payload validation:
|
|
457
|
+
|
|
458
|
+
### EVM Validation
|
|
459
|
+
|
|
460
|
+
```python
|
|
461
|
+
from uvd_x402_sdk.models import EVMPayloadContent, EVMAuthorization
|
|
462
|
+
|
|
463
|
+
# Parse and validate EVM payload
|
|
464
|
+
payload = client.extract_payload(x_payment_header)
|
|
465
|
+
evm_data = payload.get_evm_payload()
|
|
466
|
+
|
|
467
|
+
# Validate authorization fields
|
|
468
|
+
auth = evm_data.authorization
|
|
469
|
+
assert auth.from_address.startswith("0x")
|
|
470
|
+
assert auth.to.startswith("0x")
|
|
471
|
+
assert int(auth.value) > 0
|
|
472
|
+
assert int(auth.validBefore) > int(auth.validAfter)
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### SVM Validation
|
|
476
|
+
|
|
477
|
+
```python
|
|
478
|
+
from uvd_x402_sdk.networks.solana import validate_svm_payload, is_valid_solana_address
|
|
479
|
+
|
|
480
|
+
# Validate SVM payload
|
|
481
|
+
payload = client.extract_payload(x_payment_header)
|
|
482
|
+
validate_svm_payload(payload.payload) # Raises ValueError if invalid
|
|
483
|
+
|
|
484
|
+
# Validate Solana addresses
|
|
485
|
+
assert is_valid_solana_address("YourSolanaAddress...")
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
### NEAR Validation
|
|
489
|
+
|
|
490
|
+
```python
|
|
491
|
+
from uvd_x402_sdk.networks.near import (
|
|
492
|
+
validate_near_payload,
|
|
493
|
+
is_valid_near_account_id,
|
|
494
|
+
BorshSerializer,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
# Validate NEAR payload
|
|
498
|
+
payload = client.extract_payload(x_payment_header)
|
|
499
|
+
validate_near_payload(payload.payload) # Raises ValueError if invalid
|
|
500
|
+
|
|
501
|
+
# Validate NEAR account IDs
|
|
502
|
+
assert is_valid_near_account_id("your-account.near")
|
|
503
|
+
assert is_valid_near_account_id("0xultravioleta.near")
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
### Stellar Validation
|
|
507
|
+
|
|
508
|
+
```python
|
|
509
|
+
from uvd_x402_sdk.networks.stellar import (
|
|
510
|
+
is_valid_stellar_address,
|
|
511
|
+
is_valid_contract_address,
|
|
512
|
+
stroops_to_usd,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
# Validate Stellar addresses
|
|
516
|
+
assert is_valid_stellar_address("G...YourStellarAddress") # G...
|
|
517
|
+
assert is_valid_contract_address("C...USDCContract") # C...
|
|
518
|
+
|
|
519
|
+
# Convert stroops to USD (7 decimals)
|
|
520
|
+
usd = stroops_to_usd(50000000) # Returns 5.0
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
---
|
|
524
|
+
|
|
525
|
+
## Configuration
|
|
526
|
+
|
|
527
|
+
### Environment Variables
|
|
528
|
+
|
|
529
|
+
```bash
|
|
530
|
+
# Core configuration
|
|
531
|
+
X402_FACILITATOR_URL=https://facilitator.ultravioletadao.xyz
|
|
532
|
+
X402_VERIFY_TIMEOUT=30
|
|
533
|
+
X402_SETTLE_TIMEOUT=55
|
|
534
|
+
|
|
535
|
+
# Recipient addresses (at least one required)
|
|
536
|
+
X402_RECIPIENT_EVM=0xYourEVMWallet
|
|
537
|
+
X402_RECIPIENT_SOLANA=YourSolanaAddress
|
|
538
|
+
X402_RECIPIENT_NEAR=your-account.near
|
|
539
|
+
X402_RECIPIENT_STELLAR=G...YourStellarAddress
|
|
540
|
+
|
|
541
|
+
# Optional
|
|
542
|
+
X402_FACILITATOR_SOLANA=F742C4VfFLQ9zRQyithoj5229ZgtX2WqKCSFKgH2EThq
|
|
543
|
+
X402_RESOURCE_URL=https://api.example.com
|
|
544
|
+
X402_DESCRIPTION=API access payment
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Programmatic Configuration
|
|
548
|
+
|
|
549
|
+
```python
|
|
550
|
+
from uvd_x402_sdk import X402Config, MultiPaymentConfig
|
|
551
|
+
|
|
552
|
+
# Full configuration
|
|
553
|
+
config = X402Config(
|
|
554
|
+
facilitator_url="https://facilitator.ultravioletadao.xyz",
|
|
555
|
+
|
|
556
|
+
# Recipients
|
|
557
|
+
recipient_evm="0xYourEVMWallet",
|
|
558
|
+
recipient_solana="YourSolanaAddress",
|
|
559
|
+
recipient_near="your-account.near",
|
|
560
|
+
recipient_stellar="G...YourStellarAddress",
|
|
561
|
+
|
|
562
|
+
# Timeouts
|
|
563
|
+
verify_timeout=30.0,
|
|
564
|
+
settle_timeout=55.0,
|
|
565
|
+
|
|
566
|
+
# Limit to specific networks
|
|
567
|
+
supported_networks=["base", "solana", "near", "stellar"],
|
|
568
|
+
|
|
569
|
+
# Metadata
|
|
570
|
+
resource_url="https://api.example.com/premium",
|
|
571
|
+
description="Premium API access",
|
|
572
|
+
|
|
573
|
+
# Protocol version (1, 2, or "auto")
|
|
574
|
+
x402_version="auto",
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
# From environment
|
|
578
|
+
config = X402Config.from_env()
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## Registering Custom Networks
|
|
584
|
+
|
|
585
|
+
```python
|
|
586
|
+
from uvd_x402_sdk.networks import NetworkConfig, NetworkType, register_network
|
|
587
|
+
|
|
588
|
+
# Register a custom EVM network
|
|
589
|
+
custom_chain = NetworkConfig(
|
|
590
|
+
name="mychain",
|
|
591
|
+
display_name="My Custom Chain",
|
|
592
|
+
network_type=NetworkType.EVM,
|
|
593
|
+
chain_id=12345,
|
|
594
|
+
usdc_address="0xUSDCContractAddress",
|
|
595
|
+
usdc_decimals=6,
|
|
596
|
+
usdc_domain_name="USD Coin", # Check actual EIP-712 domain!
|
|
597
|
+
usdc_domain_version="2",
|
|
598
|
+
rpc_url="https://rpc.mychain.com",
|
|
599
|
+
enabled=True,
|
|
600
|
+
)
|
|
601
|
+
|
|
602
|
+
register_network(custom_chain)
|
|
603
|
+
|
|
604
|
+
# Now you can use it
|
|
605
|
+
config = X402Config(
|
|
606
|
+
recipient_evm="0xYourWallet...",
|
|
607
|
+
supported_networks=["base", "mychain"],
|
|
608
|
+
)
|
|
609
|
+
```
|
|
610
|
+
|
|
611
|
+
---
|
|
612
|
+
|
|
613
|
+
## Error Handling
|
|
614
|
+
|
|
615
|
+
```python
|
|
616
|
+
from uvd_x402_sdk.exceptions import (
|
|
617
|
+
X402Error,
|
|
618
|
+
PaymentRequiredError,
|
|
619
|
+
PaymentVerificationError,
|
|
620
|
+
PaymentSettlementError,
|
|
621
|
+
UnsupportedNetworkError,
|
|
622
|
+
InvalidPayloadError,
|
|
623
|
+
FacilitatorError,
|
|
624
|
+
X402TimeoutError,
|
|
625
|
+
)
|
|
626
|
+
|
|
627
|
+
try:
|
|
628
|
+
result = client.process_payment(header, amount)
|
|
629
|
+
except PaymentVerificationError as e:
|
|
630
|
+
# Signature invalid, amount mismatch, expired, etc.
|
|
631
|
+
print(f"Verification failed: {e.reason}")
|
|
632
|
+
print(f"Errors: {e.errors}")
|
|
633
|
+
except PaymentSettlementError as e:
|
|
634
|
+
# On-chain settlement failed (insufficient balance, nonce used, etc.)
|
|
635
|
+
print(f"Settlement failed on {e.network}: {e.message}")
|
|
636
|
+
except UnsupportedNetworkError as e:
|
|
637
|
+
# Network not recognized or disabled
|
|
638
|
+
print(f"Network {e.network} not supported")
|
|
639
|
+
print(f"Supported: {e.supported_networks}")
|
|
640
|
+
except InvalidPayloadError as e:
|
|
641
|
+
# Malformed X-PAYMENT header
|
|
642
|
+
print(f"Invalid payload: {e.message}")
|
|
643
|
+
except FacilitatorError as e:
|
|
644
|
+
# Facilitator returned error
|
|
645
|
+
print(f"Facilitator error: {e.status_code} - {e.response_body}")
|
|
646
|
+
except X402TimeoutError as e:
|
|
647
|
+
# Request timed out
|
|
648
|
+
print(f"{e.operation} timed out after {e.timeout_seconds}s")
|
|
649
|
+
except X402Error as e:
|
|
650
|
+
# Catch-all for x402 errors
|
|
651
|
+
print(f"Payment error: {e.message}")
|
|
652
|
+
```
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
656
|
+
## How x402 Works
|
|
657
|
+
|
|
658
|
+
The x402 protocol enables gasless USDC payments:
|
|
659
|
+
|
|
660
|
+
```
|
|
661
|
+
1. User Request --> Client sends request without payment
|
|
662
|
+
2. 402 Response <-- Server returns payment requirements
|
|
663
|
+
3. User Signs --> Wallet signs authorization (NO GAS!)
|
|
664
|
+
4. Frontend Sends --> X-PAYMENT header with signed payload
|
|
665
|
+
5. SDK Verifies --> Validates signature with facilitator
|
|
666
|
+
6. SDK Settles --> Facilitator executes on-chain transfer
|
|
667
|
+
7. Success <-- Payment confirmed, request processed
|
|
668
|
+
```
|
|
669
|
+
|
|
670
|
+
The facilitator (https://facilitator.ultravioletadao.xyz) handles all on-chain interactions and pays gas fees on behalf of users.
|
|
671
|
+
|
|
672
|
+
### Payment Flow by Network Type
|
|
673
|
+
|
|
674
|
+
| Network Type | User Signs | Facilitator Does |
|
|
675
|
+
|--------------|-----------|------------------|
|
|
676
|
+
| EVM | EIP-712 message | Calls `transferWithAuthorization()` |
|
|
677
|
+
| SVM | Partial transaction | Co-signs + submits transaction |
|
|
678
|
+
| NEAR | DelegateAction (Borsh) | Wraps in `Action::Delegate` |
|
|
679
|
+
| Stellar | Auth entry (XDR) | Wraps in fee-bump transaction |
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
## Error Codes
|
|
684
|
+
|
|
685
|
+
| Exception | Description |
|
|
686
|
+
|-----------|-------------|
|
|
687
|
+
| `PaymentRequiredError` | No payment header provided |
|
|
688
|
+
| `PaymentVerificationError` | Signature invalid, amount mismatch, expired |
|
|
689
|
+
| `PaymentSettlementError` | On-chain settlement failed |
|
|
690
|
+
| `UnsupportedNetworkError` | Network not recognized or disabled |
|
|
691
|
+
| `InvalidPayloadError` | Malformed X-PAYMENT header |
|
|
692
|
+
| `FacilitatorError` | Facilitator service error |
|
|
693
|
+
| `ConfigurationError` | Invalid SDK configuration |
|
|
694
|
+
| `X402TimeoutError` | Request timed out |
|
|
695
|
+
|
|
696
|
+
---
|
|
697
|
+
|
|
698
|
+
## Security
|
|
699
|
+
|
|
700
|
+
- Users **NEVER** pay gas or submit transactions directly
|
|
701
|
+
- **EVM**: Users sign EIP-712 structured messages only (no transaction broadcast)
|
|
702
|
+
- **Solana/Fogo**: Users sign partial transactions (facilitator co-signs and submits)
|
|
703
|
+
- **Stellar**: Users sign Soroban authorization entries only
|
|
704
|
+
- **NEAR**: Users sign NEP-366 meta-transactions (DelegateAction)
|
|
705
|
+
- The facilitator submits and pays for all on-chain transactions
|
|
706
|
+
- All signatures include expiration timestamps (`validBefore`) for replay protection
|
|
707
|
+
- Nonces prevent double-spending of authorizations
|
|
708
|
+
|
|
709
|
+
---
|
|
710
|
+
|
|
711
|
+
## Troubleshooting
|
|
712
|
+
|
|
713
|
+
### Common Issues
|
|
714
|
+
|
|
715
|
+
**"Unsupported network"**
|
|
716
|
+
- Check that the network is in `supported_networks`
|
|
717
|
+
- Verify the network is enabled
|
|
718
|
+
- For v2, ensure CAIP-2 format is correct
|
|
719
|
+
|
|
720
|
+
**"Payment verification failed"**
|
|
721
|
+
- Amount mismatch between expected and signed
|
|
722
|
+
- Recipient address mismatch
|
|
723
|
+
- Authorization expired (`validBefore` in the past)
|
|
724
|
+
- Nonce already used (replay attack protection)
|
|
725
|
+
|
|
726
|
+
**"Settlement timed out"**
|
|
727
|
+
- Network congestion - increase `settle_timeout`
|
|
728
|
+
- Facilitator under load - retry after delay
|
|
729
|
+
|
|
730
|
+
**"Invalid payload"**
|
|
731
|
+
- Check base64 encoding of X-PAYMENT header
|
|
732
|
+
- Verify JSON structure matches expected format
|
|
733
|
+
- Ensure `x402Version` is 1 or 2
|
|
734
|
+
|
|
735
|
+
### Debug Logging
|
|
736
|
+
|
|
737
|
+
```python
|
|
738
|
+
import logging
|
|
739
|
+
logging.basicConfig(level=logging.DEBUG)
|
|
740
|
+
logging.getLogger("uvd_x402_sdk").setLevel(logging.DEBUG)
|
|
741
|
+
```
|
|
742
|
+
|
|
743
|
+
---
|
|
744
|
+
|
|
745
|
+
## Development
|
|
746
|
+
|
|
747
|
+
```bash
|
|
748
|
+
# Clone and install
|
|
749
|
+
git clone https://github.com/UltravioletaDAO/uvd-x402-sdk-python
|
|
750
|
+
cd uvd-x402-sdk-python
|
|
751
|
+
pip install -e ".[dev]"
|
|
752
|
+
|
|
753
|
+
# Run tests
|
|
754
|
+
pytest
|
|
755
|
+
|
|
756
|
+
# Format code
|
|
757
|
+
black src tests
|
|
758
|
+
ruff check src tests
|
|
759
|
+
|
|
760
|
+
# Type checking
|
|
761
|
+
mypy src
|
|
762
|
+
```
|
|
763
|
+
|
|
764
|
+
---
|
|
765
|
+
|
|
766
|
+
## Links
|
|
767
|
+
|
|
768
|
+
- [x402 Protocol](https://x402.org)
|
|
769
|
+
- [Ultravioleta DAO](https://ultravioletadao.xyz)
|
|
770
|
+
- [402milly](https://402milly.xyz)
|
|
771
|
+
- [GitHub](https://github.com/UltravioletaDAO/uvd-x402-sdk-python)
|
|
772
|
+
- [PyPI](https://pypi.org/project/uvd-x402-sdk/)
|
|
773
|
+
- [TypeScript SDK](https://github.com/UltravioletaDAO/uvd-x402-sdk-typescript)
|
|
774
|
+
|
|
775
|
+
---
|
|
776
|
+
|
|
777
|
+
## License
|
|
778
|
+
|
|
779
|
+
MIT License - see LICENSE file.
|
|
780
|
+
|
|
781
|
+
---
|
|
782
|
+
|
|
783
|
+
## Changelog
|
|
784
|
+
|
|
785
|
+
### v0.2.2 (2025-12-16)
|
|
786
|
+
|
|
787
|
+
- Added Security section to documentation
|
|
788
|
+
- Added Error Codes table
|
|
789
|
+
- Updated links to new GitHub repository
|
|
790
|
+
- Synced documentation with TypeScript SDK
|
|
791
|
+
|
|
792
|
+
### v0.2.1 (2025-12-16)
|
|
793
|
+
|
|
794
|
+
- Removed BSC network (doesn't support ERC-3009)
|
|
795
|
+
- Added GitHub Actions workflow for PyPI publishing
|
|
796
|
+
- Updated to 14 supported networks
|
|
797
|
+
|
|
798
|
+
### v0.2.0 (2025-12-15)
|
|
799
|
+
|
|
800
|
+
- Added **NEAR Protocol** support with NEP-366 meta-transactions
|
|
801
|
+
- Added **Fogo** SVM chain support
|
|
802
|
+
- Added **x402 v2** protocol support with CAIP-2 network identifiers
|
|
803
|
+
- Added `accepts` array for multi-network payment options
|
|
804
|
+
- Refactored Solana to generic SVM type (supports Solana, Fogo, future SVM chains)
|
|
805
|
+
- Added CAIP-2 parsing utilities (`parse_caip2_network`, `to_caip2_network`)
|
|
806
|
+
- Added `MultiPaymentConfig` for multi-network recipient configuration
|
|
807
|
+
- Added `Payment402BuilderV2` for v2 response construction
|
|
808
|
+
|
|
809
|
+
### v0.1.0 (2025-12-01)
|
|
810
|
+
|
|
811
|
+
- Initial release
|
|
812
|
+
- EVM, Solana, Stellar network support
|
|
813
|
+
- Flask, FastAPI, Django, Lambda integrations
|
|
814
|
+
- Full Pydantic models
|