Skip to content

Tosh-Labs/blockchain0x-x402-python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

blockchain0x-x402 (Python)

License: Apache-2.0 Python ≥ 3.9

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.1a0 ships the wire primitives + the X402Client 402-aware fetch wrapper + ASGI middleware (Starlette / FastAPI / Quart) + a Flask before_request hook.

Why a separate package

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.

Install

pip install blockchain0x-x402

For the payer path (X402Client) you also need the main SDK:

pip install blockchain0x blockchain0x-x402

For the recipient/verifier path you only need blockchain0x-x402.

Verify an inbound x402 payment (recipient)

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 with header.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.

Issue x402-aware HTTP calls (payer)

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:

  1. Parses the 402 body and picks the requirement matching the SDK's network.
  2. Calls sdk.payments.create(...) to settle on-chain. The SDK auto-attaches an Idempotency-Key so a flaky retry does not double-spend.
  3. Polls sdk.transactions.get(payment_id) every 1s for up to 30s until the transaction confirms.
  4. Rebuilds the request with the X-Payment header 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 no accepts entry 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 to failed status before confirming.

Expose a paid HTTP route (recipient, server-side)

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.

Starlette / FastAPI / any ASGI app

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.

Flask

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.

Wire-format cross-compatibility

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.

License

Apache-2.0.

About

Official Python port of @blockchain0x/x402 - HTTP 402 wire primitives + client + ASGI/Flask server adapters. USDC on Base.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages