merge: chore/cleanup-remove-bloat-and-secrets into main

This commit is contained in:
Crypto Rug Munch 2026-07-02 01:24:22 +07:00
commit bde2f3a97d
1173 changed files with 437609 additions and 0 deletions

155
app/domain/x402/router.py Normal file
View file

@ -0,0 +1,155 @@
"""T34 x402 Paid Tools Catalog.
Per v4.0 §T34. Endpoints:
GET /api/v1/x402/catalog list all tools with pricing tiers
GET /api/v1/x402/usage per-agent usage stats
GET /api/v1/x402/receipts/{tx_hash} payment receipt
POST /api/v1/x402/verify verify a payment header
"""
from __future__ import annotations
import logging
from typing import Any
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, Field
from app.catalog.service import get_catalog
from app.domain.x402.middleware import (
PRICING,
get_tool_metadata,
get_tool_price_usd,
verify_payment_header,
verify_payment_on_chain,
)
router = APIRouter(prefix="/api/v1/x402", tags=["x402"])
class CatalogResponse(BaseModel):
server: str = "rugmunch-intelligence"
version: str = "4.0"
tools: list[dict[str, Any]] = Field(default_factory=list)
payment_protocol: str = "x402"
class UsageResponse(BaseModel):
agent_id: str
daily_calls: int
free_limit: int
paid_calls: int
total_usd: float
recent_receipts: list[dict[str, Any]] = Field(default_factory=list)
class ReceiptResponse(BaseModel):
tx_hash: str
tool: str
agent_id: str | None = None
amount_usd: float
chain: str | None = None
paid_at: str | None = None
tier: str | None = None
@router.get("/catalog", response_model=CatalogResponse)
async def catalog() -> CatalogResponse:
"""List all paid tools with pricing tiers."""
return CatalogResponse(tools=[get_tool_metadata(t) for t in PRICING])
@router.get("/usage", response_model=UsageResponse)
async def usage(request: Request) -> UsageResponse:
"""Per-agent usage stats: calls made, $ spent, top tools."""
catalog = get_catalog()
await catalog._init_stores()
agent_id = request.headers.get("X-Agent-Id") or request.headers.get("X-Api-Key") or "anon"
daily_calls = 0
paid_calls = 0
total_usd = 0.0
recent: list[dict] = []
if catalog._health.redis:
try:
from datetime import UTC, datetime
day = datetime.now(UTC).strftime("%Y%m%d")
key = f"x402:rl:{agent_id}:{day}"
daily_calls = int(await catalog._redis.get(key) or 0)
except Exception:
pass
if catalog._health.postgres:
try:
async with catalog._pg_pool.acquire() as conn:
rows = await conn.fetch(
"SELECT tx_hash, tool, amount_usd, paid_at FROM x402_receipts "
"WHERE agent_id = $1 ORDER BY paid_at DESC LIMIT 10",
agent_id,
)
for r in rows:
paid_calls += 1
total_usd += float(r["amount_usd"] or 0)
recent.append({
"tx_hash": r["tx_hash"],
"tool": r["tool"],
"amount_usd": float(r["amount_usd"] or 0),
"paid_at": r["paid_at"].isoformat() if r["paid_at"] else None,
})
except Exception as e:
logging.getLogger(__name__).warning(f"usage_query_fail: {e}")
return UsageResponse(
agent_id=agent_id, daily_calls=daily_calls, free_limit=5,
paid_calls=paid_calls, total_usd=round(total_usd, 4),
recent_receipts=recent,
)
@router.get("/receipts/{tx_hash}", response_model=ReceiptResponse)
async def receipt(tx_hash: str) -> ReceiptResponse:
"""Payment receipt for a specific on-chain tx."""
catalog = get_catalog()
await catalog._init_stores()
if not catalog._health.postgres:
raise HTTPException(503, "postgres unavailable")
try:
async with catalog._pg_pool.acquire() as conn:
r = await conn.fetchrow(
"SELECT * FROM x402_receipts WHERE tx_hash=$1", tx_hash
)
if not r:
raise HTTPException(404, "receipt not found")
d = dict(r)
return ReceiptResponse(
tx_hash=d["tx_hash"],
tool=d["tool"],
agent_id=d.get("agent_id"),
amount_usd=float(d.get("amount_usd", 0)),
chain=d.get("chain"),
paid_at=d["paid_at"].isoformat() if d.get("paid_at") else None,
tier=d.get("tier"),
)
except HTTPException:
raise
except Exception as e:
raise HTTPException(500, f"receipt_query_fail: {e}")
@router.post("/verify")
async def verify_payment(request: Request) -> dict:
"""Verify an X-Payment header for a given tool."""
body = await request.json()
tool = body.get("tool", "")
x_payment = body.get("x_payment", "")
if tool not in PRICING:
raise HTTPException(404, f"unknown tool: {tool}")
if get_tool_price_usd(tool) == 0:
return {"valid": True, "tier": "free", "amount_usd": 0}
decoded = verify_payment_header(x_payment, get_tool_price_usd(tool))
if not decoded:
return {"valid": False, "reason": "invalid format"}
tx_hash, _ = decoded
valid = await verify_payment_on_chain(tx_hash, get_tool_price_usd(tool))
return {
"valid": valid,
"tool": tool,
"amount_usd": get_tool_price_usd(tool),
"tx_hash": tx_hash,
}