diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d691d2..4067388 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -132,4 +132,4 @@ jobs: - name: Stop API if: always() run: | - [[ -n "${API_PID:-}" ]] && kill "$API_PID" 2>/dev/null || true \ No newline at end of file + [[ -n "${API_PID:-}" ]] && kill "$API_PID" 2>/dev/null || true diff --git a/apps/agent-client/package.json b/apps/agent-client/package.json index 524c427..3e549a8 100644 --- a/apps/agent-client/package.json +++ b/apps/agent-client/package.json @@ -33,4 +33,4 @@ "typescript": "^5.8.3", "vitest": "^3.2.4" } -} \ No newline at end of file +} diff --git a/apps/agent-client/src/cli.test.ts b/apps/agent-client/src/cli.test.ts index 82e4197..c4a9be7 100644 --- a/apps/agent-client/src/cli.test.ts +++ b/apps/agent-client/src/cli.test.ts @@ -54,7 +54,7 @@ describe("formatSummary", () => { asset: "USDC", traceId: "trace-abc-123", evidenceId: "ev-xyz-789", - latencyMs: 342, + latencyMs: 342 }; it("includes mode and provider", () => { diff --git a/apps/agent-client/src/cli.ts b/apps/agent-client/src/cli.ts index 0d91d85..a6a3518 100644 --- a/apps/agent-client/src/cli.ts +++ b/apps/agent-client/src/cli.ts @@ -17,22 +17,20 @@ export interface SummaryInput { /** Formats the post-query summary table. Pure function — safe to unit-test directly. */ export function formatSummary(input: SummaryInput): string { const rows: [string, string][] = [ - ["Mode", input.mode], - ["Provider", input.provider], - ["Status", String(input.status)], - ["Client", input.isDemoMode ? "demo" : "real"], + ["Mode", input.mode], + ["Provider", input.provider], + ["Status", String(input.status)], + ["Client", input.isDemoMode ? "demo" : "real"], ["Price (USD)", input.priceUsd != null ? String(input.priceUsd) : "n/a"], - ["Asset", input.asset ?? "n/a"], - ["Trace ID", input.traceId ?? "unavailable"], - ["Evidence ID", input.evidenceId ?? "unavailable"], + ["Asset", input.asset ?? "n/a"], + ["Trace ID", input.traceId ?? "unavailable"], + ["Evidence ID", input.evidenceId ?? "unavailable"] ]; if (input.latencyMs != null) { rows.push(["Latency", `${input.latencyMs}ms`]); } const labelWidth = Math.max(...rows.map(([label]) => label.length)); - const body = rows - .map(([label, value]) => ` ${label.padEnd(labelWidth)} ${value}`) - .join("\n"); + const body = rows.map(([label, value]) => ` ${label.padEnd(labelWidth)} ${value}`).join("\n"); const divider = "=".repeat(labelWidth + 4 + 20); return `\n=== Query402 Paid Query Summary ===\n${body}\n${divider}`; } @@ -88,8 +86,10 @@ async function main() { const latencyMs = Date.now() - start; const payload = result.body as Record; - const resultBlock = (payload?.result ?? (payload?.body as Record)?.result) as Record | undefined; - const evidenceBlock = (payload?.payment as Record)?.evidence as Record | undefined; + const resultBlock = (payload?.result ?? (payload?.body as Record)?.result) as + Record | undefined; + const evidenceBlock = (payload?.payment as Record)?.evidence as + Record | undefined; console.log( formatSummary({ @@ -101,7 +101,7 @@ async function main() { asset: (evidenceBlock?.proofLinks as Record | undefined)?.asset, traceId: resultBlock?.traceId as string | undefined, evidenceId: (evidenceBlock?.id ?? evidenceBlock?.evidenceId) as string | undefined, - latencyMs, + latencyMs }) ); } diff --git a/apps/agent-client/src/client.ts b/apps/agent-client/src/client.ts index 7dca6f7..80027cf 100644 --- a/apps/agent-client/src/client.ts +++ b/apps/agent-client/src/client.ts @@ -102,6 +102,11 @@ export async function runPaidQuery(input: { } const json = await response.json(); + if (!response.ok || response.status === 402) { + if (json && typeof json === "object" && !json.errorCode) { + json.errorCode = response.status === 402 ? "payment_required" : "internal_error"; + } + } return { endpoint, diff --git a/apps/agent-client/src/transcript.ts b/apps/agent-client/src/transcript.ts index aaea821..39e2736 100644 --- a/apps/agent-client/src/transcript.ts +++ b/apps/agent-client/src/transcript.ts @@ -37,9 +37,7 @@ const OUT_DIR = path.resolve(__dirname, "../../../transcript"); // Guard: refuse to run outside DEMO_MODE // --------------------------------------------------------------------------- if (config.DEMO_MODE !== "true") { - console.error( - "ERROR: Set DEMO_MODE=true to generate a transcript without live credentials." - ); + console.error("ERROR: Set DEMO_MODE=true to generate a transcript without live credentials."); process.exit(1); } @@ -49,10 +47,10 @@ const API_BASE = config.API_BASE_URL.replace(/\/$/, ""); // Secret redaction // --------------------------------------------------------------------------- const SECRET_PATTERNS: RegExp[] = [ - /S[A-Z0-9]{55}/g, // Stellar secret key (starts with S, 56 chars) - /Bearer\s+\S+/gi, // Bearer tokens - /x-payment:\s*\S+/gi, // raw payment header value - /X402-Payment:\s*\S+/gi, + /S[A-Z0-9]{55}/g, + /Bearer\s+\S+/gi, + /x-payment:\s*\S+/gi, + /X402-Payment:\s*\S+/gi ]; const REDACTED = "[REDACTED]"; @@ -61,7 +59,7 @@ const SENSITIVE_HEADER_KEYS = new Set([ "x402-payment", "authorization", "x-api-key", - "payment-response", // raw tx ID; replaced with presence flag below + "payment-response" ]); const SENSITIVE_OBJ_KEYS = new Set([ @@ -71,7 +69,7 @@ const SENSITIVE_OBJ_KEYS = new Set([ "private_key", "privatekey", "api_key", - "apikey", + "apikey" ]); function redact(value: unknown): unknown { @@ -117,15 +115,21 @@ interface RawResult { function httpGet(url: string): Promise { return new Promise((resolve, reject) => { - http.get(url, (res) => { - let raw = ""; - res.on("data", (chunk: Buffer) => (raw += chunk.toString())); - res.on("end", () => { - let body: unknown; - try { body = JSON.parse(raw); } catch { body = raw; } - resolve({ status: res.statusCode ?? 0, headers: res.headers, body }); - }); - }).on("error", reject); + http + .get(url, (res) => { + let raw = ""; + res.on("data", (chunk: Buffer) => (raw += chunk.toString())); + res.on("end", () => { + let body: unknown; + try { + body = JSON.parse(raw); + } catch { + body = raw; + } + resolve({ status: res.statusCode ?? 0, headers: res.headers, body }); + }); + }) + .on("error", reject); }); } @@ -167,9 +171,7 @@ async function step1Health(): Promise { responseHeaders: safeHeaders(r.headers), body: redact(r.body), note: - r.status === 200 - ? "API is healthy and ready." - : "API responded but may not be fully ready.", + r.status === 200 ? "API is healthy and ready." : "API responded but may not be fully ready." }; } catch (err) { return { @@ -177,7 +179,7 @@ async function step1Health(): Promise { timestamp, status: "n/a", error: `Could not reach ${API_BASE}/health — is the API running? (${err})`, - note: "Transcript is still written for CI evidence purposes.", + note: "Transcript is still written for CI evidence purposes." }; } } @@ -192,14 +194,14 @@ async function step2Catalog(): Promise { status: r.status, responseHeaders: safeHeaders(r.headers), body: redact(r.body), - note: "Available search/news/scrape providers with per-request pricing.", + note: "Available search/news/scrape providers with per-request pricing." }; } catch (err) { return { step: "2_provider_catalog", timestamp, status: "n/a", - error: String(err), + error: String(err) }; } } @@ -210,9 +212,17 @@ async function step2Catalog(): Promise { */ async function step3PaidQueries(): Promise { const queries: Array[0]> = [ - { mode: "search", provider: "search.pro", query: "latest stellar x402 updates" }, - { mode: "news", provider: "news.deep", query: "stablecoin micropayments" }, - { mode: "scrape", provider: "scrape.extract", url: "https://developers.stellar.org" }, + { + mode: "search", + provider: "search.pro", + query: "latest stellar x402 updates" + }, + { mode: "news", provider: "news.deep", query: "stablecoin micropayments" }, + { + mode: "scrape", + provider: "scrape.extract", + url: "https://developers.stellar.org" + } ]; const steps: Step[] = []; @@ -220,29 +230,28 @@ async function step3PaidQueries(): Promise { for (let i = 0; i < queries.length; i++) { const q = queries[i]; const timestamp = new Date().toISOString(); - const label = `3${String.fromCharCode(97 + i)}_demo_paid_${q.mode}`; // 3a_, 3b_, 3c_ + const label = `3${String.fromCharCode(97 + i)}_demo_paid_${q.mode}`; try { const response = await runPaidQuery(q); const payload = response.body as Record | undefined; - const result = payload?.result as Record | undefined; + const result = payload?.result as Record | undefined; steps.push({ step: label, timestamp, status: response.status, body: redact({ - provider: q.provider, - endpoint: response.endpoint, - // Never write the raw payment-response header value; record presence only + provider: q.provider, + endpoint: response.endpoint, payment_response_present: Boolean(response.paymentResponse), - price_usd: result?.priceUsd ?? "n/a", - items_returned: Array.isArray(result?.items) ? result.items.length : 0, - result_body: payload, + price_usd: result?.priceUsd ?? "n/a", + items_returned: Array.isArray(result?.items) ? result.items.length : 0, + result_body: payload }), note: "DEMO_MODE=true — payment header contains a placeholder tx ID; " + - "no real Stellar transaction is submitted or settled.", + "no real Stellar transaction is submitted or settled." }); } catch (err) { steps.push({ step: label, timestamp, status: "n/a", error: String(err) }); @@ -254,21 +263,21 @@ async function step3PaidQueries(): Promise { function step4Metadata(paidSteps: Step[]): Step { const first = paidSteps.find((s) => s.status !== "n/a"); - const body = first?.body as Record | undefined; + const body = first?.body as Record | undefined; return { step: "4_response_metadata", timestamp: new Date().toISOString(), status: "n/a", body: { - provider: body?.provider ?? "search.pro", - price_usd: body?.price_usd ?? "n/a", + provider: body?.provider ?? "search.pro", + price_usd: body?.price_usd ?? "n/a", settlement_network: "stellar:testnet", - payment_status: "DEMO — no real Stellar settlement", + payment_status: "DEMO — no real Stellar settlement", note: "In production this section contains the facilitator-signed " + - "payment-response header. Here it is intentionally omitted.", + "payment-response header. Here it is intentionally omitted." }, - note: "Synthesised from paid-query responses. No secrets included.", + note: "Synthesised from paid-query responses. No secrets included." }; } @@ -282,14 +291,14 @@ async function step5Analytics(): Promise { status: r.status, responseHeaders: safeHeaders(r.headers), body: redact(r.body), - note: "Total spend + per-category breakdown stored in SQLite.", + note: "Total spend + per-category breakdown stored in SQLite." }; } catch (err) { return { step: "5_analytics_summary", timestamp, status: "n/a", - error: String(err), + error: String(err) }; } } @@ -308,7 +317,7 @@ function assemble(steps: Step[]): Transcript { "Generated in DEMO_MODE. No real Stellar credentials or live payments " + "were used. All secret fields are redacted. " + "Safe to attach to Drips/SCF updates and investor notes.", - steps, + steps }; } @@ -323,13 +332,13 @@ function toText(t: Transcript): string { ` Network : ${t.settlement_network}`, ` ⚠ ${t.warning}`, bar, - "", + "" ]; for (const s of t.steps) { lines.push(`▶ ${s.step}`); lines.push(` Timestamp : ${s.timestamp}`); lines.push(` Status : ${s.status}`); - if (s.note) lines.push(` Note : ${s.note}`); + if (s.note) lines.push(` Note : ${s.note}`); if (s.error) lines.push(` ERROR : ${s.error}`); if (s.body) { lines.push(" Body:"); @@ -348,10 +357,10 @@ function writeArtifact(t: Transcript): { json: string; txt: string } { const slug = t.generated_at.replace(/[:.]/g, "-").replace("T", "_"); const jsonPath = path.join(OUT_DIR, `demo-transcript-${slug}.json`); - const txtPath = path.join(OUT_DIR, `demo-transcript-${slug}.txt`); + const txtPath = path.join(OUT_DIR, `demo-transcript-${slug}.txt`); fs.writeFileSync(jsonPath, JSON.stringify(t, null, 2), "utf8"); - fs.writeFileSync(txtPath, toText(t), "utf8"); + fs.writeFileSync(txtPath, toText(t), "utf8"); return { json: jsonPath, txt: txtPath }; } @@ -389,7 +398,8 @@ async function main(): Promise { console.log(` JSON : ${json}`); console.log(` TXT : ${txt}`); console.log(""); - console.log(" Label : DEMO_MODE (safe for SCF / Drips / investor notes)"); + const note = " Label : DEMO_MODE (safe for SCF / Drips / investor notes)"; + console.log(note); console.log(" Secrets : all redacted"); console.log(" Payment : no real Stellar transaction"); } @@ -397,4 +407,4 @@ async function main(): Promise { main().catch((err) => { console.error("Fatal:", err); process.exit(1); -}); \ No newline at end of file +}); diff --git a/apps/agent-client/src/validate-real.test.ts b/apps/agent-client/src/validate-real.test.ts index a06b73f..0d2d556 100644 --- a/apps/agent-client/src/validate-real.test.ts +++ b/apps/agent-client/src/validate-real.test.ts @@ -18,9 +18,9 @@ describe("safeFacilitatorUrl", () => { it("strips auth credentials from URL", async () => { const { safeFacilitatorUrl } = await import("./validate-real.js"); - expect( - safeFacilitatorUrl("https://user:password@channels.openzeppelin.com/x402/testnet") - ).toBe("channels.openzeppelin.com"); + expect(safeFacilitatorUrl("https://user:password@channels.openzeppelin.com/x402/testnet")).toBe( + "channels.openzeppelin.com" + ); }); it("preserves port in host", async () => { diff --git a/apps/agent-client/src/validate-real.ts b/apps/agent-client/src/validate-real.ts index 7dd7939..9054e58 100644 --- a/apps/agent-client/src/validate-real.ts +++ b/apps/agent-client/src/validate-real.ts @@ -86,17 +86,25 @@ async function main() { console.log("\n--- Facilitator Health ---"); if (!config.X402_FACILITATOR_URL) { console.log(`Host: not configured`); - console.log(`Warning: X402_FACILITATOR_URL is not set. Set it in your .env file for real payment validation.`); + console.log( + `Warning: X402_FACILITATOR_URL is not set. Set it in your .env file for real payment validation.` + ); console.log(`Probe attempted: no`); console.log(`Network: ${network}`); console.log(`Timestamp: ${probeTimestamp}`); - throw new Error("X402_FACILITATOR_URL is not configured. Real payment validation requires a facilitator URL."); + throw new Error( + "X402_FACILITATOR_URL is not configured. Real payment validation requires a facilitator URL." + ); } else { console.log(`Host: ${facilitatorHost ?? "unparseable"}`); console.log(`Network: ${network}`); console.log("[probe] Checking facilitator /supported..."); const healthResult = await checkFacilitator(config.X402_FACILITATOR_URL); - const reasonText = healthResult.ok ? undefined : typeof healthResult.body === "string" ? healthResult.body : JSON.stringify(healthResult.body); + const reasonText = healthResult.ok + ? undefined + : typeof healthResult.body === "string" + ? healthResult.body + : JSON.stringify(healthResult.body); console.log(`Probe attempted: yes`); console.log(`Status: ${healthResult.status === 0 ? "N/A" : healthResult.status}`); if (!healthResult.ok && reasonText) { @@ -105,9 +113,7 @@ async function main() { console.log(`Timestamp: ${probeTimestamp}`); if (!healthResult.ok) { - throw new Error( - `Facilitator check failed (${healthResult.status}): ${reasonText}` - ); + throw new Error(`Facilitator check failed (${healthResult.status}): ${reasonText}`); } console.log("[ok] Facilitator reachable and returned /supported response."); } @@ -155,8 +161,9 @@ async function main() { }); if (!result.ok) { + const errorCode = (result.body as any)?.errorCode ? ` [${(result.body as any).errorCode}]` : ""; throw new Error( - `Paid request failed with status ${result.status}. Response: ${JSON.stringify(result.body)}` + `Paid request failed with status ${result.status}${errorCode}. Response: ${JSON.stringify(result.body)}` ); } diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index 77835ba..62b7528 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -9,6 +9,7 @@ import { logger } from "./lib/logger.js"; import { config } from "./lib/config.js"; import { UnsafeScrapeUrlError } from "./lib/scrape-url-safety.js"; import { PaymentEvidenceError } from "./lib/payment-evidence.js"; +import { ProviderTimeoutError, ProviderFailedError } from "./services/query-service.js"; export const app = express(); @@ -65,7 +66,8 @@ app.use( if (error instanceof UnsafeScrapeUrlError) { res.status(400).json({ error: "Scrape URL is not allowed", - type: "unsafe_scrape_url" + type: "unsafe_scrape_url", + errorCode: "invalid_query" }); return; } @@ -73,14 +75,34 @@ app.use( if (error instanceof PaymentEvidenceError) { res.status(400).json({ error: error.message, - type: "payment_evidence_error" + type: "payment_evidence_error", + errorCode: "payment_invalid" + }); + return; + } + + if (error instanceof ProviderTimeoutError) { + res.status(504).json({ + error: error.message, + type: "provider_timeout", + errorCode: "provider_timeout" + }); + return; + } + + if (error instanceof ProviderFailedError) { + res.status(502).json({ + error: error.message, + type: "provider_failed", + errorCode: "provider_failed" }); return; } res.status(500).json({ error: error.message, - type: "internal_error" + type: "internal_error", + errorCode: "internal_error" }); } ); diff --git a/apps/api/src/lib/payment-evidence.ts b/apps/api/src/lib/payment-evidence.ts index 74622c1..295d0c2 100644 --- a/apps/api/src/lib/payment-evidence.ts +++ b/apps/api/src/lib/payment-evidence.ts @@ -349,6 +349,7 @@ export async function persistPaymentEvidence( facilitatorResult: "facilitatorResult" in evidence ? toJsonRecord(evidence.facilitatorResult) : undefined, error: evidence.kind === "failed" ? evidence.error : undefined, + errorCode: evidence.kind === "failed" ? "payment_invalid" : undefined, createdAt: now }; diff --git a/apps/api/src/lib/persistence.ts b/apps/api/src/lib/persistence.ts index f50a0ca..c3c6f4d 100644 --- a/apps/api/src/lib/persistence.ts +++ b/apps/api/src/lib/persistence.ts @@ -27,6 +27,7 @@ export interface PersistPaidRequestInput { paymentResponseHeader: string | null; execution: ProviderExecutionMetadata; payerPublicKey?: string; + errorCode?: string; } export interface PersistSponsoredPaymentInput extends PersistPaidRequestInput { @@ -54,6 +55,7 @@ function buildPaymentAttempt( facilitatorUrl: config.X402_FACILITATOR_URL, status: "settled", transactionHash: input.paymentResponseHeader ?? undefined, + errorCode: input.errorCode as any, createdAt: now, ...overrides }; diff --git a/apps/api/src/lib/storage/serialization.ts b/apps/api/src/lib/storage/serialization.ts index cdb1558..a4fb243 100644 --- a/apps/api/src/lib/storage/serialization.ts +++ b/apps/api/src/lib/storage/serialization.ts @@ -217,9 +217,7 @@ export function rowToUsageEvent(row: Record): UsageEvent { : undefined, sponsorPublicKey: row.sponsor_public_key ? String(row.sponsor_public_key) : undefined, priceOutlier: priceOutlier || undefined, - priceOutlierReason: row.price_outlier_reason - ? String(row.price_outlier_reason) - : undefined + priceOutlierReason: row.price_outlier_reason ? String(row.price_outlier_reason) : undefined }; } @@ -246,7 +244,8 @@ export function paymentAttemptToRow(payment: PaymentAttempt) { sponsorship_grant_id: payment.sponsorshipGrantId ?? null, policy_decision: payment.policyDecision ?? null, payment_source: payment.paymentSource ?? null, - sponsor_public_key: payment.sponsorPublicKey ?? null + sponsor_public_key: payment.sponsorPublicKey ?? null, + error_code: payment.errorCode ?? null }; } @@ -277,6 +276,7 @@ export function rowToPaymentAttempt(row: Record): PaymentAttemp paymentSource: row.payment_source ? (row.payment_source as PaymentAttempt["paymentSource"]) : undefined, - sponsorPublicKey: row.sponsor_public_key ? String(row.sponsor_public_key) : undefined + sponsorPublicKey: row.sponsor_public_key ? String(row.sponsor_public_key) : undefined, + errorCode: row.error_code ? (row.error_code as PaymentAttempt["errorCode"]) : undefined }; } diff --git a/apps/api/src/lib/storage/sqlite/migrations.ts b/apps/api/src/lib/storage/sqlite/migrations.ts index 0b2e7bb..ce59255 100644 --- a/apps/api/src/lib/storage/sqlite/migrations.ts +++ b/apps/api/src/lib/storage/sqlite/migrations.ts @@ -84,6 +84,8 @@ ALTER TABLE usage_events ADD COLUMN execution_fallback_reason TEXT; ALTER TABLE usage_events ADD COLUMN execution_latency_estimate_ms INTEGER; ALTER TABLE usage_events ADD COLUMN execution_observed_duration_ms INTEGER; ALTER TABLE usage_events ADD COLUMN execution_circuit_breaker_state TEXT; +ALTER TABLE usage_events ADD COLUMN error_code TEXT; +ALTER TABLE payment_attempts ADD COLUMN error_code TEXT; ` }, { diff --git a/apps/api/src/lib/storage/sqlite/repository.ts b/apps/api/src/lib/storage/sqlite/repository.ts index 076dafe..d9fa4a3 100644 --- a/apps/api/src/lib/storage/sqlite/repository.ts +++ b/apps/api/src/lib/storage/sqlite/repository.ts @@ -44,12 +44,12 @@ INSERT INTO payment_attempts ( id, endpoint, provider_id, amount_usd, network, asset, amount, evidence_kind, payer_public_key, pay_to_address, facilitator_url, status, transaction_hash, facilitator_result, error, created_at, sponsorship_grant_id, policy_decision, - payment_source, sponsor_public_key + payment_source, sponsor_public_key, error_code ) VALUES ( @id, @endpoint, @provider_id, @amount_usd, @network, @asset, @amount, @evidence_kind, @payer_public_key, @pay_to_address, @facilitator_url, @status, @transaction_hash, @facilitator_result, @error, @created_at, @sponsorship_grant_id, @policy_decision, - @payment_source, @sponsor_public_key + @payment_source, @sponsor_public_key, @error_code ) `; diff --git a/apps/api/src/lib/x402.ts b/apps/api/src/lib/x402.ts index bcfa225..98a04d2 100644 --- a/apps/api/src/lib/x402.ts +++ b/apps/api/src/lib/x402.ts @@ -99,6 +99,7 @@ function demoMode402Middleware(req: Request, res: Response, next: NextFunction) return res.status(402).json({ error: "Payment Required", + errorCode: "payment_required", demoMode: true, debug, accepts: { @@ -174,7 +175,12 @@ export function createX402Middleware() { return { contentType: "application/json", - body: { error: "Payment settlement failed", type: "payment_settlement_failed", debug } + body: { + error: "Payment settlement failed", + type: "payment_settlement_failed", + errorCode: "payment_invalid", + debug + } }; }; diff --git a/apps/api/src/routes/protected.idempotency.test.ts b/apps/api/src/routes/protected.idempotency.test.ts index 8e21907..46ab219 100644 --- a/apps/api/src/routes/protected.idempotency.test.ts +++ b/apps/api/src/routes/protected.idempotency.test.ts @@ -2,10 +2,7 @@ import { randomUUID } from "node:crypto"; import express from "express"; import request from "supertest"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { - applySponsorshipTestEnv, - resetSponsorshipStore -} from "../test/sponsorship-test-helpers.js"; +import { applyApiTestEnv, resetApiTestStorage } from "../test/api-test-helpers.js"; const executeQueryMock = vi.fn(); @@ -66,17 +63,19 @@ function demoPaidRequest(app: express.Express) { } describe("x402 idempotency", () => { - let dbPath: string | undefined; + let analyticsDbPath: string | undefined; + let sponsorshipDbPath: string | undefined; beforeEach(() => { - dbPath = applySponsorshipTestEnv(); + ({ analyticsDbPath, sponsorshipDbPath } = applyApiTestEnv()); executeQueryMock.mockReset(); mockQueryResult(); }); afterEach(async () => { - await resetSponsorshipStore(dbPath); - dbPath = undefined; + await resetApiTestStorage(analyticsDbPath, sponsorshipDbPath); + analyticsDbPath = undefined; + sponsorshipDbPath = undefined; }); it("returns cached response for idempotent retries", async () => { diff --git a/apps/api/src/routes/protected.ts b/apps/api/src/routes/protected.ts index c9d6543..95bcdc1 100644 --- a/apps/api/src/routes/protected.ts +++ b/apps/api/src/routes/protected.ts @@ -8,7 +8,7 @@ export const protectedRouter = Router(); protectedRouter.get("/x402/search", async (req, res, next) => { const parsed = searchQuerySchema.safeParse(req.query); if (!parsed.success) { - return res.status(400).json({ error: parsed.error.flatten() }); + return res.status(400).json({ error: parsed.error.flatten(), errorCode: "invalid_query" }); } return handlePaidX402Route(req, res, next, { @@ -29,7 +29,7 @@ protectedRouter.get("/x402/search", async (req, res, next) => { protectedRouter.get("/x402/news", async (req, res, next) => { const parsed = newsQuerySchema.safeParse(req.query); if (!parsed.success) { - return res.status(400).json({ error: parsed.error.flatten() }); + return res.status(400).json({ error: parsed.error.flatten(), errorCode: "invalid_query" }); } return handlePaidX402Route(req, res, next, { @@ -50,7 +50,7 @@ protectedRouter.get("/x402/news", async (req, res, next) => { protectedRouter.get("/x402/scrape", async (req, res, next) => { const parsed = scrapeQuerySchema.safeParse(req.query); if (!parsed.success) { - return res.status(400).json({ error: parsed.error.flatten() }); + return res.status(400).json({ error: parsed.error.flatten(), errorCode: "invalid_query" }); } return handlePaidX402Route(req, res, next, { diff --git a/apps/api/src/routes/protected.validation.test.ts b/apps/api/src/routes/protected.validation.test.ts index a686643..791c069 100644 --- a/apps/api/src/routes/protected.validation.test.ts +++ b/apps/api/src/routes/protected.validation.test.ts @@ -5,9 +5,13 @@ import { applyApiTestEnv, resetApiTestStorage, TEST_WALLET } from "../test/api-t const executeQueryMock = vi.fn(); -vi.mock("../services/query-service.js", () => ({ - executeQuery: (...args: unknown[]) => executeQueryMock(...args) -})); +vi.mock("../services/query-service.js", async (importOriginal) => { + const original = await importOriginal(); + return { + ...original, + executeQuery: (...args: unknown[]) => executeQueryMock(...args) + }; +}); vi.mock("../lib/persistence.js", () => ({ persistPaymentAndUsage: vi.fn().mockResolvedValue(undefined), @@ -33,12 +37,13 @@ function mockQueryResult(mode: string, providerId: string, priceUsd: number) { }); } -describe("protected route validation", () => { +describe("protected route validation and error handling", () => { let analyticsDbPath: string; let sponsorshipDbPath: string; beforeEach(() => { ({ analyticsDbPath, sponsorshipDbPath } = applyApiTestEnv()); + executeQueryMock.mockReset(); }); afterEach(async () => { @@ -48,12 +53,43 @@ describe("protected route validation", () => { async function createValidationApp() { const { protectedRouter } = await import("../routes/protected.js"); + const { UnsafeScrapeUrlError } = await import("../lib/scrape-url-safety.js"); + const { PaymentEvidenceError } = await import("../lib/payment-evidence.js"); + const { ProviderTimeoutError, ProviderFailedError } = + await import("../services/query-service.js"); const app = express(); app.use(protectedRouter); + app.use((error: any, _req: any, res: any, _next: any) => { + if (error instanceof UnsafeScrapeUrlError) { + return res + .status(400) + .json({ error: error.message, type: "unsafe_scrape_url", errorCode: "invalid_query" }); + } + if (error instanceof PaymentEvidenceError) { + return res.status(400).json({ + error: error.message, + type: "payment_evidence_error", + errorCode: "payment_invalid" + }); + } + if (error instanceof ProviderTimeoutError) { + return res + .status(504) + .json({ error: error.message, type: "provider_timeout", errorCode: "provider_timeout" }); + } + if (error instanceof ProviderFailedError) { + return res + .status(502) + .json({ error: error.message, type: "provider_failed", errorCode: "provider_failed" }); + } + return res + .status(500) + .json({ error: error.message, type: "internal_error", errorCode: "internal_error" }); + }); return app; } - it("rejects invalid search query input", async () => { + it("rejects invalid search query input with invalid_query", async () => { const app = await createValidationApp(); const missingQuery = await request(app).get("/x402/search").query({ provider: "search.basic" }); @@ -62,19 +98,21 @@ describe("protected route validation", () => { .query({ provider: "search.basic", q: "x" }); expect(missingQuery.status).toBe(400); + expect(missingQuery.body.errorCode).toBe("invalid_query"); expect(shortQuery.status).toBe(400); + expect(shortQuery.body.errorCode).toBe("invalid_query"); }); - it("rejects invalid news query input", async () => { + it("rejects invalid news query input with invalid_query", async () => { const app = await createValidationApp(); const response = await request(app).get("/x402/news").query({ provider: "news.fast" }); expect(response.status).toBe(400); - expect(response.body.error).toBeDefined(); + expect(response.body.errorCode).toBe("invalid_query"); }); - it("rejects invalid scrape query input", async () => { + it("rejects invalid scrape query input with invalid_query", async () => { const app = await createValidationApp(); const missingUrl = await request(app).get("/x402/scrape").query({ provider: "scrape.page" }); @@ -83,7 +121,35 @@ describe("protected route validation", () => { .query({ provider: "scrape.page", url: "not-a-url" }); expect(missingUrl.status).toBe(400); + expect(missingUrl.body.errorCode).toBe("invalid_query"); expect(invalidUrl.status).toBe(400); + expect(invalidUrl.body.errorCode).toBe("invalid_query"); + }); + + it("handles provider timeout and returns provider_timeout", async () => { + const { ProviderTimeoutError } = await import("../services/query-service.js"); + const app = await createValidationApp(); + executeQueryMock.mockRejectedValueOnce(new ProviderTimeoutError("Request timed out")); + + const response = await request(app) + .get("/x402/search") + .query({ provider: "search.basic", q: "valid query" }); + + expect(response.status).toBe(504); + expect(response.body.errorCode).toBe("provider_timeout"); + }); + + it("handles provider failure and returns provider_failed", async () => { + const { ProviderFailedError } = await import("../services/query-service.js"); + const app = await createValidationApp(); + executeQueryMock.mockRejectedValueOnce(new ProviderFailedError("Provider down")); + + const response = await request(app) + .get("/x402/search") + .query({ provider: "search.basic", q: "valid query" }); + + expect(response.status).toBe(502); + expect(response.body.errorCode).toBe("provider_failed"); }); }); diff --git a/apps/api/src/routes/public.test.ts b/apps/api/src/routes/public.test.ts index 874a57e..dcb3186 100644 --- a/apps/api/src/routes/public.test.ts +++ b/apps/api/src/routes/public.test.ts @@ -271,8 +271,18 @@ describe("public routes", () => { const { persistPaymentAndUsage } = await import("../lib/persistence.js"); await persistPaymentAndUsage( buildPaidQueryFixture({ - payment: { id: "pay_fixture_demo_01", status: "demo-paid", evidenceKind: "demo", transactionHash: undefined }, - usage: { id: "use_fixture_demo_01", paymentStatus: "demo-paid", paymentKind: "demo", paymentTxHash: undefined } + payment: { + id: "pay_fixture_demo_01", + status: "demo-paid", + evidenceKind: "demo", + transactionHash: undefined + }, + usage: { + id: "use_fixture_demo_01", + paymentStatus: "demo-paid", + paymentKind: "demo", + paymentTxHash: undefined + } }) ); diff --git a/apps/api/src/services/query-service.ts b/apps/api/src/services/query-service.ts index cbaf59f..72f82d0 100644 --- a/apps/api/src/services/query-service.ts +++ b/apps/api/src/services/query-service.ts @@ -35,6 +35,20 @@ function sanitizeErrorMessage(message: string): string { .replace(/https?:\/\/\S+/gi, "[redacted-url]"); } +export class ProviderTimeoutError extends Error { + constructor(message: string) { + super(message); + this.name = "ProviderTimeoutError"; + } +} + +export class ProviderFailedError extends Error { + constructor(message: string) { + super(message); + this.name = "ProviderFailedError"; + } +} + export async function executeQuery(params: { mode: "search" | "news" | "scrape"; provider: string; @@ -66,7 +80,11 @@ export async function executeQuery(params: { }, "provider execution failed" ); - throw error; + const msg = getErrorMessage(error); + if (msg.toLowerCase().includes("timeout")) { + throw new ProviderTimeoutError(msg); + } + throw new ProviderFailedError(msg); } const latencyMs = execution.execution.observedDurationMs; diff --git a/apps/api/src/test/storage-test-helpers.ts b/apps/api/src/test/storage-test-helpers.ts index d91ede4..3b11748 100644 --- a/apps/api/src/test/storage-test-helpers.ts +++ b/apps/api/src/test/storage-test-helpers.ts @@ -75,10 +75,12 @@ export function buildTestPaymentAttempt(overrides: Partial = {}) }; } -export function buildPaidQueryFixture(overrides: { - payment?: Partial; - usage?: Partial; -} = {}): PaidQueryFixture { +export function buildPaidQueryFixture( + overrides: { + payment?: Partial; + usage?: Partial; + } = {} +): PaidQueryFixture { const payment: PaymentAttempt = { id: "pay_fixture_0001", endpoint: "/x402/search", diff --git a/package-lock.json b/package-lock.json index 96cb307..7d34e47 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,7 @@ "eslint-config-prettier": "^10.1.5", "prettier": "^3.5.3", "typescript": "^5.8.3", - "typescript-eslint": "^8.32.1" + "typescript-eslint": "^8.62.1" }, "engines": { "node": ">=20.0.0 <25.0.0" @@ -93,7 +93,7 @@ "@types/node": "^22.20.0", "@types/react": "^18.3.20", "@types/react-dom": "^18.3.7", - "@vitejs/plugin-react": "^4.4.1", + "@vitejs/plugin-react": "4.3.4", "autoprefixer": "^10.4.21", "postcss": "^8.5.3", "tailwindcss": "^3.4.17", @@ -105,6 +105,52 @@ "@rollup/rollup-linux-x64-gnu": "^4.60.1" } }, + "apps/web/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "apps/web/node_modules/@vitejs/plugin-react": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.3.4.tgz", + "integrity": "sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.26.0", + "@babel/plugin-transform-react-jsx-self": "^7.25.9", + "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0" + } + }, + "apps/web/node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@adraffy/ens-normalize": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", @@ -480,7 +526,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -498,7 +543,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -516,7 +560,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -534,7 +577,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -552,7 +594,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -570,7 +611,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -588,7 +628,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -606,7 +645,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -624,7 +662,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -642,7 +679,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -660,7 +696,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -678,7 +713,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -696,7 +730,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -714,7 +747,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -732,7 +764,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -750,7 +781,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -768,7 +798,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -786,7 +815,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -804,7 +832,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -822,7 +849,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -840,7 +866,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -858,7 +883,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -876,7 +900,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -894,7 +917,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -912,7 +934,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1343,13 +1364,6 @@ "node": ">=14.0.0" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.62.2", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", @@ -1399,6 +1413,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1594,7 +1609,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1768,8 +1782,6 @@ }, "node_modules/@stellar/freighter-api": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@stellar/freighter-api/-/freighter-api-6.0.1.tgz", - "integrity": "sha512-eqwakEqSg+zoLuPpSbKyrX0pG8DQFzL/J5GtbfuMCmJI+h+oiC9pQ5C6QLc80xopZQKdGt8dUAFCmDMNdAG95w==", "license": "Apache-2.0", "dependencies": { "buffer": "6.0.3", @@ -1778,8 +1790,6 @@ }, "node_modules/@stellar/freighter-api/node_modules/semver": { "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1790,8 +1800,6 @@ }, "node_modules/@stellar/js-xdr": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-4.0.0.tgz", - "integrity": "sha512-+NmNa7Tk5BI5XFdy/6xGTqAN4J9a9KgCrCGhj2uEUTCBhLkch0M+QbKzNH8zEnejWe0p8w+0q5hUVX6L3OzoVA==", "license": "Apache-2.0", "engines": { "node": ">=20.0.0", @@ -1800,9 +1808,6 @@ }, "node_modules/@stellar/stellar-base": { "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-15.0.0.tgz", - "integrity": "sha512-XQhxUr9BYiEcFcgc4oWcCMR9QJCny/GmmGsuwPKf/ieIcOeb5149KLHYx9mJCA0ea8QbucR2/GzV58QbXOTxQA==", - "deprecated": "This package is now rolled into @stellar/stellar-sdk. Please use @stellar/stellar-sdk to continue receiving updates and support.", "license": "Apache-2.0", "dependencies": { "@noble/curves": "^1.9.7", @@ -1817,13 +1822,11 @@ } }, "node_modules/@stellar/stellar-sdk": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-15.1.0.tgz", - "integrity": "sha512-GsJUcWx2yboVzYdhTe/LHS3V1wVLSHkUkglC5bBoYWGJt31vzIhbSGno60NP9CdCTNkLJdnrsLJ63oA58Zvh5A==", + "version": "15.0.1", "license": "Apache-2.0", "dependencies": { "@stellar/stellar-base": "^15.0.0", - "axios": "1.15.0", + "axios": "1.14.0", "bignumber.js": "^9.3.1", "commander": "^14.0.3", "eventsource": "^2.0.2", @@ -1841,8 +1844,6 @@ }, "node_modules/@types/babel__core": { "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", "dev": true, "license": "MIT", "dependencies": { @@ -1855,8 +1856,6 @@ }, "node_modules/@types/babel__generator": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", "dev": true, "license": "MIT", "dependencies": { @@ -1865,8 +1864,6 @@ }, "node_modules/@types/babel__template": { "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", "dev": true, "license": "MIT", "dependencies": { @@ -1876,8 +1873,6 @@ }, "node_modules/@types/babel__traverse": { "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { @@ -1886,8 +1881,6 @@ }, "node_modules/@types/better-sqlite3": { "version": "7.6.13", - "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", - "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", "dev": true, "license": "MIT", "dependencies": { @@ -1896,8 +1889,6 @@ }, "node_modules/@types/body-parser": { "version": "1.19.6", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", - "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", "dev": true, "license": "MIT", "dependencies": { @@ -1907,8 +1898,6 @@ }, "node_modules/@types/chai": { "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", "dev": true, "license": "MIT", "dependencies": { @@ -1918,8 +1907,6 @@ }, "node_modules/@types/connect": { "version": "3.4.38", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", - "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", "dev": true, "license": "MIT", "dependencies": { @@ -1928,15 +1915,11 @@ }, "node_modules/@types/cookiejar": { "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", - "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true, "license": "MIT" }, "node_modules/@types/cors": { "version": "2.8.19", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", - "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "dev": true, "license": "MIT", "dependencies": { @@ -1945,22 +1928,16 @@ }, "node_modules/@types/deep-eql": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", "dev": true, "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "version": "1.0.8", "dev": true, "license": "MIT" }, "node_modules/@types/express": { "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", - "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", "dependencies": { @@ -1971,8 +1948,6 @@ }, "node_modules/@types/express-serve-static-core": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.1.tgz", - "integrity": "sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==", "dev": true, "license": "MIT", "dependencies": { @@ -1984,8 +1959,6 @@ }, "node_modules/@types/http-errors": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", - "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", "dev": true, "license": "MIT" }, @@ -1998,8 +1971,6 @@ }, "node_modules/@types/methods": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", - "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", "dev": true, "license": "MIT" }, @@ -2015,29 +1986,21 @@ }, "node_modules/@types/prop-types": { "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", "dev": true, "license": "MIT" }, "node_modules/@types/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", + "version": "6.15.0", "dev": true, "license": "MIT" }, "node_modules/@types/range-parser": { "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", - "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true, "license": "MIT" }, "node_modules/@types/react": { - "version": "18.3.31", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.31.tgz", - "integrity": "sha512-vfEqpXTvwT91yhmwdfouStN2hSKwTvyRs8qpLfADyrq/kxDw0hZM7Wk9Ug1FELj8hIby+S/+kQCSRFF32nv2Qw==", + "version": "18.3.28", "dev": true, "license": "MIT", "dependencies": { @@ -2047,8 +2010,6 @@ }, "node_modules/@types/react-dom": { "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", "dev": true, "license": "MIT", "peerDependencies": { @@ -2057,8 +2018,6 @@ }, "node_modules/@types/send": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", - "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2067,8 +2026,6 @@ }, "node_modules/@types/serve-static": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", - "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2078,8 +2035,6 @@ }, "node_modules/@types/superagent": { "version": "8.1.10", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.10.tgz", - "integrity": "sha512-nbt4IWXABhW0jGmmpRzCFNlbmwCTzZ2gTUsNIr+X+ItdqPms+PAJZbWsNzpS2USqXjcoNLQcO6nXo60zcPQiIg==", "dev": true, "license": "MIT", "dependencies": { @@ -2091,8 +2046,6 @@ }, "node_modules/@types/supertest": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", - "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", "dev": true, "license": "MIT", "dependencies": { @@ -2101,374 +2054,265 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz", - "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz", + "integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==", "dev": true, "license": "MIT", "dependencies": { - "@noble/hashes": "^1.7.0", - "apg-js": "^4.4.0" - } - }, - "node_modules/@stellar/freighter-api": { - "version": "6.0.1", - "license": "Apache-2.0", - "dependencies": { - "buffer": "6.0.3", - "semver": "7.7.1" - } - }, - "node_modules/@stellar/freighter-api/node_modules/semver": { - "version": "7.7.1", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/type-utils": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.62.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@stellar/js-xdr": { - "version": "4.0.0", - "license": "Apache-2.0", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=20.0.0", - "pnpm": ">=9.0.0" + "node": ">= 4" } }, - "node_modules/@stellar/stellar-base": { - "version": "15.0.0", - "license": "Apache-2.0", + "node_modules/@typescript-eslint/parser": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz", + "integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==", + "dev": true, + "license": "MIT", "dependencies": { - "@noble/curves": "^1.9.7", - "@stellar/js-xdr": "^4.0.0", - "base32.js": "^0.1.0", - "bignumber.js": "^9.3.1", - "buffer": "^6.0.3", - "sha.js": "^2.4.12" + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3" }, "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@stellar/stellar-sdk": { - "version": "15.0.1", - "license": "Apache-2.0", - "dependencies": { - "@stellar/stellar-base": "^15.0.0", - "axios": "1.14.0", - "bignumber.js": "^9.3.1", - "commander": "^14.0.3", - "eventsource": "^2.0.2", - "feaxios": "^0.0.23", - "randombytes": "^2.1.0", - "toml": "^3.0.0", - "urijs": "^1.19.11" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "bin": { - "stellar-js": "bin/stellar-js" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz", + "integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz", + "integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz", + "integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", + "node_modules/@typescript-eslint/types": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz", + "integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@types/better-sqlite3": { - "version": "7.6.13", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz", + "integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@typescript-eslint/project-service": "8.62.1", + "@typescript-eslint/tsconfig-utils": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/visitor-keys": "8.62.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/body-parser": { - "version": "1.19.6", + "node_modules/@typescript-eslint/typescript-estree/node_modules/@typescript-eslint/project-service": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz", + "integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==", "dev": true, "license": "MIT", "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "@typescript-eslint/tsconfig-utils": "^8.62.1", + "@typescript-eslint/types": "^8.62.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/chai": { - "version": "5.2.3", + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/@types/connect": { - "version": "3.4.38", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz", + "integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/@types/cookiejar": { - "version": "2.1.5", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "MIT" + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "node_modules/@types/cors": { - "version": "2.8.19", + "node_modules/@typescript-eslint/utils": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz", + "integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.62.1", + "@typescript-eslint/types": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "5.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/methods": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "22.20.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.0.tgz", - "integrity": "sha512-QWlFW2wf3nTjC13/DqRnBpR4ZO36VJH/JVBkA/vcnmbTBNQIlnObqyqZE1tUR7+Ni23Lda8R1BxMfbXRpCUx5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/qs": { - "version": "6.15.0", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.28", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "18.3.7", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^18.0.0" - } - }, - "node_modules/@types/send": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" - } - }, - "node_modules/@types/superagent": { - "version": "8.1.10", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/cookiejar": "^2.1.5", - "@types/methods": "^1.1.4", - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/@types/supertest": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/methods": "^1.1.4", - "@types/superagent": "^8.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz", - "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/type-utils": "8.62.0", - "@typescript-eslint/utils": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.62.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz", - "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", - "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.0", - "@typescript-eslint/types": "^8.62.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz", - "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz", + "integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0" + "@typescript-eslint/types": "8.62.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2478,21 +2322,17 @@ "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "url": "https://opencollective.com/eslint" } }, "node_modules/@vitest/coverage-v8": { @@ -2789,36 +2629,6 @@ "integrity": "sha512-AJ9dSeaUGj2xu7tEwmdqb51dqdb633xo4njI9K8ZFfcLrNr0XN8/EPkkZUNaF9fkCblGt2zVwZymesUdGynEkQ==", "license": "MIT" }, - "node_modules/@x402/stellar/node_modules/eventsource": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-4.1.0.tgz", - "integrity": "sha512-2GuF51iuHX6A9xdTccMTsNb7VO0lHZihApxhvQzJB5A03DvHDd2FQepodbMaztPBmBcE/ox7o2gqaxGhYB9LhQ==", - "license": "MIT", - "dependencies": { - "eventsource-parser": "^3.0.1" - }, - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@x402/stellar/node_modules/axios": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz", - "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "https-proxy-agent": "^5.0.1", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/@x402/stellar/node_modules/bignumber.js": { - "version": "11.1.4", - "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-11.1.4.tgz", - "integrity": "sha512-AJ9dSeaUGj2xu7tEwmdqb51dqdb633xo4njI9K8ZFfcLrNr0XN8/EPkkZUNaF9fkCblGt2zVwZymesUdGynEkQ==", - "license": "MIT" - }, "node_modules/@x402/stellar/node_modules/eventsource": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-4.1.0.tgz", @@ -4010,20 +3820,6 @@ "@esbuild/win32-x64": "0.28.1" } }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/escalade": { "version": "3.2.0", "dev": true, @@ -6536,16 +6332,6 @@ "react": "^18.3.1" } }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-router": { "version": "6.30.4", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.4.tgz", @@ -7767,16 +7553,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.0.tgz", - "integrity": "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==", + "version": "8.62.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz", + "integrity": "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.62.0", - "@typescript-eslint/parser": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/utils": "8.62.0" + "@typescript-eslint/eslint-plugin": "8.62.1", + "@typescript-eslint/parser": "8.62.1", + "@typescript-eslint/typescript-estree": "8.62.1", + "@typescript-eslint/utils": "8.62.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -8399,295 +8185,6 @@ "node": ">=12" } }, - "node_modules/vite/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vite/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/vite/node_modules/esbuild": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", diff --git a/package.json b/package.json index 3a6eb0f..77d5955 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,6 @@ "eslint-config-prettier": "^10.1.5", "prettier": "^3.5.3", "typescript": "^5.8.3", - "typescript-eslint": "^8.32.1" + "typescript-eslint": "^8.62.1" } } diff --git a/packages/shared/src/schemas.ts b/packages/shared/src/schemas.ts index 2fe1fa0..c1eaa2a 100644 --- a/packages/shared/src/schemas.ts +++ b/packages/shared/src/schemas.ts @@ -104,3 +104,12 @@ export const sponsorshipPreviewResponseSchema = z.object({ }), reason: z.string().optional() }); + +export const paidRouteErrorCodeSchema = z.enum([ + "payment_required", + "payment_invalid", + "provider_timeout", + "provider_failed", + "invalid_query", + "internal_error" +]); diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 13abdac..b3faf03 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -1,3 +1,6 @@ +import { z } from "zod"; +import { paidRouteErrorCodeSchema } from "./schemas.js"; + export type QueryMode = "search" | "news" | "scrape"; export type ProviderCategory = QueryMode; export type SourceType = "live" | "deterministic-fallback" | "unavailable"; @@ -10,6 +13,7 @@ export type ExecutionFallbackReason = | "missing-fallback"; export type CircuitBreakerState = "closed" | "half-open" | "open"; export type PaymentSource = "sponsored" | "wallet" | "demo"; +export type PaidRouteErrorCode = z.infer; export interface ProviderExecutionMetadata { providerId: string; @@ -103,6 +107,7 @@ export interface PaymentAttempt { policyDecision?: string; paymentSource?: PaymentSource; sponsorPublicKey?: string; + errorCode?: PaidRouteErrorCode; } export interface AnalyticsSummary { diff --git a/scripts/check-payment-leaks.mjs b/scripts/check-payment-leaks.mjs index 050607d..90672fe 100644 --- a/scripts/check-payment-leaks.mjs +++ b/scripts/check-payment-leaks.mjs @@ -45,7 +45,8 @@ function findLeaks(content) { } // 4. Facilitator API Key - const facilitatorKeyRegex = /(?:facilitator.*api.*key|x402.*facilitator.*api.*key)\s*[:=]\s*["']?([a-zA-Z0-9_\-]+)["']?/gi; + const facilitatorKeyRegex = + /(?:facilitator.*api.*key|x402.*facilitator.*api.*key)\s*[:=]\s*["']?([a-zA-Z0-9_\-]+)["']?/gi; while ((match = facilitatorKeyRegex.exec(line)) !== null) { const val = match[1]; const isRedacted = @@ -61,7 +62,8 @@ function findLeaks(content) { } // 5. X-Payment Header or payment-response header - const paymentHeaderRegex = /(?:x-payment|payment-response|x-payment-response)\s*[:=]\s*["']?([a-zA-Z0-9_\-\[\]\+\/=]+)["']?/gi; + const paymentHeaderRegex = + /(?:x-payment|payment-response|x-payment-response)\s*[:=]\s*["']?([a-zA-Z0-9_\-\[\]\+\/=]+)["']?/gi; while ((match = paymentHeaderRegex.exec(line)) !== null) { const val = match[1]; const isRedacted = @@ -71,7 +73,9 @@ function findLeaks(content) { val.endsWith("]") || val.startsWith("demo_tx_") || val.startsWith("demo-proof-") || - ["none", "", "tx_test", "proof_123", "demo-proof-news", "demo-proof-scrape"].includes(val.toLowerCase()); + ["none", "", "tx_test", "proof_123", "demo-proof-news", "demo-proof-scrape"].includes( + val.toLowerCase() + ); if (!isRedacted) { leaks.push({ lineNum, pattern: "X-Payment Header", match: val }); } @@ -150,12 +154,17 @@ function runSelfTest() { for (const fail of failingCases) { const leaks = findLeaks(fail.text); if (leaks.length === 0) { - console.error(`Self-test failed: Expected leak for pattern "${fail.pattern}" in "${fail.text}", but none found.`); + console.error( + `Self-test failed: Expected leak for pattern "${fail.pattern}" in "${fail.text}", but none found.` + ); process.exit(1); } const hasPattern = leaks.some((l) => l.pattern === fail.pattern); if (!hasPattern) { - console.error(`Self-test failed: Expected leak of type "${fail.pattern}" in "${fail.text}", but found different type:`, leaks); + console.error( + `Self-test failed: Expected leak of type "${fail.pattern}" in "${fail.text}", but found different type:`, + leaks + ); process.exit(1); } }