Skip to main content

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

  1. Agent calls your tool endpoint
  2. No valid payment → server returns HTTP 402 with price and wallet address
  3. Agent pays USDC on Base chain, gets tx hash
  4. Agent retries with X-Payment: <tx_hash> header
  5. 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 — not tx.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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

payclaw-0.1.0.tar.gz (11.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

payclaw-0.1.0-py3-none-any.whl (9.3 kB view details)

Uploaded Python 3

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

Hashes for payclaw-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5774b4333587d57fcc71cd6ab9a3375d966d356c892d4017d07a6bd5537b5dd0
MD5 c4284199b9340dafb9d1dd633c5ee9b0
BLAKE2b-256 9724626bdcaea7316d267827e401d5b063561237c4453cfcde6b626b63fc280f

See more details on using hashes here.

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

Hashes for payclaw-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 eb26fcebcf9ba8d3f1f9c1e55e52d9eb4e9a300314052392a4610be33a356c33
MD5 c194cfe2f3af8d3c65a8a31fadff4f06
BLAKE2b-256 b81db350f77e342be2e15908562b854602d64e5d20b1bc0a1da20f5a24dea4dd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page