Flask server demonstrating how to protect API endpoints with a paywall using the x402 middleware.
from flask import Flask, jsonify
from x402.http.middleware.flask import payment_middleware
from x402.http import HTTPFacilitatorClientSync, FacilitatorConfig, PaymentOption
from x402.http.types import RouteConfig
from x402.server import x402ResourceServerSync
from x402.mechanisms.evm.exact import ExactEvmServerScheme
from x402.mechanisms.svm.exact import ExactSvmServerScheme
app = Flask(__name__)
# Flask is sync, so use sync variants
facilitator = HTTPFacilitatorClientSync(FacilitatorConfig(url=facilitator_url))
server = x402ResourceServerSync(facilitator)
server.register("eip155:84532", ExactEvmServerScheme())
server.register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", ExactSvmServerScheme())
routes = {
"GET /weather": RouteConfig(
accepts=[
PaymentOption(scheme="exact", price="$0.01", network="eip155:84532", pay_to=evm_address),
PaymentOption(scheme="exact", price="$0.01", network="solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", pay_to=svm_address),
]
),
}
payment_middleware(app, routes=routes, server=server)
@app.route("/weather")
def get_weather():
return jsonify({"weather": "sunny", "temperature": 70})- Python 3.10+
- uv (install via docs.astral.sh/uv)
- Valid EVM address for receiving payments (Base Sepolia)
- Valid SVM address for receiving payments (Solana Devnet)
- URL of a facilitator supporting the desired payment network, see facilitator list
- Copy
.env-localto.env:
cp .env-local .env- Fill required environment variables:
EVM_ADDRESS- Ethereum address to receive payments (Base Sepolia)SVM_ADDRESS- Solana address to receive payments (Solana Devnet)FACILITATOR_URL- Facilitator endpoint URL (optional, defaults to production)
- Install dependencies:
uv sync- Run the server:
uv run python main.pyServer runs at http://localhost:4021
| Endpoint | Payment | Price |
|---|---|---|
GET /health |
No | - |
GET /weather |
Yes | $0.01 USDC |
GET /premium/content |
Yes | $0.01 USDC |
$ curl -i http://localhost:4021/weather
HTTP/1.1 402 Payment Required
content-type: application/json
payment-required: <base64-encoded JSON>
{}
The payment-required header contains base64-encoded JSON with payment requirements.
Note: amount is in atomic units (e.g., 10000 = $0.01 USDC, since USDC has 6 decimals):
{
"x402Version": 2,
"error": "Payment required",
"resource": {
"url": "http://localhost:4021/weather"
},
"accepts": [
{
"scheme": "exact",
"network": "eip155:84532",
"asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
"amount": "10000",
"payTo": "0x...",
"maxTimeoutSeconds": 300,
"extra": {
"name": "USDC",
"version": "2"
}
}
]
}After payment is verified, the protected endpoint returns the requested data:
HTTP/1.1 200 OK
content-type: application/json
{"report":{"weather":"sunny","temperature":70}}
routes = {
"GET /your-endpoint": RouteConfig(
accepts=[
# EVM payment option
PaymentOption(
scheme="exact",
price="$0.10",
network="eip155:84532",
pay_to=EVM_ADDRESS,
),
# SVM payment option
PaymentOption(
scheme="exact",
price="$0.10",
network="solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
pay_to=SVM_ADDRESS,
),
]
),
}
@app.route("/your-endpoint")
def your_endpoint():
return jsonify({"data": "your response"})Two ways to specify price:
# String format (uses default USDC)
price="$0.01"
# AssetAmount object (explicit asset)
price=AssetAmount(
amount="10000", # $0.01 USDC (6 decimals)
asset="0x036CbD53842c5426634e7929541eC2318f3dCF7e",
extra={"name": "USDC", "version": "2"},
)Network identifiers use CAIP-2 format:
EVM Networks:
eip155:84532— Base Sepoliaeip155:8453— Base Mainnet
SVM Networks:
solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1— Solana Devnetsolana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp— Solana Mainnet