Drop-in x402 payment middleware for MCP servers — charge AI agents per tool call with USDC
Project description
payclaw
Drop-in x402 payment middleware for MCP servers. Charge AI agents per tool call using USDC on Base chain — 10 lines of code, no payment processor, no KYC.
pip install payclaw
How it works
- Agent calls your tool endpoint
- No valid payment → server returns HTTP 402 with price and wallet address
- Agent pays USDC on Base chain, gets tx hash
- Agent retries with
X-Payment: <tx_hash>header - payclaw verifies on-chain → executes your tool
Money flows directly: agent wallet → your wallet. payclaw never holds funds.
FastAPI
from fastapi import FastAPI, Request
from payclaw import require_payment, PayclawConfig
app = FastAPI()
config = PayclawConfig(
price_usdc=0.001,
wallet_address="0xYourWalletAddress",
)
@app.post("/search")
@require_payment(config)
async def search(request: Request, q: str):
return {"results": ["result1", "result2"]}
Flask
from flask import Flask, jsonify
from payclaw import require_payment, PayclawConfig
app = Flask(__name__)
config = PayclawConfig(
price_usdc=0.001,
wallet_address="0xYourWalletAddress",
)
@app.route("/search", methods=["POST"])
@require_payment(config)
def search():
return jsonify({"results": ["result1", "result2"]})
Custom framework
from payclaw import PayclawMiddleware, PayclawConfig
config = PayclawConfig(price_usdc=0.001, wallet_address="0xYourWallet")
middleware = PayclawMiddleware(config)
# In your request handler:
allowed, reason = middleware.check(dict(request.headers))
if not allowed:
status, body = middleware.payment_required(reason)
# return 402 response with body
402 Response format
{
"x402": true,
"price": "0.001",
"currency": "USDC",
"network": "base-sepolia",
"recipient": "0xYourWallet",
"chain_id": 84532,
"reason": "missing X-Payment header"
}
When rate limit is exceeded, the response is HTTP 429 with the same body format and "reason": "rate limit exceeded".
Base Mainnet
from payclaw import mainnet_config, require_payment
config = mainnet_config(
price_usdc=0.001,
wallet_address="0xYourWallet",
)
@app.post("/tool")
@require_payment(config)
async def my_tool(request: Request):
return {"result": "..."}
Config options
| Parameter | Default | Description |
|---|---|---|
price_usdc |
required | Price per call in USDC |
wallet_address |
required | Your wallet (0x...) |
network |
base-sepolia |
Network name |
chain_id |
84532 |
Chain ID |
usdc_address |
Base Sepolia USDC | USDC contract address |
rpc_url |
https://sepolia.base.org |
JSON-RPC endpoint |
freshness_seconds |
300 |
Max tx age in seconds |
nonce_cache_ttl |
600 |
Nonce cache TTL in seconds |
nonce_db_path |
.payclaw_nonces.db |
SQLite file for replay protection |
rate_limit_requests |
10 |
Max requests per IP per window (0 = disabled) |
rate_limit_window_seconds |
60 |
Rate limit window in seconds |
trust_proxy |
False |
Trust X-Forwarded-For for per-IP rate limiting. Set True only when behind a trusted reverse proxy. When False, all traffic shares one rate limit bucket. |
Getting testnet USDC
Get free testnet USDC from the Circle faucet — select Base Sepolia and paste your wallet address.
Containerized deployments
The nonce cache is a SQLite file (default: .payclaw_nonces.db). In Docker or serverless environments where the filesystem is ephemeral, mount a persistent volume or point nonce_db_path to a mounted path:
config = PayclawConfig(
price_usdc=0.001,
wallet_address="0xYourWallet",
nonce_db_path="/data/payclaw_nonces.db", # mounted volume
)
Without persistence, a container restart clears the nonce cache and allows replay attacks within the freshness_seconds window.
Async frameworks (FastAPI)
The verify_payment function uses synchronous HTTP (requests). In an async FastAPI app, this blocks the event loop during RPC calls. For high-throughput deployments, wrap with asyncio.to_thread:
import asyncio
from payclaw import PayclawMiddleware, PayclawConfig
middleware = PayclawMiddleware(config)
@app.post("/tool")
async def my_tool(request: Request):
headers = dict(request.headers)
allowed, reason = await asyncio.to_thread(middleware.check, headers)
if not allowed:
_, body = middleware.payment_required(reason)
return JSONResponse(status_code=402, content=body)
return {"result": "..."}
Security
- Replay protection: SQLite nonce cache survives process restarts. Atomic INSERT OR IGNORE prevents race conditions.
- ERC-20 verification: Reads Transfer event logs from
eth_getTransactionReceipt— nottx.value(which is always 0 for USDC). - Integer math: USDC amounts compared as integer units (1 USDC = 1,000,000 units). No floating point.
- Block timestamp: Uses on-chain block timestamp for freshness check, not local clock.
- Address matching: Case-insensitive comparison (EIP-55 safe).
Legal
MIT License. payclaw is infrastructure software. Compliance with sanctions (OFAC) and applicable regulations is the responsibility of the deploying party.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file payclaw-0.1.0.tar.gz.
File metadata
- Download URL: payclaw-0.1.0.tar.gz
- Upload date:
- Size: 11.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5774b4333587d57fcc71cd6ab9a3375d966d356c892d4017d07a6bd5537b5dd0
|
|
| MD5 |
c4284199b9340dafb9d1dd633c5ee9b0
|
|
| BLAKE2b-256 |
9724626bdcaea7316d267827e401d5b063561237c4453cfcde6b626b63fc280f
|
File details
Details for the file payclaw-0.1.0-py3-none-any.whl.
File metadata
- Download URL: payclaw-0.1.0-py3-none-any.whl
- Upload date:
- Size: 9.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eb26fcebcf9ba8d3f1f9c1e55e52d9eb4e9a300314052392a4610be33a356c33
|
|
| MD5 |
c194cfe2f3af8d3c65a8a31fadff4f06
|
|
| BLAKE2b-256 |
b81db350f77e342be2e15908562b854602d64e5d20b1bc0a1da20f5a24dea4dd
|