Skip to content

Commit 30a847d

Browse files
CarsonRoscoephdargensohw400Ilevk1bcMax
authored
feat: New Python SDK with v2 support (#841)
* chore: renamed python/x402 to python/legacy * feat: initial Python core SDK * feat: added core HTTP wrappers * feat: added core types * feat: added evm mechanism for exact v1/v2 * feat: added httpx client * feat: added requests client * feat: added fastapi middleware * feat: added flask middleware * feat: added integration tests * feat: added Python SDK to e2e tests * python-v2: fix workflow checks (#863) * fix format * fix lint * fix build * fix pytest * Fix syntax errors and missing imports in manual integration example (#902) - Add missing imports (base64, json, Response, JSONResponse, PaymentPayload, FacilitatorClient) - Fix function signature syntax error - Fix dict initialization syntax (colon to equals) - Add missing return statement after successful payment - Remove trailing whitespace * feat: add Python SDK v2 server examples (FastAPI, Flask) (#879) * feat: add fastapi example project configuration * feat: add fastapi example with x402 payment middleware * docs: add comprehensive README for fastapi example * feat: add flask example project configuration * feat: add flask example with x402 payment middleware * docs: add comprehensive README for flask example * feat: add test clients for fastapi and flask examples * docs: add test client usage to READMEs * chore - remove python-version and test-client.py * docs - remove test client guide from README.md * chore - remove license and classifier * feat - add description and mime type * feat - update .env-local * feat: add Python v2 client examples (httpx & requests) (#857) * feat: add Python v2 client examples (httpx & requests) Add simple client examples demonstrating httpx (async) and requests (sync) usage with the x402 Python v2 SDK. - examples/python/clients/example.py - Simple example showing both httpx and requests - Uses EthAccountSigner with register_exact_evm_client helper - Minimal configuration required * refactor: split Python client examples into separate directories Address PR review feedback: - Separate httpx/ and requests/ directories - Add pyproject.toml for uv sync support - Add environment variable validation - Add payment response header extraction - Use context manager for requests session cleanup Tested on Base Sepolia testnet: - httpx: tx 0xce49f668aba01c4b0b1af2eb0d9a381f4c62295506a91f20af479e0e834ef350 - requests: tx 0xb83b6feefa5a744b7779ba5b37df33f9c7241f58119454d66102acd97a92f024 * Update examples/python/clients/httpx/main.py Co-authored-by: phdargen <[email protected]> * Update examples/python/clients/requests/main.py Co-authored-by: phdargen <[email protected]> --------- Co-authored-by: phdargen <[email protected]> * feat(python): Make v2 framework and client dependencies optional extras (#901) * python-v2: add svm mechanism (#904) * add svm mechanism * add unit tests * add integration tests * update server examples * add e2e tests * update clients * make EVM/SVM dependencies optional extras * fix legacy uv paths * V2 python bazaar (#929) * add bazaar extension * strip query params for discovery * fix 862 * updated e2e tests * Add advanced Python client examples (#947) * Add advanced Python client examples Implements 5 advanced patterns demonstrating x402 SDK v2 features: - hooks.py: Payment lifecycle hooks (before, after, failure) - preferred_network.py: Custom network preference selector - builder_pattern.py: Network-specific registration with wildcards - error_recovery.py: Error classification and recovery strategies - custom_transport.py: Custom httpx transport with retry/timing Includes: - CLI entry point (index.py) for running examples - Comprehensive test suite (40 unit tests) - E2E integration tests - README documentation All tests passing. * Remove tests and examples beyond TypeScript SDK parity Address PR review feedback: - Remove tests directory (tests belong in SDK, not examples) - Remove custom_transport.py (runtime error, beyond TS parity) - Remove error_recovery.py (duplicate of hooks.py, beyond TS parity) - Update index.py, README.md, pyproject.toml accordingly * Address PR feedback: add SVM support and use optional deps - Update pyproject.toml to use x402[evm,svm,httpx] instead of listing dependencies separately - Add Solana/SVM network examples to preferred_network.py - Support both EVM and SVM signers for cross-chain flexibility - Update .env-local with SOLANA_PRIVATE_KEY option * Python-v2: facilitator example + e2e (#950) * add facilitator example * improve svm signer * add description to DiscoveredResource * add facilitator e2e * add paywall builder (#953) * Add Python custom client example (#954) Demonstrates manual x402 v2 payment flow without convenience wrappers. Shows the 6-step process: 1. Initialize client and register payment schemes 2. Make initial request, receive 402 3. Decode PAYMENT-REQUIRED header 4. Create signed payment payload 5. Retry with PAYMENT-SIGNATURE header 6. Handle success/failure response Useful for integrating with non-standard HTTP libraries or implementing custom retry logic. * feat(v2-sdk): support async dynamic hooks for price and metadata (#900) * feat(http): implement dual class design for sync/async support - Add x402HTTPResourceServer (async) for FastAPI/Starlette - Add x402HTTPResourceServerSync (sync) for Flask/Django - Share common logic via _x402HTTPServerBase - Remove dynamic description/resource (keep only dynamic price/payTo) - Update Flask middleware to use sync server directly - Integrate with enrich_extensions for bazaar support - Add per-route hook_timeout_seconds configuration Co-Authored-By: Claude Opus 4.5 <[email protected]> * fix(types): add missing Awaitable import and fix test import sorting - Add Awaitable import to types.py for DynamicPayTo/DynamicPrice type hints - Fix import block sorting in test_async_hooks.py and test_core.py Co-Authored-By: Claude Opus 4.5 <[email protected]> * refactor: simplify API by removing unused types and making timeout opt-in Address reviewer feedback from @phdargen: - Remove SyncDynamicPayTo/SyncDynamicPrice (unused types that would require SyncPaymentOption to be useful) - Change hook_timeout_seconds default to None (opt-in, matching TS/Go parity) - Export x402HTTPResourceServerSync from http/__init__.py Co-Authored-By: Claude Opus 4.5 <[email protected]> --------- Co-authored-by: Claude Opus 4.5 <[email protected]> * python-v2: make create_facilitator_config work with both V1 and V2 (#968) * make create_facilitator_config work with both V1 and V2 * fix lint * feat: add Python SDK v2 server advanced example (FastAPI) (#952) * feat - add pyproject.toml, uv.lock for advanced example * feat - add bazaar extension * feat - add hooks * feat - add x402 extensions to pyproject.toml * feat - add dynamic pricing feature * feat - add dynamic pay-to and custom money parser features * docs - add README for advanced server example * style - apply lint * chore - add comments for bazaar extension code * fix: python v2 x402HTTPAdapter retry logic (#978) * fix: fix consecutive payment bug in x402HTTPAdapter - Replace instance-level _is_retry flag with per-request header to prevent state leakage between consecutive 402 requests. * tests - add test case for consecutive payment with x402HTTPAdapter * style - apply lint * Python-v2: async lifecycle hooks (#975) * add dual sync/async classes for server/client/facilitator * update integration/e2e tests * add unit tests for core, http and implement wrapHttpxWithPaymentFromConfig/wrapRequestsWithPayment * refactor core server/client/facilitator * refactor x402_http_server/facilitator_client/x402_http_client * update exmamples * add utility function that takes care of html-safe escaping * add sdk readmes * add retry unit tests to httpx client * Update Python SDK Docs for v2 (#976) * Porting docs and updating for v2 changes (#813) * porting docs and updating for v2 changes * update quickstart for sellers * update quickstarts, miniapps, and mcp docs * update doc links * update FAQs * fix getting started docs * update core concepts * remove miniapps guide * update extensions * fix bazaar code examples * fix broken links and yaml file * remove unnecessary protocol layer doc * add migration guide * update documentation for python v2 sdk * testing pr preview * docs(python): remove unused os imports and align with TS tab patterns - Remove unused 'import os' from FastAPI and Flask examples in quickstart-for-sellers - Update bazaar-discovery-layer Python example to use hardcoded '0xYourPrivateKey' to match TypeScript tab consistency * update migration doc * cleanup comments * cleanup readme * fix: Python facilitator e2e code * fix: lint --------- Co-authored-by: phdargen <[email protected]> Co-authored-by: somesome <[email protected]> Co-authored-by: Logan Kang <[email protected]> Co-authored-by: 1bcMax <[email protected]> Co-authored-by: phdargen <[email protected]> Co-authored-by: Vladimir Sotnikov <[email protected]> Co-authored-by: Shuhei <[email protected]> Co-authored-by: Claude Opus 4.5 <[email protected]> Co-authored-by: Faris Habib <[email protected]>
1 parent fb6527d commit 30a847d

File tree

311 files changed

+75794
-2213
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

311 files changed

+75794
-2213
lines changed

.github/workflows/check_python.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ jobs:
2323
run: uv sync --all-extras --dev
2424

2525
- name: Run Tests
26-
run: uv run python -m pytest
26+
run: uv run pytest
2727

2828
lint-python:
2929
runs-on: ubuntu-latest

docs/core-concepts/bazaar-discovery-layer.md

Lines changed: 96 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -89,18 +89,14 @@ Fetch the list of available x402 services using the facilitator client:
8989
import { HTTPFacilitatorClient } from "@x402/core/http";
9090
import { withBazaar } from "@x402/extensions";
9191

92-
// Create facilitator client with Bazaar discovery extension
9392
const facilitatorClient = new HTTPFacilitatorClient({
9493
url: "https://x402.org/facilitator"
9594
});
9695
const client = withBazaar(facilitatorClient);
9796

98-
// Fetch all available services
9997
const response = await client.extensions.discovery.listResources({ type: "http" });
10098

101-
// NOTE: in an MCP context, you can see the full list then decide which service to use
102-
103-
// Find services under $0.10
99+
// Filter services under $0.10
104100
const usdcAsset = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
105101
const maxPrice = 100000; // $0.10 in USDC atomic units (6 decimals)
106102

@@ -115,28 +111,26 @@ const affordableServices = response.items.filter(item =>
115111

116112
{% tab title="Python" %}
117113
```python
118-
from x402.facilitator import FacilitatorClient
119-
120-
# Set up facilitator client (defaults to https://x402.org/facilitator)
121-
facilitator = FacilitatorClient()
114+
from x402.http import FacilitatorConfig, HTTPFacilitatorClient
122115

123-
# Fetch all available services
124-
response = await facilitator.list()
116+
facilitator = HTTPFacilitatorClient(
117+
FacilitatorConfig(url="https://api.cdp.coinbase.com/platform/v2/x402")
118+
)
125119

126-
# NOTE: in an MCP context, you can see the full list then decide which service to use
120+
response = await facilitator.list_resources(type="http")
127121

128-
# Find services under $0.10
122+
# Filter services under $0.10
129123
usdc_asset = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
130124
max_price = 100000 # $0.10 in USDC atomic units (6 decimals)
131125

132126
affordable_services = [
133-
item
134-
for item in response.items
135-
if any(
136-
payment_req.asset == usdc_asset
137-
and int(payment_req.max_amount_required) < max_price
138-
for payment_req in item.accepts
139-
)
127+
item
128+
for item in response.items
129+
if any(
130+
payment_req.asset == usdc_asset
131+
and int(payment_req.max_amount_required) < max_price
132+
for payment_req in item.accepts
133+
)
140134
]
141135
```
142136
{% endtab %}
@@ -185,28 +179,40 @@ console.log("Response data:", response.data);
185179

186180
{% tab title="Python" %}
187181
```python
188-
from x402.client import X402Client
182+
import asyncio
183+
189184
from eth_account import Account
190185

191-
# Set up your payment account
192-
account = Account.from_key("0xYourPrivateKey")
193-
client = X402Client(account)
186+
from x402 import x402Client
187+
from x402.http.clients import x402HttpxClient
188+
from x402.mechanisms.evm import EthAccountSigner
189+
from x402.mechanisms.evm.exact.register import register_exact_evm_client
194190

195-
# Select a service from discovery
196-
selected_service = affordable_services[0]
197191

198-
# Select the payment method of your choice
199-
selected_payment_requirements = selected_service.accepts[0]
200-
input_schema = selected_payment_requirements.output_schema.input
192+
async def main() -> None:
193+
account = Account.from_key("0xYourPrivateKey")
194+
client = x402Client()
195+
register_exact_evm_client(client, EthAccountSigner(account))
201196

202-
# Make the request
203-
response = client.request(
204-
method=input_schema.method,
205-
url=input_schema.resource,
206-
params={"location": "San Francisco"} # Based on input_schema
207-
)
197+
# Select a service from discovery (from Step 1)
198+
selected_service = affordable_services[0]
199+
200+
# Select the payment method of your choice
201+
selected_payment_requirements = selected_service.accepts[0]
202+
input_schema = selected_payment_requirements.output_schema.input
203+
204+
# Make the request using httpx client
205+
async with x402HttpxClient(client) as http:
206+
response = await http.request(
207+
method=input_schema.method,
208+
url=input_schema.resource,
209+
params={"location": "San Francisco"} # Based on input_schema
210+
)
211+
await response.aread()
212+
print(f"Response data: {response.json()}")
208213

209-
print(f"Response data: {response}")
214+
215+
asyncio.run(main())
210216
```
211217
{% endtab %}
212218
{% endtabs %}
@@ -272,44 +278,64 @@ app.use(paymentMiddleware(routes, server));
272278

273279
{% tab title="Python" %}
274280
```python
275-
from x402.middleware.fastapi import payment_middleware
276-
from x402.facilitators import cdp_facilitator
277-
278-
app.middleware("http")(
279-
payment_middleware(
280-
routes={
281-
"/weather": {
282-
"price": "$0.001",
283-
"network": "eip155:8453",
284-
"resource": "0xYourAddress",
285-
"description": "Get current weather data for any location",
286-
"extensions": {
287-
"bazaar": {
288-
"discoverable": True,
289-
"inputSchema": {
290-
"queryParams": {
291-
"location": {
292-
"type": "string",
293-
"description": "City name or coordinates",
294-
"required": True
295-
}
296-
}
297-
},
298-
"outputSchema": {
299-
"type": "object",
300-
"properties": {
301-
"temperature": {"type": "number"},
302-
"conditions": {"type": "string"},
303-
"humidity": {"type": "number"}
304-
}
281+
from fastapi import FastAPI
282+
283+
from x402.http import FacilitatorConfig, HTTPFacilitatorClient, PaymentOption
284+
from x402.http.middleware.fastapi import PaymentMiddlewareASGI
285+
from x402.http.types import RouteConfig
286+
from x402.mechanisms.evm.exact import ExactEvmServerScheme
287+
from x402.server import x402ResourceServer
288+
289+
app = FastAPI()
290+
291+
# Create facilitator client (CDP mainnet)
292+
facilitator = HTTPFacilitatorClient(
293+
FacilitatorConfig(url="https://api.cdp.coinbase.com/platform/v2/x402")
294+
)
295+
296+
# Create resource server and register EVM scheme
297+
server = x402ResourceServer(facilitator)
298+
server.register("eip155:8453", ExactEvmServerScheme())
299+
300+
# Define routes with Bazaar discovery metadata
301+
routes: dict[str, RouteConfig] = {
302+
"GET /weather": RouteConfig(
303+
accepts=[
304+
PaymentOption(
305+
scheme="exact",
306+
pay_to="0xYourAddress",
307+
price="$0.001",
308+
network="eip155:8453",
309+
),
310+
],
311+
mime_type="application/json",
312+
description="Get current weather data for any location",
313+
extensions={
314+
"bazaar": {
315+
"discoverable": True,
316+
"inputSchema": {
317+
"queryParams": {
318+
"location": {
319+
"type": "string",
320+
"description": "City name or coordinates",
321+
"required": True,
305322
}
306323
}
307-
}
324+
},
325+
"outputSchema": {
326+
"type": "object",
327+
"properties": {
328+
"temperature": {"type": "number"},
329+
"conditions": {"type": "string"},
330+
"humidity": {"type": "number"},
331+
},
332+
},
308333
}
309334
},
310-
facilitator=cdp_facilitator
311-
)
312-
)
335+
),
336+
}
337+
338+
app.add_middleware(PaymentMiddlewareASGI, routes=routes, server=server)
313339
```
314340
{% endtab %}
315341

docs/core-concepts/network-and-token-support.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,38 @@ schemes := []ginmw.SchemeConfig{
192192
}
193193
```
194194
{% endtab %}
195+
196+
{% tab title="Python" %}
197+
```python
198+
from x402.http import FacilitatorConfig, HTTPFacilitatorClient, PaymentOption
199+
from x402.http.types import RouteConfig
200+
from x402.mechanisms.evm.exact import ExactEvmServerScheme
201+
from x402.server import x402ResourceServer
202+
203+
# Create facilitator client for your network
204+
facilitator = HTTPFacilitatorClient(
205+
FacilitatorConfig(url="https://your-facilitator.com")
206+
)
207+
208+
# Create server and register EVM scheme for your network
209+
server = x402ResourceServer(facilitator)
210+
server.register("eip155:43114", ExactEvmServerScheme()) # Avalanche mainnet
211+
212+
# Now use any CAIP-2 network identifier in your routes:
213+
routes: dict[str, RouteConfig] = {
214+
"GET /api/data": RouteConfig(
215+
accepts=[
216+
PaymentOption(
217+
scheme="exact",
218+
price="$0.001",
219+
network="eip155:43114", # Avalanche mainnet
220+
pay_to="0xYourAddress",
221+
),
222+
],
223+
),
224+
}
225+
```
226+
{% endtab %}
195227
{% endtabs %}
196228

197229
**Key Points:**

0 commit comments

Comments
 (0)