UNPKG

run402-mcp

Version:

MCP server for Run402 — full-stack backend infrastructure for AI agents: Postgres, auth, storage, serverless functions and atomic deploys. Paid with x402/MPP. Includes $0.03 image generation.

231 lines • 9.68 kB
/** * Sanitized local journal for automatic x402 and MPP payment attempts. * * Each attempt is a separate mode-0600 file. Separate files avoid lost * updates when multiple SDK processes pay concurrently. Records intentionally * exclude request headers, query strings, bodies, wallet keys, signed payment * authorizations, and provider proofs. */ import { chmodSync, closeSync, fchmodSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, readdirSync, rmSync, statSync, writeFileSync, } from "node:fs"; import { dirname, join } from "node:path"; import { createHash, randomUUID } from "node:crypto"; import { getConfigDir } from "../../core-dist/config.js"; import { LocalError } from "../errors.js"; export const PAYMENT_ATTEMPT_HEADER = "X-Run402-Payment-Attempt-Id"; export const PAYMENT_ATTEMPT_ID_PATTERN = /^pat_[0-9a-f]{32}$/; export function createPaymentAttemptId() { return `pat_${randomUUID().replaceAll("-", "")}`; } export function paymentAttemptJournalDir() { return join(getConfigDir(), "payment-attempts"); } export function createFilePaymentAttemptStore(dir = paymentAttemptJournalDir()) { return { claim(record) { assertPaymentAttemptId(record.payment_attempt_id); ensureJournalDir(dir); const path = join(dir, `${record.payment_attempt_id}.json`); let fd; try { fd = openSync(path, "wx", 0o600); } catch (cause) { if (cause?.code === "EEXIST") return false; throw cause; } try { writeFileSync(fd, `${JSON.stringify(record, null, 2)}\n`); fchmodSync(fd, 0o600); fsyncSync(fd); } finally { closeSync(fd); } fsyncDirectory(dir); return true; }, write(record) { assertPaymentAttemptId(record.payment_attempt_id); ensureJournalDir(dir); const path = join(dir, `${record.payment_attempt_id}.json`); const tmp = `${path}.${process.pid}.${randomUUID()}.tmp`; try { const fd = openSync(tmp, "wx", 0o600); try { writeFileSync(fd, `${JSON.stringify(record, null, 2)}\n`); fchmodSync(fd, 0o600); fsyncSync(fd); } finally { closeSync(fd); } renameSync(tmp, path); fsyncDirectory(dir); } finally { rmSync(tmp, { force: true }); } }, read(paymentAttemptId) { assertPaymentAttemptId(paymentAttemptId); const path = join(dir, `${paymentAttemptId}.json`); let raw; try { raw = readFileSync(path, "utf8"); } catch (cause) { if (cause?.code === "ENOENT") return null; throw new LocalError("The local x402 payment attempt could not be read.", "reading x402 payment attempt", { code: "PAYMENT_ATTEMPT_READ_FAILED", cause }); } let parsed; try { parsed = JSON.parse(raw); } catch (cause) { throw new LocalError("The local x402 payment attempt record is not valid JSON.", "reading x402 payment attempt", { code: "PAYMENT_ATTEMPT_RECORD_INVALID", cause }); } if (!isPaymentAttemptRecord(parsed)) { throw new LocalError("The local x402 payment attempt record has an invalid shape.", "reading x402 payment attempt", { code: "PAYMENT_ATTEMPT_RECORD_INVALID" }); } return parsed; }, }; } function ensureJournalDir(dir) { mkdirSync(dir, { recursive: true, mode: 0o700 }); chmodSync(dir, 0o700); // Persist both the directory metadata and its entry in the parent before a // payment can depend on a claim within it. fsyncDirectory(dir); fsyncDirectory(dirname(dir)); } function fsyncDirectory(dir) { // Node cannot open directory handles for fsync on Windows. Exclusive create // and atomic replace still apply there; POSIX needs this extra durability // barrier for the directory entry itself. if (process.platform === "win32") return; const fd = openSync(dir, "r"); try { fsyncSync(fd); } finally { closeSync(fd); } } /** Read one sanitized attempt from the active wallet profile's journal. */ export function readPaymentAttempt(paymentAttemptId) { return createFilePaymentAttemptStore().read(paymentAttemptId); } /** * List recent sanitized attempts for reconciliation. This is a local read; * provider/target state remains authoritative for ambiguous attempts. */ export function listPaymentAttempts(opts = {}) { const limit = Math.max(1, Math.min(100, Math.trunc(opts.limit ?? 20))); const dir = paymentAttemptJournalDir(); let names; try { names = readdirSync(dir).filter((name) => /^pat_[0-9a-f]{32}\.json$/.test(name)); } catch { return []; } return names .map((name) => { try { const path = join(dir, name); const parsed = JSON.parse(readFileSync(path, "utf8")); return isPaymentAttemptRecord(parsed) ? { record: parsed, mtimeMs: statSync(path).mtimeMs } : null; } catch { return null; } }) .filter((item) => item !== null) .sort((a, b) => b.mtimeMs - a.mtimeMs) .slice(0, limit) .map((item) => item.record); } export function requestSummary(input, init) { const method = (init?.method ?? (input instanceof Request ? input.method : "GET")).toUpperCase(); const callerKey = mergedHeaders(input, init).get("idempotency-key"); const caller_key_sha256 = callerKey ? createHash("sha256").update(callerKey).digest("hex") : null; try { const url = new URL(input instanceof Request ? input.url : String(input)); return { method, origin: url.origin, path_sha256: createHash("sha256").update(url.pathname).digest("hex"), caller_key_sha256, }; } catch { return { method, origin: null, path_sha256: null, caller_key_sha256 }; } } export function attemptIdFromRequest(input, init) { const headers = mergedHeaders(input, init); const supplied = headers.get(PAYMENT_ATTEMPT_HEADER); if (!supplied) return null; assertPaymentAttemptId(supplied); return supplied; } export function withPaymentAttemptHeader(input, init, paymentAttemptId, paymentBearing) { const headers = mergedHeaders(input, init); // Reserved correlation metadata is sent only with the payment-bearing call, // never the unpriced discovery request. `redirect: "error"` prevents both // this id and the signed payment authorization from being forwarded to a // redirect target (especially an unrelated origin). headers.delete(PAYMENT_ATTEMPT_HEADER); if (paymentBearing) headers.set(PAYMENT_ATTEMPT_HEADER, paymentAttemptId); return [input, { ...init, headers, ...(paymentBearing ? { redirect: "error" } : {}) }]; } export function hasPaymentAuthorization(input, init) { const headers = mergedHeaders(input, init); return headers.has("PAYMENT-SIGNATURE") || headers.has("X-PAYMENT"); } function mergedHeaders(input, init) { const headers = new Headers(input instanceof Request ? input.headers : undefined); if (init?.headers) { new Headers(init.headers).forEach((value, key) => headers.set(key, value)); } return headers; } function assertPaymentAttemptId(value) { if (!PAYMENT_ATTEMPT_ID_PATTERN.test(value)) { throw new LocalError("Payment attempt id must match pat_ followed by 32 lowercase hexadecimal characters.", "reading x402 payment attempt", { code: "INVALID_PAYMENT_ATTEMPT_ID", details: { field: "paymentAttemptId" } }); } } function isPaymentAttemptRecord(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const record = value; return (record.version === 1 && typeof record.payment_attempt_id === "string" && PAYMENT_ATTEMPT_ID_PATTERN.test(record.payment_attempt_id) && (record.rail === "x402" || record.rail === "mpp") && ["intent", "submitting", "response_received", "intent_pending", "completed", "failed", "ambiguous"] .includes(record.state ?? "") && ["not_started", "in_progress", "completed", "ambiguous"].includes(record.mutation_state ?? "") && typeof record.method === "string" && (record.origin === null || typeof record.origin === "string") && (record.path_sha256 === null || (typeof record.path_sha256 === "string" && /^[0-9a-f]{64}$/.test(record.path_sha256))) && (record.caller_key_sha256 === undefined || /^[0-9a-f]{64}$/.test(record.caller_key_sha256)) && (record.payment_id === undefined || typeof record.payment_id === "string") && (record.intent_state === undefined || typeof record.intent_state === "string") && (record.retry_after_seconds === undefined || (Number.isSafeInteger(record.retry_after_seconds) && record.retry_after_seconds >= 0)) && typeof record.created_at === "string" && typeof record.updated_at === "string"); } //# sourceMappingURL=payment-attempts.js.map