Official Python port of @blockchain0x/x402.
Ships the wire primitives + a 402-aware fetch wrapper. Sibling
package to blockchain0x; install only when your service either
issues x402-aware HTTP calls (a payer) or verifies inbound x402
payments (a recipient).
Pre-release:
0.0.1a0ships the wire primitives + theX402Client402-aware fetch wrapper + ASGI middleware (Starlette / FastAPI / Quart) + a Flaskbefore_requesthook.
x402 is a separable concern from the rest of the Blockchain0x SDK:
- A consumer who only verifies inbound webhooks does not need the x402 client stack.
- A service that exposes paid HTTP routes does not need the webhook verifier bundled with it.
Keeping them as two pip-installable packages lets each downstream service depend only on what it actually uses.
pip install blockchain0x-x402For the payer path (X402Client) you also need the main SDK:
pip install blockchain0x blockchain0x-x402For the recipient/verifier path you only need blockchain0x-x402.
from blockchain0x_x402 import parse_payment_header, X402WireError
def verify(request):
raw = request.headers.get("X-Payment")
try:
payment = parse_payment_header(raw)
except X402WireError as e:
return JSONResponse({"code": e.code}, status_code=400)
# payment.payment_request_id, payment.tx_hash, payment.network
# ...The verifier:
- Accepts only
exact-usdc:<base64>scheme; anything else rejects withheader.unknown_scheme. - Validates txHash, payerAddress, amountUsdc, and network shape; any
drift rejects with
header.payload_malformed. - Lowercases hex fields so downstream comparisons against on-chain transaction logs are deterministic.
import os
from blockchain0x import Client
from blockchain0x_x402 import X402Client
sdk = Client(api_key=os.environ["BLOCKCHAIN0X_API_KEY"])
x402 = X402Client(sdk=sdk, agent_id="agt_...")
response = x402.fetch("https://service-b.com/llm-query", method="POST")
response.raise_for_status()The wrapper handles a 402 response transparently:
- Parses the 402 body and picks the requirement matching the SDK's network.
- Calls
sdk.payments.create(...)to settle on-chain. The SDK auto-attaches anIdempotency-Keyso a flaky retry does not double-spend. - Polls
sdk.transactions.get(payment_id)every 1s for up to 30s until the transaction confirms. - Rebuilds the request with the
X-Paymentheader and re-issues it once. The second 200 response is returned to the caller.
Failures surface as X402ClientError with stable codes:
no_matching_requirement- the 402 had noacceptsentry for the SDK's network mode.settlement_timeout- the on-chain payment did not confirm within the poll window.chain_failed- the payment row flipped tofailedstatus before confirming.
Two ready-made adapters wrap the verifier into a framework-level gate so paid routes do not have to repeat the parse + settle dance themselves.
from starlette.applications import Starlette
from blockchain0x import Client
from blockchain0x_x402.server import X402Middleware, PricingEntry
sdk = Client(api_key=os.environ["BLOCKCHAIN0X_API_KEY"])
app = Starlette(routes=[...])
app.add_middleware(
X402Middleware,
sdk=sdk,
pricing={
"POST /llm-query": PricingEntry(
amount_usdc="0.10",
pay_to_address="0xabc...",
payment_request_id="pr_demo",
),
},
)A miss in the pricing table is a no-op (the route is free). A hit
with no valid X-Payment short-circuits the response with HTTP 402
and the canonical accepts[] body the payer needs. A hit with a
valid payment attaches the parsed payload to scope["x402_payment"]
for downstream handlers and lets the request proceed.
from flask import Flask, g
from blockchain0x import Client
from blockchain0x_x402.server import x402_before_request_factory, PricingEntry
app = Flask(__name__)
app.before_request(x402_before_request_factory(
sdk=Client(api_key=os.environ["BLOCKCHAIN0X_API_KEY"]),
pricing={
"POST /llm-query": PricingEntry(
amount_usdc="0.10",
pay_to_address="0xabc...",
payment_request_id="pr_demo",
),
},
))
@app.post("/llm-query")
def handler():
# g.x402_payment is the verified ExactUsdcPayment payload.
...Both adapters call sdk.payment_requests.settle() once per request
to anchor trust; the backend's settle route is the chain-of-custody
boundary. The settle() impl may be sync OR async - the adapter
awaits whichever shape it returns.
The Python build_payment_header output is byte-for-byte identical
to the Node SDK's. A Python payer can pay a Node server, a Node
payer can pay a Python server - the canonical JSON shape is:
{
"scheme": "exact-usdc",
"version": 1,
"paymentRequestId": "...",
"txHash": "...",
"payerAddress": "...",
"amountUsdc": "...",
"network": "..."
}Keys are insertion-ordered; no whitespace between separators. The
hex fields (txHash, payerAddress) are lowercased before encoding.
Apache-2.0.