uvd-x402-sdk 0.2.2__py3-none-any.whl → 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- uvd_x402_sdk/__init__.py +185 -169
- uvd_x402_sdk/client.py +527 -527
- uvd_x402_sdk/config.py +248 -248
- uvd_x402_sdk/decorators.py +325 -325
- uvd_x402_sdk/exceptions.py +254 -254
- uvd_x402_sdk/integrations/__init__.py +74 -74
- uvd_x402_sdk/integrations/django_integration.py +237 -237
- uvd_x402_sdk/integrations/fastapi_integration.py +330 -330
- uvd_x402_sdk/integrations/flask_integration.py +259 -259
- uvd_x402_sdk/integrations/lambda_integration.py +320 -320
- uvd_x402_sdk/models.py +397 -397
- uvd_x402_sdk/networks/__init__.py +80 -54
- uvd_x402_sdk/networks/base.py +498 -347
- uvd_x402_sdk/networks/evm.py +356 -215
- uvd_x402_sdk/networks/near.py +397 -397
- uvd_x402_sdk/networks/solana.py +282 -282
- uvd_x402_sdk/networks/stellar.py +129 -129
- uvd_x402_sdk/response.py +439 -439
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/LICENSE +21 -21
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/METADATA +927 -778
- uvd_x402_sdk-0.3.0.dist-info/RECORD +23 -0
- uvd_x402_sdk-0.2.2.dist-info/RECORD +0 -23
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/WHEEL +0 -0
- {uvd_x402_sdk-0.2.2.dist-info → uvd_x402_sdk-0.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,330 +1,330 @@
|
|
|
1
|
-
"""
|
|
2
|
-
FastAPI/Starlette integration for x402 payments.
|
|
3
|
-
|
|
4
|
-
Provides:
|
|
5
|
-
- FastAPIX402: App integration class
|
|
6
|
-
- X402Depends: Dependency injection for payment verification
|
|
7
|
-
- fastapi_require_payment: Decorator for protected routes
|
|
8
|
-
"""
|
|
9
|
-
|
|
10
|
-
from decimal import Decimal
|
|
11
|
-
from functools import wraps
|
|
12
|
-
from typing import Any, Callable, Optional, TypeVar, Union
|
|
13
|
-
|
|
14
|
-
try:
|
|
15
|
-
from fastapi import FastAPI, Request, HTTPException, Depends
|
|
16
|
-
from fastapi.responses import JSONResponse
|
|
17
|
-
from starlette.middleware.base import BaseHTTPMiddleware
|
|
18
|
-
except ImportError:
|
|
19
|
-
raise ImportError(
|
|
20
|
-
"FastAPI is required for FastAPI integration. "
|
|
21
|
-
"Install with: pip install uvd-x402-sdk[fastapi]"
|
|
22
|
-
)
|
|
23
|
-
|
|
24
|
-
from uvd_x402_sdk.client import X402Client
|
|
25
|
-
from uvd_x402_sdk.config import X402Config
|
|
26
|
-
from uvd_x402_sdk.exceptions import X402Error
|
|
27
|
-
from uvd_x402_sdk.models import PaymentResult
|
|
28
|
-
from uvd_x402_sdk.response import create_402_response, create_402_headers
|
|
29
|
-
|
|
30
|
-
F = TypeVar("F", bound=Callable[..., Any])
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class FastAPIX402:
|
|
34
|
-
"""
|
|
35
|
-
FastAPI integration for x402 payments.
|
|
36
|
-
|
|
37
|
-
Example:
|
|
38
|
-
>>> from fastapi import FastAPI
|
|
39
|
-
>>> from uvd_x402_sdk.integrations import FastAPIX402
|
|
40
|
-
>>>
|
|
41
|
-
>>> app = FastAPI()
|
|
42
|
-
>>> x402 = FastAPIX402(app, recipient_address="0xYourWallet...")
|
|
43
|
-
>>>
|
|
44
|
-
>>> @app.get("/premium")
|
|
45
|
-
>>> async def premium(payment: PaymentResult = Depends(x402.require_payment(1.00))):
|
|
46
|
-
... return {"payer": payment.payer_address}
|
|
47
|
-
"""
|
|
48
|
-
|
|
49
|
-
def __init__(
|
|
50
|
-
self,
|
|
51
|
-
app: Optional[FastAPI] = None,
|
|
52
|
-
config: Optional[X402Config] = None,
|
|
53
|
-
recipient_address: Optional[str] = None,
|
|
54
|
-
**kwargs: Any,
|
|
55
|
-
) -> None:
|
|
56
|
-
"""
|
|
57
|
-
Initialize FastAPI x402 integration.
|
|
58
|
-
|
|
59
|
-
Args:
|
|
60
|
-
app: FastAPI application (optional)
|
|
61
|
-
config: X402Config object
|
|
62
|
-
recipient_address: Default recipient for EVM chains
|
|
63
|
-
**kwargs: Additional config parameters
|
|
64
|
-
"""
|
|
65
|
-
self._config = config or X402Config(
|
|
66
|
-
recipient_evm=recipient_address or "",
|
|
67
|
-
**kwargs,
|
|
68
|
-
)
|
|
69
|
-
self._client = X402Client(config=self._config)
|
|
70
|
-
|
|
71
|
-
if app is not None:
|
|
72
|
-
self.init_app(app)
|
|
73
|
-
|
|
74
|
-
def init_app(self, app: FastAPI) -> None:
|
|
75
|
-
"""
|
|
76
|
-
Initialize with FastAPI app.
|
|
77
|
-
|
|
78
|
-
Stores client in app.state for access in routes.
|
|
79
|
-
"""
|
|
80
|
-
app.state.x402_client = self._client
|
|
81
|
-
app.state.x402_config = self._config
|
|
82
|
-
|
|
83
|
-
@property
|
|
84
|
-
def client(self) -> X402Client:
|
|
85
|
-
"""Get the x402 client."""
|
|
86
|
-
return self._client
|
|
87
|
-
|
|
88
|
-
@property
|
|
89
|
-
def config(self) -> X402Config:
|
|
90
|
-
"""Get the x402 config."""
|
|
91
|
-
return self._config
|
|
92
|
-
|
|
93
|
-
def require_payment(
|
|
94
|
-
self,
|
|
95
|
-
amount_usd: Union[Decimal, float, str],
|
|
96
|
-
message: Optional[str] = None,
|
|
97
|
-
) -> Callable[..., PaymentResult]:
|
|
98
|
-
"""
|
|
99
|
-
Create a FastAPI dependency that requires payment.
|
|
100
|
-
|
|
101
|
-
Args:
|
|
102
|
-
amount_usd: Required payment amount in USD
|
|
103
|
-
message: Custom message for 402 response
|
|
104
|
-
|
|
105
|
-
Returns:
|
|
106
|
-
Dependency function that returns PaymentResult
|
|
107
|
-
|
|
108
|
-
Example:
|
|
109
|
-
>>> @app.post("/api/premium")
|
|
110
|
-
>>> async def premium(
|
|
111
|
-
... request: Request,
|
|
112
|
-
... payment: PaymentResult = Depends(x402.require_payment(5.00))
|
|
113
|
-
... ):
|
|
114
|
-
... return {"payer": payment.payer_address}
|
|
115
|
-
"""
|
|
116
|
-
required_amount = Decimal(str(amount_usd))
|
|
117
|
-
|
|
118
|
-
async def dependency(request: Request) -> PaymentResult:
|
|
119
|
-
payment_header = request.headers.get("X-PAYMENT")
|
|
120
|
-
|
|
121
|
-
if not payment_header:
|
|
122
|
-
response_body = create_402_response(
|
|
123
|
-
amount_usd=required_amount,
|
|
124
|
-
config=self._config,
|
|
125
|
-
message=message,
|
|
126
|
-
)
|
|
127
|
-
raise HTTPException(
|
|
128
|
-
status_code=402,
|
|
129
|
-
detail=response_body,
|
|
130
|
-
headers=create_402_headers(),
|
|
131
|
-
)
|
|
132
|
-
|
|
133
|
-
try:
|
|
134
|
-
return self._client.process_payment(
|
|
135
|
-
x_payment_header=payment_header,
|
|
136
|
-
expected_amount_usd=required_amount,
|
|
137
|
-
)
|
|
138
|
-
except X402Error as e:
|
|
139
|
-
raise HTTPException(
|
|
140
|
-
status_code=402,
|
|
141
|
-
detail=e.to_dict(),
|
|
142
|
-
headers=create_402_headers(),
|
|
143
|
-
)
|
|
144
|
-
|
|
145
|
-
return dependency
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
class X402Depends:
|
|
149
|
-
"""
|
|
150
|
-
Reusable FastAPI dependency for x402 payments.
|
|
151
|
-
|
|
152
|
-
This provides a cleaner syntax for dependency injection.
|
|
153
|
-
|
|
154
|
-
Example:
|
|
155
|
-
>>> x402_payment = X402Depends(
|
|
156
|
-
... config=X402Config(recipient_evm="0x..."),
|
|
157
|
-
... amount_usd=Decimal("1.00")
|
|
158
|
-
... )
|
|
159
|
-
>>>
|
|
160
|
-
>>> @app.get("/resource")
|
|
161
|
-
>>> async def resource(payment: PaymentResult = Depends(x402_payment)):
|
|
162
|
-
... return {"payer": payment.payer_address}
|
|
163
|
-
"""
|
|
164
|
-
|
|
165
|
-
def __init__(
|
|
166
|
-
self,
|
|
167
|
-
config: X402Config,
|
|
168
|
-
amount_usd: Union[Decimal, float, str],
|
|
169
|
-
message: Optional[str] = None,
|
|
170
|
-
) -> None:
|
|
171
|
-
self._config = config
|
|
172
|
-
self._client = X402Client(config=config)
|
|
173
|
-
self._amount = Decimal(str(amount_usd))
|
|
174
|
-
self._message = message
|
|
175
|
-
|
|
176
|
-
async def __call__(self, request: Request) -> PaymentResult:
|
|
177
|
-
"""Process payment when used as dependency."""
|
|
178
|
-
payment_header = request.headers.get("X-PAYMENT")
|
|
179
|
-
|
|
180
|
-
if not payment_header:
|
|
181
|
-
response_body = create_402_response(
|
|
182
|
-
amount_usd=self._amount,
|
|
183
|
-
config=self._config,
|
|
184
|
-
message=self._message,
|
|
185
|
-
)
|
|
186
|
-
raise HTTPException(
|
|
187
|
-
status_code=402,
|
|
188
|
-
detail=response_body,
|
|
189
|
-
headers=create_402_headers(),
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
try:
|
|
193
|
-
return self._client.process_payment(
|
|
194
|
-
x_payment_header=payment_header,
|
|
195
|
-
expected_amount_usd=self._amount,
|
|
196
|
-
)
|
|
197
|
-
except X402Error as e:
|
|
198
|
-
raise HTTPException(
|
|
199
|
-
status_code=402,
|
|
200
|
-
detail=e.to_dict(),
|
|
201
|
-
headers=create_402_headers(),
|
|
202
|
-
)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def fastapi_require_payment(
|
|
206
|
-
amount_usd: Union[Decimal, float, str],
|
|
207
|
-
config: X402Config,
|
|
208
|
-
message: Optional[str] = None,
|
|
209
|
-
) -> Callable[[F], F]:
|
|
210
|
-
"""
|
|
211
|
-
Decorator for FastAPI routes requiring payment.
|
|
212
|
-
|
|
213
|
-
Alternative to dependency injection for simpler cases.
|
|
214
|
-
|
|
215
|
-
Args:
|
|
216
|
-
amount_usd: Required payment amount
|
|
217
|
-
config: X402Config with recipient addresses
|
|
218
|
-
message: Custom 402 message
|
|
219
|
-
|
|
220
|
-
Example:
|
|
221
|
-
>>> @app.get("/resource")
|
|
222
|
-
>>> @fastapi_require_payment(amount_usd="1.00", config=config)
|
|
223
|
-
>>> async def resource(request: Request):
|
|
224
|
-
... # Payment already verified
|
|
225
|
-
... return {"success": True}
|
|
226
|
-
"""
|
|
227
|
-
required_amount = Decimal(str(amount_usd))
|
|
228
|
-
client = X402Client(config=config)
|
|
229
|
-
|
|
230
|
-
def decorator(func: F) -> F:
|
|
231
|
-
@wraps(func)
|
|
232
|
-
async def wrapper(request: Request, *args: Any, **kwargs: Any) -> Any:
|
|
233
|
-
payment_header = request.headers.get("X-PAYMENT")
|
|
234
|
-
|
|
235
|
-
if not payment_header:
|
|
236
|
-
response_body = create_402_response(
|
|
237
|
-
amount_usd=required_amount,
|
|
238
|
-
config=config,
|
|
239
|
-
message=message,
|
|
240
|
-
)
|
|
241
|
-
return JSONResponse(
|
|
242
|
-
status_code=402,
|
|
243
|
-
content=response_body,
|
|
244
|
-
headers=create_402_headers(),
|
|
245
|
-
)
|
|
246
|
-
|
|
247
|
-
try:
|
|
248
|
-
result = client.process_payment(
|
|
249
|
-
x_payment_header=payment_header,
|
|
250
|
-
expected_amount_usd=required_amount,
|
|
251
|
-
)
|
|
252
|
-
# Store result in request state
|
|
253
|
-
request.state.payment_result = result
|
|
254
|
-
return await func(request, *args, **kwargs)
|
|
255
|
-
|
|
256
|
-
except X402Error as e:
|
|
257
|
-
return JSONResponse(
|
|
258
|
-
status_code=402,
|
|
259
|
-
content=e.to_dict(),
|
|
260
|
-
headers=create_402_headers(),
|
|
261
|
-
)
|
|
262
|
-
|
|
263
|
-
return wrapper # type: ignore
|
|
264
|
-
|
|
265
|
-
return decorator
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
class X402Middleware(BaseHTTPMiddleware):
|
|
269
|
-
"""
|
|
270
|
-
Middleware that automatically handles x402 payments for configured paths.
|
|
271
|
-
|
|
272
|
-
Example:
|
|
273
|
-
>>> from uvd_x402_sdk.integrations.fastapi_integration import X402Middleware
|
|
274
|
-
>>>
|
|
275
|
-
>>> app.add_middleware(
|
|
276
|
-
... X402Middleware,
|
|
277
|
-
... config=config,
|
|
278
|
-
... protected_paths={
|
|
279
|
-
... "/api/premium": Decimal("5.00"),
|
|
280
|
-
... "/api/basic": Decimal("1.00"),
|
|
281
|
-
... }
|
|
282
|
-
... )
|
|
283
|
-
"""
|
|
284
|
-
|
|
285
|
-
def __init__(
|
|
286
|
-
self,
|
|
287
|
-
app: Any,
|
|
288
|
-
config: X402Config,
|
|
289
|
-
protected_paths: dict[str, Decimal],
|
|
290
|
-
) -> None:
|
|
291
|
-
super().__init__(app)
|
|
292
|
-
self._config = config
|
|
293
|
-
self._client = X402Client(config=config)
|
|
294
|
-
self._protected_paths = protected_paths
|
|
295
|
-
|
|
296
|
-
async def dispatch(self, request: Request, call_next: Any) -> Any:
|
|
297
|
-
path = request.url.path
|
|
298
|
-
|
|
299
|
-
# Check if path is protected
|
|
300
|
-
if path not in self._protected_paths:
|
|
301
|
-
return await call_next(request)
|
|
302
|
-
|
|
303
|
-
required_amount = self._protected_paths[path]
|
|
304
|
-
payment_header = request.headers.get("X-PAYMENT")
|
|
305
|
-
|
|
306
|
-
if not payment_header:
|
|
307
|
-
response_body = create_402_response(
|
|
308
|
-
amount_usd=required_amount,
|
|
309
|
-
config=self._config,
|
|
310
|
-
)
|
|
311
|
-
return JSONResponse(
|
|
312
|
-
status_code=402,
|
|
313
|
-
content=response_body,
|
|
314
|
-
headers=create_402_headers(),
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
try:
|
|
318
|
-
result = self._client.process_payment(
|
|
319
|
-
x_payment_header=payment_header,
|
|
320
|
-
expected_amount_usd=required_amount,
|
|
321
|
-
)
|
|
322
|
-
request.state.payment_result = result
|
|
323
|
-
return await call_next(request)
|
|
324
|
-
|
|
325
|
-
except X402Error as e:
|
|
326
|
-
return JSONResponse(
|
|
327
|
-
status_code=402,
|
|
328
|
-
content=e.to_dict(),
|
|
329
|
-
headers=create_402_headers(),
|
|
330
|
-
)
|
|
1
|
+
"""
|
|
2
|
+
FastAPI/Starlette integration for x402 payments.
|
|
3
|
+
|
|
4
|
+
Provides:
|
|
5
|
+
- FastAPIX402: App integration class
|
|
6
|
+
- X402Depends: Dependency injection for payment verification
|
|
7
|
+
- fastapi_require_payment: Decorator for protected routes
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from decimal import Decimal
|
|
11
|
+
from functools import wraps
|
|
12
|
+
from typing import Any, Callable, Optional, TypeVar, Union
|
|
13
|
+
|
|
14
|
+
try:
|
|
15
|
+
from fastapi import FastAPI, Request, HTTPException, Depends
|
|
16
|
+
from fastapi.responses import JSONResponse
|
|
17
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
18
|
+
except ImportError:
|
|
19
|
+
raise ImportError(
|
|
20
|
+
"FastAPI is required for FastAPI integration. "
|
|
21
|
+
"Install with: pip install uvd-x402-sdk[fastapi]"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
from uvd_x402_sdk.client import X402Client
|
|
25
|
+
from uvd_x402_sdk.config import X402Config
|
|
26
|
+
from uvd_x402_sdk.exceptions import X402Error
|
|
27
|
+
from uvd_x402_sdk.models import PaymentResult
|
|
28
|
+
from uvd_x402_sdk.response import create_402_response, create_402_headers
|
|
29
|
+
|
|
30
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class FastAPIX402:
|
|
34
|
+
"""
|
|
35
|
+
FastAPI integration for x402 payments.
|
|
36
|
+
|
|
37
|
+
Example:
|
|
38
|
+
>>> from fastapi import FastAPI
|
|
39
|
+
>>> from uvd_x402_sdk.integrations import FastAPIX402
|
|
40
|
+
>>>
|
|
41
|
+
>>> app = FastAPI()
|
|
42
|
+
>>> x402 = FastAPIX402(app, recipient_address="0xYourWallet...")
|
|
43
|
+
>>>
|
|
44
|
+
>>> @app.get("/premium")
|
|
45
|
+
>>> async def premium(payment: PaymentResult = Depends(x402.require_payment(1.00))):
|
|
46
|
+
... return {"payer": payment.payer_address}
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
app: Optional[FastAPI] = None,
|
|
52
|
+
config: Optional[X402Config] = None,
|
|
53
|
+
recipient_address: Optional[str] = None,
|
|
54
|
+
**kwargs: Any,
|
|
55
|
+
) -> None:
|
|
56
|
+
"""
|
|
57
|
+
Initialize FastAPI x402 integration.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
app: FastAPI application (optional)
|
|
61
|
+
config: X402Config object
|
|
62
|
+
recipient_address: Default recipient for EVM chains
|
|
63
|
+
**kwargs: Additional config parameters
|
|
64
|
+
"""
|
|
65
|
+
self._config = config or X402Config(
|
|
66
|
+
recipient_evm=recipient_address or "",
|
|
67
|
+
**kwargs,
|
|
68
|
+
)
|
|
69
|
+
self._client = X402Client(config=self._config)
|
|
70
|
+
|
|
71
|
+
if app is not None:
|
|
72
|
+
self.init_app(app)
|
|
73
|
+
|
|
74
|
+
def init_app(self, app: FastAPI) -> None:
|
|
75
|
+
"""
|
|
76
|
+
Initialize with FastAPI app.
|
|
77
|
+
|
|
78
|
+
Stores client in app.state for access in routes.
|
|
79
|
+
"""
|
|
80
|
+
app.state.x402_client = self._client
|
|
81
|
+
app.state.x402_config = self._config
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def client(self) -> X402Client:
|
|
85
|
+
"""Get the x402 client."""
|
|
86
|
+
return self._client
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def config(self) -> X402Config:
|
|
90
|
+
"""Get the x402 config."""
|
|
91
|
+
return self._config
|
|
92
|
+
|
|
93
|
+
def require_payment(
|
|
94
|
+
self,
|
|
95
|
+
amount_usd: Union[Decimal, float, str],
|
|
96
|
+
message: Optional[str] = None,
|
|
97
|
+
) -> Callable[..., PaymentResult]:
|
|
98
|
+
"""
|
|
99
|
+
Create a FastAPI dependency that requires payment.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
amount_usd: Required payment amount in USD
|
|
103
|
+
message: Custom message for 402 response
|
|
104
|
+
|
|
105
|
+
Returns:
|
|
106
|
+
Dependency function that returns PaymentResult
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
>>> @app.post("/api/premium")
|
|
110
|
+
>>> async def premium(
|
|
111
|
+
... request: Request,
|
|
112
|
+
... payment: PaymentResult = Depends(x402.require_payment(5.00))
|
|
113
|
+
... ):
|
|
114
|
+
... return {"payer": payment.payer_address}
|
|
115
|
+
"""
|
|
116
|
+
required_amount = Decimal(str(amount_usd))
|
|
117
|
+
|
|
118
|
+
async def dependency(request: Request) -> PaymentResult:
|
|
119
|
+
payment_header = request.headers.get("X-PAYMENT")
|
|
120
|
+
|
|
121
|
+
if not payment_header:
|
|
122
|
+
response_body = create_402_response(
|
|
123
|
+
amount_usd=required_amount,
|
|
124
|
+
config=self._config,
|
|
125
|
+
message=message,
|
|
126
|
+
)
|
|
127
|
+
raise HTTPException(
|
|
128
|
+
status_code=402,
|
|
129
|
+
detail=response_body,
|
|
130
|
+
headers=create_402_headers(),
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
return self._client.process_payment(
|
|
135
|
+
x_payment_header=payment_header,
|
|
136
|
+
expected_amount_usd=required_amount,
|
|
137
|
+
)
|
|
138
|
+
except X402Error as e:
|
|
139
|
+
raise HTTPException(
|
|
140
|
+
status_code=402,
|
|
141
|
+
detail=e.to_dict(),
|
|
142
|
+
headers=create_402_headers(),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return dependency
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class X402Depends:
|
|
149
|
+
"""
|
|
150
|
+
Reusable FastAPI dependency for x402 payments.
|
|
151
|
+
|
|
152
|
+
This provides a cleaner syntax for dependency injection.
|
|
153
|
+
|
|
154
|
+
Example:
|
|
155
|
+
>>> x402_payment = X402Depends(
|
|
156
|
+
... config=X402Config(recipient_evm="0x..."),
|
|
157
|
+
... amount_usd=Decimal("1.00")
|
|
158
|
+
... )
|
|
159
|
+
>>>
|
|
160
|
+
>>> @app.get("/resource")
|
|
161
|
+
>>> async def resource(payment: PaymentResult = Depends(x402_payment)):
|
|
162
|
+
... return {"payer": payment.payer_address}
|
|
163
|
+
"""
|
|
164
|
+
|
|
165
|
+
def __init__(
|
|
166
|
+
self,
|
|
167
|
+
config: X402Config,
|
|
168
|
+
amount_usd: Union[Decimal, float, str],
|
|
169
|
+
message: Optional[str] = None,
|
|
170
|
+
) -> None:
|
|
171
|
+
self._config = config
|
|
172
|
+
self._client = X402Client(config=config)
|
|
173
|
+
self._amount = Decimal(str(amount_usd))
|
|
174
|
+
self._message = message
|
|
175
|
+
|
|
176
|
+
async def __call__(self, request: Request) -> PaymentResult:
|
|
177
|
+
"""Process payment when used as dependency."""
|
|
178
|
+
payment_header = request.headers.get("X-PAYMENT")
|
|
179
|
+
|
|
180
|
+
if not payment_header:
|
|
181
|
+
response_body = create_402_response(
|
|
182
|
+
amount_usd=self._amount,
|
|
183
|
+
config=self._config,
|
|
184
|
+
message=self._message,
|
|
185
|
+
)
|
|
186
|
+
raise HTTPException(
|
|
187
|
+
status_code=402,
|
|
188
|
+
detail=response_body,
|
|
189
|
+
headers=create_402_headers(),
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
return self._client.process_payment(
|
|
194
|
+
x_payment_header=payment_header,
|
|
195
|
+
expected_amount_usd=self._amount,
|
|
196
|
+
)
|
|
197
|
+
except X402Error as e:
|
|
198
|
+
raise HTTPException(
|
|
199
|
+
status_code=402,
|
|
200
|
+
detail=e.to_dict(),
|
|
201
|
+
headers=create_402_headers(),
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def fastapi_require_payment(
|
|
206
|
+
amount_usd: Union[Decimal, float, str],
|
|
207
|
+
config: X402Config,
|
|
208
|
+
message: Optional[str] = None,
|
|
209
|
+
) -> Callable[[F], F]:
|
|
210
|
+
"""
|
|
211
|
+
Decorator for FastAPI routes requiring payment.
|
|
212
|
+
|
|
213
|
+
Alternative to dependency injection for simpler cases.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
amount_usd: Required payment amount
|
|
217
|
+
config: X402Config with recipient addresses
|
|
218
|
+
message: Custom 402 message
|
|
219
|
+
|
|
220
|
+
Example:
|
|
221
|
+
>>> @app.get("/resource")
|
|
222
|
+
>>> @fastapi_require_payment(amount_usd="1.00", config=config)
|
|
223
|
+
>>> async def resource(request: Request):
|
|
224
|
+
... # Payment already verified
|
|
225
|
+
... return {"success": True}
|
|
226
|
+
"""
|
|
227
|
+
required_amount = Decimal(str(amount_usd))
|
|
228
|
+
client = X402Client(config=config)
|
|
229
|
+
|
|
230
|
+
def decorator(func: F) -> F:
|
|
231
|
+
@wraps(func)
|
|
232
|
+
async def wrapper(request: Request, *args: Any, **kwargs: Any) -> Any:
|
|
233
|
+
payment_header = request.headers.get("X-PAYMENT")
|
|
234
|
+
|
|
235
|
+
if not payment_header:
|
|
236
|
+
response_body = create_402_response(
|
|
237
|
+
amount_usd=required_amount,
|
|
238
|
+
config=config,
|
|
239
|
+
message=message,
|
|
240
|
+
)
|
|
241
|
+
return JSONResponse(
|
|
242
|
+
status_code=402,
|
|
243
|
+
content=response_body,
|
|
244
|
+
headers=create_402_headers(),
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
result = client.process_payment(
|
|
249
|
+
x_payment_header=payment_header,
|
|
250
|
+
expected_amount_usd=required_amount,
|
|
251
|
+
)
|
|
252
|
+
# Store result in request state
|
|
253
|
+
request.state.payment_result = result
|
|
254
|
+
return await func(request, *args, **kwargs)
|
|
255
|
+
|
|
256
|
+
except X402Error as e:
|
|
257
|
+
return JSONResponse(
|
|
258
|
+
status_code=402,
|
|
259
|
+
content=e.to_dict(),
|
|
260
|
+
headers=create_402_headers(),
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
return wrapper # type: ignore
|
|
264
|
+
|
|
265
|
+
return decorator
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class X402Middleware(BaseHTTPMiddleware):
|
|
269
|
+
"""
|
|
270
|
+
Middleware that automatically handles x402 payments for configured paths.
|
|
271
|
+
|
|
272
|
+
Example:
|
|
273
|
+
>>> from uvd_x402_sdk.integrations.fastapi_integration import X402Middleware
|
|
274
|
+
>>>
|
|
275
|
+
>>> app.add_middleware(
|
|
276
|
+
... X402Middleware,
|
|
277
|
+
... config=config,
|
|
278
|
+
... protected_paths={
|
|
279
|
+
... "/api/premium": Decimal("5.00"),
|
|
280
|
+
... "/api/basic": Decimal("1.00"),
|
|
281
|
+
... }
|
|
282
|
+
... )
|
|
283
|
+
"""
|
|
284
|
+
|
|
285
|
+
def __init__(
|
|
286
|
+
self,
|
|
287
|
+
app: Any,
|
|
288
|
+
config: X402Config,
|
|
289
|
+
protected_paths: dict[str, Decimal],
|
|
290
|
+
) -> None:
|
|
291
|
+
super().__init__(app)
|
|
292
|
+
self._config = config
|
|
293
|
+
self._client = X402Client(config=config)
|
|
294
|
+
self._protected_paths = protected_paths
|
|
295
|
+
|
|
296
|
+
async def dispatch(self, request: Request, call_next: Any) -> Any:
|
|
297
|
+
path = request.url.path
|
|
298
|
+
|
|
299
|
+
# Check if path is protected
|
|
300
|
+
if path not in self._protected_paths:
|
|
301
|
+
return await call_next(request)
|
|
302
|
+
|
|
303
|
+
required_amount = self._protected_paths[path]
|
|
304
|
+
payment_header = request.headers.get("X-PAYMENT")
|
|
305
|
+
|
|
306
|
+
if not payment_header:
|
|
307
|
+
response_body = create_402_response(
|
|
308
|
+
amount_usd=required_amount,
|
|
309
|
+
config=self._config,
|
|
310
|
+
)
|
|
311
|
+
return JSONResponse(
|
|
312
|
+
status_code=402,
|
|
313
|
+
content=response_body,
|
|
314
|
+
headers=create_402_headers(),
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
result = self._client.process_payment(
|
|
319
|
+
x_payment_header=payment_header,
|
|
320
|
+
expected_amount_usd=required_amount,
|
|
321
|
+
)
|
|
322
|
+
request.state.payment_result = result
|
|
323
|
+
return await call_next(request)
|
|
324
|
+
|
|
325
|
+
except X402Error as e:
|
|
326
|
+
return JSONResponse(
|
|
327
|
+
status_code=402,
|
|
328
|
+
content=e.to_dict(),
|
|
329
|
+
headers=create_402_headers(),
|
|
330
|
+
)
|