-
Notifications
You must be signed in to change notification settings - Fork 1k
feat: New Python SDK with v2 support #841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
🟡 Heimdall Review Status
|
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Awesome, thanks a lot for putting this out @CarsonRoscoe! To everyone interested in contributing, please FIRST write a comment here with what you'd like to work on in order to avoid any duplicate work. I will coordinate and update the TODO below with who is working on what. To simplify review, please open one PR per TODO item and try to keep changes strictly related to that to avoid merge conflicts. TODO:
|
Awesome, thanks a lot for putting this out @CarsonRoscoe! To everyone interested in contributing, please FIRST write a comment here with what you'd like to work on in order to avoid any duplicate work. I will coordinate and update the TODO below with who is working on what. To simplify review, please open one PR per TODO item and try to keep changes strictly related to that to avoid merge conflicts. TODO: -[ ] Add Solana mechanism I will work on Python facilitator example and Python client examples (httpx & requests) Please let me know if you are okay with it @CarsonRoscoe |
|
Hi @1bcMax, thanks a lot for for offering your help! Let's start with one python client example (httpx) and then go from there. Updated the TODO list accordingly |
|
Happy to review the code and examples for security issues, including both manual checks and static analysis. Please let me know if you’d like me to take a look! |
Thanks @phdargen I'd love to contribute the examples. TODO: |
Add link to in-progress Python v2 SDK work (coinbase/x402#841) in Python documentation files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* refactor: reorganize directory structure to language-first pattern Reorganize examples/ and docs/korean/ directories to match the official x402 repository structure (language/version/type). Changes: - examples/v1/ → examples/python/v1/ - examples/v2/ → examples/python/v2/ - docs/korean/v1/ → docs/korean/python/v1/ - docs/korean/v2/ → docs/korean/python/v2/ - Remove python- prefix from example doc filenames - Update all README path references - Update CLAUDE.md directory structure section 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: convert external paths to official GitHub links - Replace local external/x402/ paths with GitHub repository links - Updated files: - examples/README.md, README.en.md - examples/python/v2/README.md, README.en.md - docs/korean/python/v1/README.md, README.en.md - docs/korean/python/v2/README.md, README.en.md - docs/korean/getting_started.ko.md - docs/korean/python/v1/examples/*.ko.md (4 files) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: docs language-first structure with TypeScript versioning - Move docs/korean/ to docs/ (language-agnostic root) - Add TypeScript v1/v2 versioning structure - Create TypeScript index README and v1 placeholder - Convert external paths to GitHub links in TypeScript docs - Fix Python v1 internal links (remove python- prefix) - Fix Python v1 examples relative paths for new structure - Add TypeScript documentation links to docs/README.md Directory structure: docs/ ├── python/v1/examples/ (Python v1 Legacy) ├── python/v2/ (Python v2) ├── typescript/v1/ (placeholder) ├── typescript/v2/ (TypeScript v2 examples) └── getting_started.ko.md, x402-v2-specification.ko.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: update broken links in getting_started.ko.md after restructure - Update Python example paths: ./v1/examples/python-* → ./python/v1/examples/* - Fix resources relative path: ../../resources/ → ../resources/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: add footer navigation to TypeScript v2 docs and update README structure - Add footer navigation to all TypeScript v2 example README files - Update main README directory structure diagram to reflect current layout - Fix broken links in README.md and README.en.md (docs/korean/ → docs/) - Update example links to new language-first structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: resolve 4-persona review issues (Phase 1-3) Critical fixes: - Fix 4 broken links in x402-v2-specification.ko.md (wrong path + filename) - Add TypeScript v2 Quick Start section to root README High priority fixes: - Create docs/python/README.md and README.en.md - Create docs/typescript/v2/fullstack/README.md and README.en.md - Fix docs/README.md relative path (../../README.md → ../README.md) - Fix korean-community broken link in getting_started.ko.md Medium priority fixes: - Fix Python v1 examples v2-spec links (4 files) - Add English footer navigation to TypeScript v2 docs (15 files) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: resolve broken links from final review - Fix LICENSE links in x402-v2-specification.ko.md and getting_started.ko.md (../../LICENSE → ../LICENSE) - Fix v2-spec links in python/v2/README.md and README.en.md (../x402-v2-specification.ko.md → ../../x402-v2-specification.ko.md) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: restructure Python v1 docs from examples/ to feature-based dirs - Move examples/*.ko.md to clients/, servers/, discovery/ directories - Rename all files to README.md for consistency - Update all internal links across 14 files to match new structure - Fix broken links in root READMEs, docs/, and external/ directories New structure: docs/python/v1/ ├── clients/requests/README.md ├── clients/httpx/README.md ├── servers/fastapi/README.md └── discovery/README.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: add language toggle linking to official x402 examples Add language toggle at the top of Python v1 docs with English links pointing to official coinbase/x402 repository examples instead of maintaining separate English translations. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: update repository structure to reflect Python v1 reorganization Update docs/python/v1/ structure in both README files to show new feature-based directory layout (clients/, servers/, discovery/). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: reorganize external/ README with language versions - Update README.md as Korean default - Add README.en.md for English version - Remove redundant README.ko.md 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: simplify docs paths and rename getting_started - Remove 'korean' from docs path (docs/korean/ → docs/) - Rename getting_started.ko.md to getting_started.md - Update all references across 14 files 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add official code links to TypeScript examples table Add links to official coinbase/x402 repository for all TypeScript v2 examples (axios, fetch, express, hono, next, miniapp, mcp). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor: rename x402-v2-specification.ko.md to .md Remove '.ko' suffix from v2 specification filename and update all references across 15 files for consistency. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: update v1/examples/ references to new v1/ structure Update paths across 8 files to reflect the new directory structure: - docs/python/v1/examples/ → docs/python/v1/ (clients/, servers/, discovery/) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add English README.en.md for Python v1 examples - Create English versions for 4 Python v1 docs: - clients/requests/README.en.md - clients/httpx/README.en.md - servers/fastapi/README.en.md - discovery/README.en.md - Update Korean READMEs with correct language toggle links - Fix footer navigation paths (../ → ../../ for nested dirs) - Fix v2-spec relative paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add Python v2 SDK PR #841 reference Add link to in-progress Python v2 SDK work (coinbase/x402#841) in Python documentation files. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
|
Hi guys, I just opened a PR. It's just a specific example for the CDP facilitator on Mainnet to serve as a workaround until cdp-sdk is updated. #874 |
@phdargen Since I had finished "Add basic Python client examples (httpx & requests)"[857], I will finish "Add advanced/custom Python client examples (httpx or requests)" later today. |
|
Quick update on my TODO item: Async support for dynamic route config — PR #900 has been updated with the dual class design ( |
* 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
* add facilitator example * improve svm signer * add description to DiscoveredResource * add facilitator e2e
| return { | ||
| "success": False, | ||
| "errorReason": str(e).replace("Settlement aborted: ", ""), | ||
| "network": request.paymentPayload.get("accepted", {}).get( | ||
| "network", "unknown" | ||
| ), | ||
| "transaction": "", | ||
| "payer": None, | ||
| } |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Stack trace information
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 days ago
General fix: avoid returning raw exception messages to the client. Log the detailed error (including stack trace) on the server, and send back a generic, user-safe message. When you do want to expose a reason, derive it from a controlled source (e.g., well-defined error codes/messages), not from str(e).
Best concrete fix here:
- In the
/settlehandler, keep logging the full exception for diagnostics, but:- For the "aborted" hook path, do not use
str(e)directly. Return a generic error message like"Settlement aborted"(or a controlled message), optionally stripping the prefix only if you are sure it’s safe. Given the CodeQL warning, the simplest safe approach is to avoid using the exception text entirely in the response. - For the generic error path (
raise HTTPException(...)), replacedetail=str(e)with a generic message like"Internal server error during settlement".
- For the "aborted" hook path, do not use
- To improve server-side debugging, we can add stack-trace logging via
traceback.format_exc()in theexceptblock. - All changes are confined to
e2e/facilitators/python/main.pyinside the shown code region. We can add a standard-library import (traceback) at the top of the file.
Concretely:
- Add
import tracebackalongside the other imports. - In the
except Exception as e:insettle:- Replace
print(f"Settle error: {e}")with something likeprint(f"Settle error: {e}\n{traceback.format_exc()}")to log more detail but keep it server-side. - In the "aborted" branch, stop passing through
str(e); instead, use a generic, controlled message (e.g.,"Settlement aborted"). - In the final
HTTPException, replacedetail=str(e)with a generic message (e.g.,"Internal server error during settlement").
- Replace
- The
/supportedendpoint is already only sending a generic HTTP 500 message, but it currently usesdetail=str(e)as well; however, CodeQL’s highlighted flow is about the/settleresponse body. To keep the fix focused on the reported issue, we will not alter/supportedunless required.
-
Copy modified line R24 -
Copy modified lines R227-R228 -
Copy modified lines R231-R233 -
Copy modified line R236 -
Copy modified lines R240-R244
| @@ -21,6 +21,7 @@ | ||
| from fastapi import FastAPI, HTTPException | ||
| from pydantic import BaseModel | ||
| from solders.keypair import Keypair | ||
| import traceback | ||
|
|
||
| from x402 import x402Facilitator | ||
| from x402.extensions.bazaar import DiscoveredResource, extract_discovery_info | ||
| @@ -223,14 +224,16 @@ | ||
| "errorReason": response.error_reason, | ||
| } | ||
| except Exception as e: | ||
| print(f"Settle error: {e}") | ||
| # Log full error details server-side, including stack trace | ||
| print(f"Settle error: {e}\n{traceback.format_exc()}") | ||
|
|
||
| # Check if this was an abort from hook | ||
| if "aborted" in str(e).lower() or "Settlement aborted" in str(e): | ||
| # Return a proper SettleResponse instead of 500 error | ||
| if "aborted" in str(e).lower() or "settlement aborted" in str(e).lower(): | ||
| # Return a proper SettleResponse instead of 500 error, | ||
| # but avoid exposing internal exception details. | ||
| return { | ||
| "success": False, | ||
| "errorReason": str(e).replace("Settlement aborted: ", ""), | ||
| "errorReason": "Settlement aborted", | ||
| "network": request.paymentPayload.get("accepted", {}).get( | ||
| "network", "unknown" | ||
| ), | ||
| @@ -238,7 +237,11 @@ | ||
| "payer": None, | ||
| } | ||
|
|
||
| raise HTTPException(status_code=500, detail=str(e)) | ||
| # For all other errors, return a generic internal error message | ||
| raise HTTPException( | ||
| status_code=500, | ||
| detail="Internal server error during settlement", | ||
| ) | ||
|
|
||
|
|
||
| @app.get("/supported") |
| return { | ||
| "success": False, | ||
| "errorReason": str(e), | ||
| "network": request.paymentPayload.get("accepted", {}).get( | ||
| "network", "unknown" | ||
| ), | ||
| "transaction": "", | ||
| } |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Stack trace information
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 days ago
In general, to fix information exposure through exceptions, avoid returning raw exception messages or stack traces to clients. Instead, log the full exception server-side (optionally with stack trace) and return a generic, user-safe error message. For business-logic errors where the client needs specific feedback, define controlled, sanitized messages or error codes rather than using arbitrary exception text.
For this file, the best minimal fix without changing functionality structure is:
- In the
settleendpoint:- Keep logging the error server-side.
- For the “aborted” case, return a response with
success: Falseand the samenetwork/transactionfields, but replaceerrorReason: str(e)with a generic or at least non-technical message such as"Payment aborted"or"Payment aborted due to a client-side condition.". - For non-aborted errors, keep raising
HTTPException(status_code=500, ...)but changedetail=str(e)to a generic string such as"Internal server error"so exception details are not leaked.
- In the
supportedendpoint:- Keep logging the error.
- Change
HTTPException(status_code=500, detail=str(e))to use a generic message like"Internal server error".
No new imports are strictly necessary beyond what is already present; we can continue to use print for minimal logging as in the existing code. If this project later adopts a logging framework, these print calls can be replaced, but we won’t assume that here.
Concretely:
- Edit the
exceptblock insettle(lines 185–197) to:- Use a generic
errorReasonstring in the aborted branch. - Use a generic
detailinHTTPException.
- Use a generic
- Edit the
exceptblock insupported(lines 223–225) to use a genericdetail.
-
Copy modified line R192 -
Copy modified line R197 -
Copy modified line R221
| @@ -189,12 +189,12 @@ | ||
| if "aborted" in str(e).lower(): | ||
| return { | ||
| "success": False, | ||
| "errorReason": str(e), | ||
| "errorReason": "Payment aborted.", | ||
| "network": request.paymentPayload.get("accepted", {}).get("network", "unknown"), | ||
| "transaction": "", | ||
| } | ||
|
|
||
| raise HTTPException(status_code=500, detail=str(e)) | ||
| raise HTTPException(status_code=500, detail="Internal server error") | ||
|
|
||
|
|
||
| @app.get("/supported") | ||
| @@ -222,7 +218,7 @@ | ||
| } | ||
| except Exception as e: | ||
| print(f"Supported error: {e}") | ||
| raise HTTPException(status_code=500, detail=str(e)) | ||
| raise HTTPException(status_code=500, detail="Internal server error") | ||
|
|
||
|
|
||
| @app.get("/health") |
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(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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> * 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 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
* 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: 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
* 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
| content={ | ||
| "error": "Payment Processing Error", | ||
| "message": str(e), | ||
| }, |
Check warning
Code scanning / CodeQL
Information exposure through an exception Medium
Stack trace information
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 2 days ago
In general, the fix is to avoid returning raw exception details to the client. Instead, log the exception on the server (ideally with stack trace) and return a generic, non-sensitive error message in the HTTP response body.
For this specific code, the best minimal fix without changing existing functionality is:
- Keep catching
Exceptionto avoid breaking behavior. - Add server-side logging of the exception (including stack trace), using the standard library
loggingmodule. - Replace the
"message": str(e)field in the JSON response with a generic, stable string that does not depend on the exception content (for example,"An internal error occurred while processing payment."). - Optionally, keep the existing
"error": "Payment Processing Error"field for clients that expect it.
Concretely:
-
At the top of
examples/python/servers/custom/main.py, addimport loggingand configure a module-level logger (e.g.,logger = logging.getLogger(__name__)). If you want more explicit logs without altering global configuration, you can rely on default logging setup; this change does not break existing behavior. -
In the
except Exception as e:block starting at line 233, replace theprint(f"❌ Payment processing error: {e}")call with a logging call likelogger.exception("Payment processing error"), which logs the message and stack trace. -
In the same block, change the JSONResponse content to not include
str(e). For example:
content={
"error": "Payment Processing Error",
"message": "An internal error occurred while processing payment.",
}This preserves the external API shape (error and message fields) while eliminating exposure of exception details.
-
Copy modified line R26 -
Copy modified lines R36-R37 -
Copy modified line R237 -
Copy modified line R242
| @@ -23,6 +23,7 @@ | ||
| import sys | ||
| from dataclasses import dataclass, field | ||
|
|
||
| import logging | ||
| from dotenv import load_dotenv | ||
| from fastapi import FastAPI, Request, Response | ||
| from fastapi.responses import JSONResponse | ||
| @@ -32,6 +33,8 @@ | ||
| from x402.schemas import Network, PaymentPayload, PaymentRequirements, ResourceConfig | ||
| from x402.server import x402ResourceServer | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| load_dotenv() | ||
|
|
||
| # Config | ||
| @@ -231,12 +234,12 @@ | ||
| return response | ||
|
|
||
| except Exception as e: | ||
| print(f"❌ Payment processing error: {e}") | ||
| logger.exception("Payment processing error") | ||
| return JSONResponse( | ||
| status_code=500, | ||
| content={ | ||
| "error": "Payment Processing Error", | ||
| "message": str(e), | ||
| "message": "An internal error occurred while processing payment.", | ||
| }, | ||
| ) | ||
|
|
Description
/python/legacy/python/x402TODO for Typescript/Go Parity:
Tests
Checklist