"""KerfLedger HTTP service; no operator private key; no production test bypass.""" import base64 import hashlib import json import os import re import sqlite3 import time from pathlib import Path import httpx from eth_account import Account from eth_account.messages import encode_typed_data from eth_utils import keccak from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse, JSONResponse, PlainTextResponse from engine import canonical, plan, EXAMPLE BASE = "https://test.hpservices-tx.com/kerfledger" ENDPOINT = BASE + "/v1/plan" NETWORK = "eip155:8453" ASSET = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913" # Authoritative signature-verified Speedbot binding (not the malformed 43-character task-context token). PAYEE = "0xcc7df8ec2585fb7b2fcee3f3cbdeba1ba0187a6f" REQUIREMENTS = {"scheme": "exact", "network": NETWORK, "amount": "10000", "asset": ASSET, "payTo": PAYEE, "maxTimeoutSeconds": 300, "extra": {"name": "USD Coin", "version": "2"}} AUTH_TOPIC = "0x" + keccak(text="AuthorizationUsed(address,bytes32)").hex() TRANSFER_TOPIC = "0x" + keccak(text="Transfer(address,address,uint256)").hex() def b64(value): return base64.b64encode(canonical(value).encode()).decode() def challenge(error="Payment required"): value = {"x402Version": 2, "error": error, "resource": {"url": ENDPOINT, "description": "Kerf-aware stock cutting plan", "mimeType": "application/json"}, "accepts": [REQUIREMENTS]} return JSONResponse(value, status_code=402, headers={"PAYMENT-REQUIRED": b64(value)}) def typed_authorization(auth): return encode_typed_data(full_message={ "types": {"EIP712Domain": [{"name": "name", "type": "string"}, {"name": "version", "type": "string"}, {"name": "chainId", "type": "uint256"}, {"name": "verifyingContract", "type": "address"}], "TransferWithAuthorization": [{"name": "from", "type": "address"}, {"name": "to", "type": "address"}, {"name": "value", "type": "uint256"}, {"name": "validAfter", "type": "uint256"}, {"name": "validBefore", "type": "uint256"}, {"name": "nonce", "type": "bytes32"}]}, "primaryType": "TransferWithAuthorization", "domain": {"name": "USD Coin", "version": "2", "chainId": 8453, "verifyingContract": ASSET}, "message": auth}) def validate_payment(encoded): if len(encoded) > 16000: raise ValueError("Payment header too large") payment = json.loads(base64.b64decode(encoded, validate=True)) if payment.get("x402Version") != 2: raise ValueError("x402 v2 required") accepted = payment["accepted"] for key in ("scheme", "network", "amount", "asset", "payTo", "maxTimeoutSeconds", "extra"): if key in ("asset", "payTo"): if accepted[key].lower() != REQUIREMENTS[key].lower(): raise ValueError("Payment terms mismatch") elif accepted.get(key) != REQUIREMENTS[key]: raise ValueError("Payment terms mismatch") if payment.get("resource", {}).get("url", ENDPOINT) != ENDPOINT: raise ValueError("Resource mismatch") auth = payment["payload"]["authorization"] if set(auth) != {"from", "to", "value", "validAfter", "validBefore", "nonce"}: raise ValueError("Invalid EIP-3009 authorization fields") if not re.fullmatch(r"0x[0-9a-fA-F]{64}", auth["nonce"]): raise ValueError("Invalid nonce") if not re.fullmatch(r"0x[0-9a-fA-F]{40}", auth["from"]): raise ValueError("Invalid payer") if auth["to"].lower() != PAYEE.lower() or str(auth["value"]) != "10000": raise ValueError("Wrong recipient or amount") signer = Account.recover_message(typed_authorization(auth), signature=payment["payload"]["signature"]) if signer.lower() != auth["from"].lower(): raise ValueError("Invalid authorization signature") identity = hashlib.sha256((NETWORK + ASSET.lower() + signer.lower() + auth["nonce"].lower()).encode()).hexdigest() return payment, auth, identity class LivePayments: """Only settle() can have chain effects. Never called by unpaid/example/test routes.""" def rpc(self, method, params): with httpx.Client(timeout=15, follow_redirects=False) as client: response = client.post("https://mainnet.base.org", json={"jsonrpc": "2.0", "id": 1, "method": method, "params": params}) response.raise_for_status() value = response.json() if "error" in value: raise RuntimeError("Base RPC unavailable") return value["result"] def block(self): return int(self.rpc("eth_blockNumber", []), 16) def facilitator(self, path, payment): with httpx.Client(timeout=45, follow_redirects=False, headers={"User-Agent": "KerfLedger/1.0"}) as client: response = client.post("https://facilitator.payai.network/" + path, json={"x402Version": 2, "paymentPayload": payment, "paymentRequirements": REQUIREMENTS}) response.raise_for_status() return response.json() def verify(self, payment): return self.facilitator("verify", payment).get("isValid") is True def settle(self, payment): return self.facilitator("settle", payment) def reconcile(self, row, auth): safe = int(self.rpc("eth_getBlockByNumber", ["safe", False])["number"], 16) tx = row["tx"] if not tx: start = row["scan_block"] stop = min(safe, start + 1999) if stop < start: return None, start logs = self.rpc("eth_getLogs", [{"address": ASSET, "fromBlock": hex(start), "toBlock": hex(stop), "topics": [AUTH_TOPIC, "0x" + auth["from"][2:].lower().zfill(64), auth["nonce"].lower()]}]) if not logs: return None, stop + 1 tx = logs[0]["transactionHash"] receipt = self.rpc("eth_getTransactionReceipt", [tx]) if not receipt or int(receipt["blockNumber"], 16) > safe or receipt.get("status") != "0x1": return None, row["scan_block"] auth_found = transfer_found = False payer_topic = "0x" + auth["from"][2:].lower().zfill(64) payee_topic = "0x" + PAYEE[2:].lower().zfill(64) for log in receipt["logs"]: if log["address"].lower() != ASSET.lower() or log.get("removed"): continue topics = [t.lower() for t in log["topics"]] if topics == [AUTH_TOPIC.lower(), payer_topic, auth["nonce"].lower()]: auth_found = True if topics == [TRANSFER_TOPIC.lower(), payer_topic, payee_topic] and int(log["data"], 16) == 10000: transfer_found = True if not (auth_found and transfer_found): return None, row["scan_block"] return {"success": True, "network": NETWORK, "transaction": tx, "payer": auth["from"], "confirmation": "safe"}, row["scan_block"] def make_app(db_path, payments=None): payments = payments or LivePayments() db_path = str(db_path) def db(): connection = sqlite3.connect(db_path, timeout=10) connection.row_factory = sqlite3.Row return connection Path(db_path).parent.mkdir(parents=True, exist_ok=True) with db() as connection: connection.execute("PRAGMA journal_mode=WAL") connection.execute("CREATE TABLE IF NOT EXISTS calls (id TEXT PRIMARY KEY, input_hash TEXT NOT NULL, result TEXT, created REAL NOT NULL, state TEXT NOT NULL, scan_block INTEGER NOT NULL, tx TEXT, receipt TEXT)") app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None) @app.middleware("http") async def headers(request, call_next): response = await call_next(request) response.headers.update({"Cache-Control": "no-store", "X-Content-Type-Options": "nosniff", "X-KerfLedger-Version": "1.0.0"}) return response @app.get("/kerfledger/health") def health(): with db() as connection: connection.execute("SELECT 1").fetchone() return {"ok": True, "version": "1.0.0", "network": NETWORK, "price_usdc": "0.01", "payment_mode": "live_facilitator", "self_purchase_performed": False} @app.get("/kerfledger/example") def example(): return {"kind": "free_fixed_worked_example_not_paid_evidence", "input": EXAMPLE, "output": plan(EXAMPLE)} @app.get("/kerfledger") @app.get("/kerfledger/") @app.get("/kerfledger/docs") def docs(): return HTMLResponse(Path(__file__).with_name("docs.html").read_text()) @app.get("/kerfledger/terms") def terms(): return PlainTextResponse(Path(__file__).with_name("terms.txt").read_text()) @app.get("/kerfledger/evidence") def evidence(): path = Path(__file__).with_name("evidence.json") return JSONResponse(json.loads(path.read_text())) if path.exists() else JSONResponse({"status": "evidence not yet collected"}, status_code=503) @app.get("/kerfledger/source/{name}") def source(name: str): if name not in {"engine.py", "server.py", "test_service.py", "requirements.txt", "README.md"}: return JSONResponse({"error": "not found"}, status_code=404) return PlainTextResponse(Path(__file__).with_name(name).read_text()) @app.post("/kerfledger/v1/plan") async def paid_plan(request: Request): raw = await request.body() if len(raw) > 16000: return JSONResponse({"error": "Request exceeds 16000 bytes"}, status_code=413) try: data = json.loads(raw) result = plan(data) except (ValueError, TypeError, KeyError, UnicodeError): return JSONResponse({"error": "Invalid input; see docs for integer-mm schema and limits"}, status_code=422) encoded = request.headers.get("payment-signature") if not encoded: return challenge() try: payment, auth, identity = validate_payment(encoded) except Exception: return challenge("Invalid payment authorization") input_hash = result["input_sha256"] with db() as connection: row = connection.execute("SELECT * FROM calls WHERE id=?", (identity,)).fetchone() if row and row["input_hash"] != input_hash: return JSONResponse({"error": "Authorization is bound to different input; never authorize a second payment to recover"}, status_code=409) if not row: now = int(time.time()) if not int(auth["validAfter"]) < now < int(auth["validBefore"]) or int(auth["validBefore"]) > now + 300: return challenge("Authorization must be currently valid and expire within 300 seconds") try: if not payments.verify(payment): return challenge("Facilitator rejected payment; nothing settled") start = payments.block() except Exception: return JSONResponse({"error": "Payment verification unavailable; no settlement attempted"}, status_code=503) with db() as connection: connection.execute("BEGIN IMMEDIATE") inserted = connection.execute("INSERT OR IGNORE INTO calls VALUES (?,?,?,?,?,?,NULL,NULL)", (identity, input_hash, canonical(result), time.time(), "settling", start)).rowcount row = connection.execute("SELECT * FROM calls WHERE id=?", (identity,)).fetchone() if row["input_hash"] != input_hash: return JSONResponse({"error": "Authorization already bound to different input"}, status_code=409) if inserted: # Intent and deterministic result are durable BEFORE disclosing payment. # At most one settle invocation; uncertainty only reconciles chain receipts. try: outcome = payments.settle(payment) tx = outcome.get("transaction") if isinstance(tx, str) and re.fullmatch(r"0x[0-9a-fA-F]{64}", tx): with db() as connection: connection.execute("UPDATE calls SET tx=? WHERE id=?", (tx, identity)) except Exception: pass with db() as connection: row = connection.execute("SELECT * FROM calls WHERE id=?", (identity,)).fetchone() if row["receipt"]: receipt = json.loads(row["receipt"]) else: try: receipt, scan = payments.reconcile(row, auth) with db() as connection: if receipt: connection.execute("UPDATE calls SET state='complete', receipt=?, tx=? WHERE id=?", (canonical(receipt), receipt["transaction"], identity)) else: connection.execute("UPDATE calls SET scan_block=MAX(scan_block,?) WHERE id=?", (scan, identity)) except Exception: receipt = None if not receipt: return JSONResponse({"status": "pending", "instruction": "Retry identical input and original PAYMENT-SIGNATURE after Retry-After; do not create a new authorization. No automatic second settlement."}, status_code=202, headers={"Retry-After": "15"}) if time.time() - row["created"] > 30 * 86400: with db() as connection: connection.execute("UPDATE calls SET result=NULL WHERE id=?", (identity,)) return JSONResponse({"error": "Result expired; payment replay tombstone retained. No new charge."}, status_code=410) return JSONResponse(json.loads(row["result"]), headers={"PAYMENT-RESPONSE": b64(receipt)}) return app app = make_app(os.environ.get("KERFLEDGER_DB", "/var/lib/kerfledger/calls.sqlite3"))