diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8d691d2..c8caf97 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,8 +38,11 @@ jobs: node-version-file: .nvmrc cache: npm + - name: Enforce npm version + run: npm install -g npm@11.9.0 + - name: Install dependencies - run: npm ci + run: npm ci --no-audit --no-fund - name: Typecheck run: npm run typecheck @@ -132,4 +135,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/transcript.ts b/apps/agent-client/src/transcript.ts index aaea821..77604c6 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, // 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 ]; 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" // raw tx ID; replaced with presence flag below ]); 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,9 @@ 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[] = []; @@ -225,24 +227,24 @@ async function step3PaidQueries(): Promise { 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, + provider: q.provider, + endpoint: response.endpoint, // Never write the raw payment-response header value; record presence only 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 +256,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 +284,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 +310,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 +325,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 +350,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 }; } @@ -397,4 +399,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..ffc1de5 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."); } diff --git a/apps/api/src/lib/payment-evidence.ts b/apps/api/src/lib/payment-evidence.ts index 74622c1..9a62f9e 100644 --- a/apps/api/src/lib/payment-evidence.ts +++ b/apps/api/src/lib/payment-evidence.ts @@ -55,6 +55,7 @@ type EvidenceBase = { amount?: string; payTo: string; facilitatorUrl: string; + capturedAt?: string; }; export type PaidRequestRecord = { @@ -192,7 +193,8 @@ export function buildEvidenceFromRequirements(input: { asset: input.requirements.asset, amount: input.requirements.amount, payTo: input.requirements.payTo, - facilitatorUrl: config.X402_FACILITATOR_URL + facilitatorUrl: config.X402_FACILITATOR_URL, + capturedAt: new Date().toISOString() }; if (input.failure) { @@ -297,7 +299,8 @@ export function buildDemoPaymentEvidence(req: Request): DemoPaymentEvidence { network: config.STELLAR_NETWORK, payTo: config.X402_PAY_TO_ADDRESS, facilitatorUrl: config.X402_FACILITATOR_URL, - payer: req.header("x-demo-payer") ?? "demo-agent" + payer: req.header("x-demo-payer") ?? "demo-agent", + capturedAt: new Date().toISOString() }; } diff --git a/apps/api/src/lib/storage/serialization.ts b/apps/api/src/lib/storage/serialization.ts index cdb1558..5c6f9dd 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 }; } 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/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/apps/web/package.json b/apps/web/package.json index cb703da..30f3476 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -10,6 +10,7 @@ "build": "tsc -p tsconfig.json --noEmit && vite build", "preview": "vite preview", "typecheck": "tsc -p tsconfig.json --noEmit", + "test": "vitest run", "lint": "eslint src vite.config.ts", "format": "prettier --write src vite.config.ts tailwind.config.ts", "format:check": "prettier --check src vite.config.ts tailwind.config.ts" @@ -35,7 +36,8 @@ "postcss": "^8.5.3", "tailwindcss": "^3.4.17", "typescript": "^5.8.3", - "vite": "^5.4.19" + "vite": "^5.4.19", + "vitest": "^3.2.6" }, "optionalDependencies": { "@esbuild/linux-x64": "^0.21.5", diff --git a/apps/web/src/components/FreshnessBadge.test.tsx b/apps/web/src/components/FreshnessBadge.test.tsx new file mode 100644 index 0000000..908d9bd --- /dev/null +++ b/apps/web/src/components/FreshnessBadge.test.tsx @@ -0,0 +1,145 @@ +import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; +import { renderToStaticMarkup } from "react-dom/server"; +import { FreshnessBadge, FreshnessBadgeView } from "./FreshnessBadge.js"; +import { + DEFAULT_STALE_THRESHOLD_MS, + deriveFreshness, + type FreshnessView +} from "../lib/freshness.js"; + +const NOW = Date.parse("2026-06-29T12:00:00.000Z"); + +function minutes(min: number) { + return min * 60_000; +} + +describe("FreshnessBadge", () => { + beforeAll(() => { + vi.useFakeTimers(); + vi.setSystemTime(new Date(NOW)); + }); + afterAll(() => { + vi.useRealTimers(); + }); + + it("renders fresh state with elapsed time and copy for settled evidence", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("freshness-badge--fresh"); + expect(html).toContain("Fresh settled proof"); + expect(html).toContain("7s ago"); + expect(html).toContain('data-state="fresh"'); + expect(html).toContain('data-kind="settled"'); + }); + + it("renders stale state when proof exceeds the threshold", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("freshness-badge--stale"); + expect(html).toContain("Stale verified proof"); + expect(html).toContain("8m ago"); + expect(html).toContain('data-state="stale"'); + expect(html).toContain('data-kind="verified"'); + }); + + it("renders unavailable state when no capturedAt is provided", () => { + const html = renderToStaticMarkup(); + + expect(html).toContain("freshness-badge--unavailable"); + expect(html).toContain("Proof timestamp unavailable"); + expect(html).toContain('data-state="unavailable"'); + expect(html).toContain('data-kind="settled"'); + expect(html).not.toMatch(/settled proof/i); + }); + + it("renders demo-flavored copy for fresh demo evidence", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("freshness-badge--demo"); + expect(html).toContain("freshness-badge--fresh"); + expect(html).toMatch(/Demo evidence · Fresh/); + expect(html).toContain('data-kind="demo"'); + expect(html).not.toMatch(/settled proof/i); + }); + + it("uses default threshold when no threshold is provided", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("freshness-badge--stale"); + }); + + it("renders failed-evidence copy without implying successful settlement", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).toContain("Failed proof"); + expect(html).toContain('data-kind="failed"'); + expect(html).not.toMatch(/settled proof/i); + expect(html).not.toMatch(/verified proof/i); + }); + + it("emits 'unknown' data-kind when no kind is provided with a timestamp", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).toContain('data-kind="unknown"'); + expect(html).toContain("freshness-badge--fresh"); + }); + + it("renders aria-label and title with the tooltip text", () => { + const html = renderToStaticMarkup( + + ); + + expect(html).toContain('aria-live="polite"'); + expect(html).toContain('role="status"'); + expect(html).toMatch(/title="[^"]+"/); + expect(html).toMatch(/aria-label="[^"]+"/); + }); + + it("renders the same data attributes via FreshnessBadgeView as a hand-built view", () => { + const view: FreshnessView = deriveFreshness({ + kind: "settled", + capturedAt: new Date(NOW - 1_000).toISOString(), + now: NOW + }); + const html = renderToStaticMarkup(); + expect(html).toContain('data-state="fresh"'); + expect(html).toContain("Fresh settled proof"); + }); +}); + +describe("FreshnessBadge production API", () => { + it("does not expose test knobs in the runtime props contract", () => { + // Compile-time: production props type must contain only "kind" and "capturedAt". + // Any future (even optional) prop addition trips this assertion via a type error + // at the `_check` assignment site. + type AllowedProps = "kind" | "capturedAt"; + type _Extras = + Exclude[0], AllowedProps> extends never + ? true + : false; + const _check: _Extras = true; + void _check; + + // Runtime: defense-in-depth so the contract cannot be widened silently at runtime. + const props: Parameters[0] = { + kind: "settled", + capturedAt: "2026-06-29T12:00:00.000Z" + }; + expect(Object.keys(props).sort().join(",")).toBe("capturedAt,kind"); + }); +}); diff --git a/apps/web/src/components/FreshnessBadge.tsx b/apps/web/src/components/FreshnessBadge.tsx new file mode 100644 index 0000000..c9fb8cd --- /dev/null +++ b/apps/web/src/components/FreshnessBadge.tsx @@ -0,0 +1,45 @@ +import { deriveFreshness, type FreshnessView } from "../lib/freshness.js"; + +export interface FreshnessBadgeProps { + /** Free-form kind discriminator; narrows to known state copy at runtime via + * `deriveFreshness`. Typed as string so any new evidence kind value passes + * through without a type change. */ + kind?: string; + /** ISO timestamp captured at evidence build time. Missing/null/invalid → unavailable. */ + capturedAt?: string | null; +} + +export function FreshnessBadge({ kind, capturedAt }: FreshnessBadgeProps) { + const view = deriveFreshness({ kind, capturedAt }); + return ; +} + +export interface FreshnessBadgeViewProps { + view: FreshnessView; +} + +export function FreshnessBadgeView({ view }: FreshnessBadgeViewProps) { + const className = [ + "freshness-badge", + `freshness-badge--${view.state}`, + view.kind ? `freshness-badge--kind-${view.kind}` : null, + view.isDemo ? "freshness-badge--demo" : null + ] + .filter(Boolean) + .join(" "); + + return ( + + + {view.label} + + ); +} diff --git a/apps/web/src/lib/freshness.test.ts b/apps/web/src/lib/freshness.test.ts new file mode 100644 index 0000000..f96fb49 --- /dev/null +++ b/apps/web/src/lib/freshness.test.ts @@ -0,0 +1,221 @@ +import { describe, expect, it } from "vitest"; +import { + DEFAULT_STALE_THRESHOLD_MS, + deriveFreshness, + type DeriveFreshnessInput +} from "./freshness.js"; + +const NOW = Date.parse("2026-06-29T12:00:00.000Z"); + +function minutes(min: number) { + return min * 60_000; +} + +function build(overrides: Partial = {}): DeriveFreshnessInput { + return { + now: NOW, + ...overrides + }; +} + +describe("deriveFreshness", () => { + describe("unknown kind fallback", () => { + it("renders generic copy for an unrecognized kind value", () => { + const view = deriveFreshness( + build({ + kind: "refunded", + capturedAt: new Date(NOW - 4_000).toISOString() + }) + ); + expect(view.state).toBe("fresh"); + expect(view.isDemo).toBe(false); + expect(view.label).toMatch(/Fresh proof/); + expect(view.label).not.toMatch(/refunded/); + expect(view.label).toContain("4s ago"); + }); + + it("renders generic stale copy for an unrecognized kind value", () => { + const view = deriveFreshness( + build({ + kind: "refunded", + capturedAt: new Date(NOW - minutes(7)).toISOString() + }) + ); + expect(view.state).toBe("stale"); + expect(view.label).toMatch(/Stale proof/); + expect(view.label).not.toMatch(/refunded/); + }); + }); + + describe("missing proof timestamp", () => { + it("renders as unavailable (not fresh) when capturedAt is missing", () => { + const view = deriveFreshness(build({ kind: "settled" })); + expect(view.state).toBe("unavailable"); + expect(view.ageMs).toBeNull(); + expect(view.isDemo).toBe(false); + expect(view.label).toContain("Proof timestamp unavailable"); + }); + + it("renders as unavailable when capturedAt is an empty string", () => { + const view = deriveFreshness(build({ kind: "verified", capturedAt: "" })); + expect(view.state).toBe("unavailable"); + expect(view.ageMs).toBeNull(); + }); + + it("renders as unavailable when capturedAt is null", () => { + const view = deriveFreshness(build({ kind: "verified", capturedAt: null })); + expect(view.state).toBe("unavailable"); + expect(view.ageMs).toBeNull(); + }); + + it("renders as unavailable when capturedAt is malformed", () => { + const view = deriveFreshness(build({ kind: "settled", capturedAt: "not-a-timestamp" })); + expect(view.state).toBe("unavailable"); + expect(view.ageMs).toBeNull(); + }); + + it("does not imply settlement in tooltip when unavailable", () => { + const view = deriveFreshness(build({ kind: "settled" })); + expect(view.tooltip).not.toMatch(/settled/i); + expect(view.tooltip).not.toMatch(/on-chain/i); + }); + + it("uses demo-flavored copy when unavailable for demo evidence", () => { + const view = deriveFreshness(build({ kind: "demo" })); + expect(view.state).toBe("unavailable"); + expect(view.isDemo).toBe(true); + expect(view.label.toLowerCase()).toContain("demo"); + expect(view.tooltip.toLowerCase()).not.toMatch(/settled/i); + }); + + it("uses failed-flavored copy when unavailable for failed evidence", () => { + const view = deriveFreshness(build({ kind: "failed" })); + expect(view.state).toBe("unavailable"); + expect(view.label.toLowerCase()).toContain("failed"); + expect(view.label.toLowerCase()).not.toMatch(/demo/); + }); + }); + + describe("fresh proof", () => { + it("returns fresh state for proof captured seconds ago", () => { + const view = deriveFreshness( + build({ + kind: "settled", + capturedAt: new Date(NOW - 12_000).toISOString() + }) + ); + expect(view.state).toBe("fresh"); + expect(view.ageMs).toBe(12_000); + expect(view.label).toContain("12s ago"); + expect(view.isDemo).toBe(false); + expect(view.tooltip).toMatch(/recent/i); + }); + + it("disambiguates verified and settled copy by kind", () => { + const verifiedView = deriveFreshness( + build({ kind: "verified", capturedAt: new Date(NOW - 4_000).toISOString() }) + ); + const settledView = deriveFreshness( + build({ kind: "settled", capturedAt: new Date(NOW - 4_000).toISOString() }) + ); + expect(verifiedView.label.toLowerCase()).toContain("verified proof"); + expect(settledView.label.toLowerCase()).toContain("settled proof"); + }); + + it("returns fresh state at the threshold boundary (just inside)", () => { + const view = deriveFreshness( + build({ + kind: "verified", + capturedAt: new Date(NOW - (DEFAULT_STALE_THRESHOLD_MS - 1)).toISOString() + }) + ); + expect(view.state).toBe("fresh"); + }); + + it("shows demo-flavored label even when fresh", () => { + const view = deriveFreshness( + build({ + kind: "demo", + capturedAt: new Date(NOW - 8_000).toISOString() + }) + ); + expect(view.state).toBe("fresh"); + expect(view.isDemo).toBe(true); + expect(view.label.toLowerCase()).toContain("demo"); + expect(view.tooltip.toLowerCase()).not.toMatch(/settled/i); + }); + + it("does not imply settlement when fresh on failed evidence", () => { + const view = deriveFreshness( + build({ kind: "failed", capturedAt: new Date(NOW - 4_000).toISOString() }) + ); + expect(view.state).toBe("fresh"); + expect(view.label.toLowerCase()).toContain("failed"); + expect(view.tooltip.toLowerCase()).not.toMatch(/recent paid execution/); + expect(view.tooltip.toLowerCase()).toContain("freshness does not imply"); + }); + }); + + describe("stale proof", () => { + it("returns stale state when age exceeds the threshold", () => { + const view = deriveFreshness( + build({ + kind: "settled", + capturedAt: new Date(NOW - minutes(6)).toISOString() + }) + ); + expect(view.state).toBe("stale"); + expect(view.ageMs).toBe(minutes(6)); + expect(view.label).toContain("6m ago"); + expect(view.label.toLowerCase()).toContain("stale settled proof"); + }); + + it("returns stale state at the threshold boundary (just outside)", () => { + const view = deriveFreshness( + build({ + kind: "verified", + capturedAt: new Date(NOW - (DEFAULT_STALE_THRESHOLD_MS + 1)).toISOString() + }) + ); + expect(view.state).toBe("stale"); + }); + + it("includes demo qualifier on stale demo evidence", () => { + const view = deriveFreshness( + build({ + kind: "demo", + capturedAt: new Date(NOW - minutes(7)).toISOString() + }) + ); + expect(view.state).toBe("stale"); + expect(view.isDemo).toBe(true); + expect(view.label.toLowerCase()).toContain("demo"); + expect(view.tooltip.toLowerCase()).not.toMatch(/settled/i); + }); + + it("supports a configurable threshold without changing the default", () => { + const view = deriveFreshness( + build({ + kind: "verified", + capturedAt: new Date(NOW - minutes(2)).toISOString(), + staleThresholdMs: 30_000 + }) + ); + expect(view.state).toBe("stale"); + expect(view.ageMs).toBe(minutes(2)); + }); + }); + + describe("non-positive ages", () => { + it("treats future timestamps at zero or positive distance as fresh", () => { + const view = deriveFreshness( + build({ + kind: "settled", + capturedAt: new Date(NOW + 5_000).toISOString() + }) + ); + expect(view.state).toBe("fresh"); + expect(view.ageMs).toBe(0); + }); + }); +}); diff --git a/apps/web/src/lib/freshness.ts b/apps/web/src/lib/freshness.ts new file mode 100644 index 0000000..9fa4be6 --- /dev/null +++ b/apps/web/src/lib/freshness.ts @@ -0,0 +1,158 @@ +import type { ProofKind } from "../types.js"; + +export type FreshnessState = "fresh" | "stale" | "unavailable"; + +export const DEFAULT_STALE_THRESHOLD_MS = 5 * 60 * 1000; + +export interface DeriveFreshnessInput { + /** Free-form kind; we narrow to the four canonical `ProofKind` values at runtime + * inside the label/tooltip helpers. Anything else falls back to a generic copy. */ + kind?: string; + capturedAt?: string | null; + /** Injectable for deterministic tests. */ + now?: number; + /** Injectable for tests; production uses DEFAULT_STALE_THRESHOLD_MS. */ + staleThresholdMs?: number; +} + +export interface FreshnessView { + state: FreshnessState; + isDemo: boolean; + kind: string | undefined; + ageMs: number | null; + label: string; + tooltip: string; +} + +const UNAVAILABLE_NON_DEMO = { + label: "Proof timestamp unavailable", + tooltip: "Payment proof metadata is unavailable for this execution." +}; + +const UNAVAILABLE_DEMO = { + label: "Demo evidence · proof timestamp unavailable", + tooltip: "Demo evidence does not constitute a settlement proof; timestamp metadata is missing." +}; + +const UNAVAILABLE_FAILED = { + label: "Failed proof · timestamp unavailable", + tooltip: + "Payment attempt failed and proof metadata is unavailable; freshness does not imply settlement." +}; + +export function deriveFreshness(input: DeriveFreshnessInput): FreshnessView { + const { + kind, + capturedAt, + now = Date.now(), + staleThresholdMs = DEFAULT_STALE_THRESHOLD_MS + } = input; + + const isDemo = kind === "demo"; + + if (!capturedAt) { + return unavailableView(kind); + } + + const ts = Date.parse(capturedAt); + if (Number.isNaN(ts)) { + return unavailableView(kind); + } + + const ageMs = Math.max(0, now - ts); + if (ageMs <= staleThresholdMs) { + return { + state: "fresh", + isDemo, + kind, + ageMs, + label: chooseFreshLabel(kind, ageMs), + tooltip: chooseFreshTooltip(kind) + }; + } + + return { + state: "stale", + isDemo, + kind, + ageMs, + label: chooseStaleLabel(kind, ageMs), + tooltip: chooseStaleTooltip(kind) + }; +} + +function unavailableView(kind: string | undefined): FreshnessView { + let copy: { label: string; tooltip: string }; + let isDemo = false; + if (kind === "demo") { + copy = UNAVAILABLE_DEMO; + isDemo = true; + } else if (kind === "failed") { + copy = UNAVAILABLE_FAILED; + } else { + copy = UNAVAILABLE_NON_DEMO; + } + return { + state: "unavailable", + isDemo, + kind, + ageMs: null, + label: copy.label, + tooltip: copy.tooltip + }; +} + +function chooseFreshLabel(kind: string | undefined, ageMs: number) { + const age = formatAge(ageMs); + if (kind === "demo") return `Demo evidence · Fresh (${age})`; + if (kind === "failed") return `Failed proof · captured (${age})`; + if (kind === "settled") return `Fresh settled proof (${age})`; + if (kind === "verified") return `Fresh verified proof (${age})`; + return `Fresh proof (${age})`; +} + +function chooseStaleLabel(kind: string | undefined, ageMs: number) { + const age = formatAge(ageMs); + if (kind === "demo") return `Demo evidence · Stale (${age})`; + if (kind === "failed") return `Failed proof · stale (${age})`; + if (kind === "settled") return `Stale settled proof (${age})`; + if (kind === "verified") return `Stale verified proof (${age})`; + return `Stale proof (${age})`; +} + +function chooseFreshTooltip(kind: string | undefined) { + if (kind === "demo") { + return "Fresh demo marker captured locally. Demo evidence does not settle on-chain."; + } + if (kind === "failed") { + return "Failed attempt captured at this time. Freshness does not imply successful settlement."; + } + return "Payment proof is recent and reflects the most recent paid execution."; +} + +function chooseStaleTooltip(kind: string | undefined) { + if (kind === "demo") { + return "Demo marker is older than the freshness threshold. Demo evidence does not settle on-chain."; + } + if (kind === "failed") { + return "Failed attempt timestamp is older than the freshness threshold; rate freshness as informational only."; + } + return "Payment proof is older than the freshness threshold; verify this reflects the latest execution."; +} + +function formatAge(ageMs: number): string { + const seconds = Math.max(0, Math.floor(ageMs / 1000)); + if (seconds < 60) { + return `${seconds}s ago`; + } + const minutes = Math.floor(seconds / 60); + if (minutes < 60) { + return `${minutes}m ago`; + } + const hours = Math.floor(minutes / 60); + if (hours < 24) { + return `${hours}h ago`; + } + const days = Math.floor(hours / 24); + return `${days}d ago`; +} diff --git a/apps/web/src/lib/wallet/machine.test.ts b/apps/web/src/lib/wallet/machine.test.ts index 142808c..9c9d2b4 100644 --- a/apps/web/src/lib/wallet/machine.test.ts +++ b/apps/web/src/lib/wallet/machine.test.ts @@ -1,7 +1,6 @@ -import { test, describe } from "node:test"; -import assert from "node:assert"; +import { describe, expect, it } from "vitest"; import { WalletSessionMachine } from "./machine.js"; -import { WalletAdapter, WalletState, WalletStatus } from "./types.js"; +import { WalletAdapter, WalletState } from "./types.js"; class FakeAdapter implements WalletAdapter { id = "fake"; @@ -75,30 +74,30 @@ class FakeAdapter implements WalletAdapter { } describe("WalletSessionMachine", () => { - test("connects and sets state correctly", async () => { + it("connects and sets state correctly", async () => { const machine = new WalletSessionMachine("TESTNET"); const adapter = new FakeAdapter(); machine.setAdapter(adapter); - assert.strictEqual(machine.getState().status, "disconnected"); + expect(machine.getState().status).toBe("disconnected"); await machine.connect(); - assert.strictEqual(machine.getState().status, "connected"); - assert.strictEqual(machine.getState().address, "GABC123"); + expect(machine.getState().status).toBe("connected"); + expect(machine.getState().address).toBe("GABC123"); }); - test("handles unsupported wallet", async () => { + it("handles unsupported wallet", async () => { const machine = new WalletSessionMachine("TESTNET"); const adapter = new FakeAdapter(); adapter.capabilities.canSignAuthEntry = false; // unsupported machine.setAdapter(adapter); await machine.connect(); - assert.strictEqual(machine.getState().status, "unsupported"); + expect(machine.getState().status).toBe("unsupported"); }); - test("handles wrong network", async () => { + it("handles wrong network", async () => { const machine = new WalletSessionMachine("PUBLIC"); const adapter = new FakeAdapter(); adapter.mockState = { @@ -110,48 +109,45 @@ describe("WalletSessionMachine", () => { machine.setAdapter(adapter); await machine.connect(); - assert.strictEqual(machine.getState().status, "wrong-network"); + expect(machine.getState().status).toBe("wrong-network"); }); - test("transitions to signing and back", async () => { + it("transitions to signing and back", async () => { const machine = new WalletSessionMachine("TESTNET"); const adapter = new FakeAdapter(); machine.setAdapter(adapter); await machine.connect(); const promise = machine.signTransaction("tx_xdr"); - assert.strictEqual(machine.getState().status, "signing"); + expect(machine.getState().status).toBe("signing"); await promise; - assert.strictEqual(machine.getState().status, "connected"); + expect(machine.getState().status).toBe("connected"); }); - test("handles user rejection during signing", async () => { + it("handles user rejection during signing", async () => { const machine = new WalletSessionMachine("TESTNET"); const adapter = new FakeAdapter(); machine.setAdapter(adapter); await machine.connect(); adapter.mockRejectSign = true; - try { - await machine.signTransaction("tx_xdr"); - assert.fail("Should throw"); - } catch (e) { - assert.strictEqual(machine.getState().status, "rejected"); - } + + await expect(machine.signTransaction("tx_xdr")).rejects.toThrow(); + expect(machine.getState().status).toBe("rejected"); }); - test("detects account/network changes via watcher", async () => { + it("detects account/network changes via watcher", async () => { const machine = new WalletSessionMachine("TESTNET"); const adapter = new FakeAdapter(); machine.setAdapter(adapter); await machine.connect(); - assert.strictEqual(machine.getState().status, "connected"); + expect(machine.getState().status).toBe("connected"); adapter.simulateNetworkChange("PUBLIC", "TESTNET"); - assert.strictEqual(machine.getState().status, "wrong-network"); + expect(machine.getState().status).toBe("wrong-network"); adapter.simulateNetworkChange("TESTNET", "TESTNET"); - assert.strictEqual(machine.getState().status, "connected"); + expect(machine.getState().status).toBe("connected"); }); }); diff --git a/apps/web/src/pages/ControlDeckPage.tsx b/apps/web/src/pages/ControlDeckPage.tsx index f0d3f60..f1ab063 100644 --- a/apps/web/src/pages/ControlDeckPage.tsx +++ b/apps/web/src/pages/ControlDeckPage.tsx @@ -25,6 +25,7 @@ import { } from "../lib/sponsorship.js"; import { runWalletPaidQuery } from "../lib/x402.js"; import { WalletSessionMachine, FreighterAdapter, type WalletState } from "../lib/wallet/index.js"; +import { FreshnessBadge } from "../components/FreshnessBadge.js"; const modeLabels: Record = { search: "Search", @@ -513,6 +514,11 @@ export default function ControlDeckPage() { + +

Trace ID diff --git a/apps/web/src/types.ts b/apps/web/src/types.ts index 5b06866..88173aa 100644 --- a/apps/web/src/types.ts +++ b/apps/web/src/types.ts @@ -1,5 +1,12 @@ import type { ProviderDefinition, QueryMode, QueryResult } from "@query402/shared"; +// Discriminator union used by the freshness badge on the Control Deck. Stored +// fields on PaymentEvidenceSummary below stay typed as `string` so any new +// evidence kind can flow through without a type change. Exported here so the +// badge and helper can import a stable shape even if upstream-sync rewrites +// this file. +export type ProofKind = "demo" | "verified" | "settled" | "failed"; + export interface PaymentProofLinks { transaction: string; payer: string; @@ -18,6 +25,10 @@ export interface PaymentEvidenceSummary { facilitatorUrl: string; payer?: string; transactionHash?: string; + /** Captured at evidence build time on the API. Drives the + * fresh/stale/unavailable badge in apps/web/src/components/FreshnessBadge.tsx. + * Older cached responses without this field render as `unavailable`. */ + capturedAt?: string; proofLinks: PaymentProofLinks; } diff --git a/apps/web/vitest.config.ts b/apps/web/vitest.config.ts new file mode 100644 index 0000000..78508ac --- /dev/null +++ b/apps/web/vitest.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + include: ["src/**/*.test.ts", "src/**/*.test.tsx"], + css: false + }, + esbuild: { + jsx: "automatic" + } +}); diff --git a/package-lock.json b/package-lock.json index 96cb307..6b24cd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -93,12 +93,13 @@ "@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", "typescript": "^5.8.3", - "vite": "^5.4.19" + "vite": "^5.4.19", + "vitest": "^3.2.6" }, "optionalDependencies": { "@esbuild/linux-x64": "^0.21.5", @@ -451,9 +452,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -468,9 +469,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -480,7 +481,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -498,7 +498,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -516,7 +515,6 @@ "os": [ "android" ], - "peer": true, "engines": { "node": ">=18" } @@ -534,7 +532,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -552,7 +549,6 @@ "os": [ "darwin" ], - "peer": true, "engines": { "node": ">=18" } @@ -570,7 +566,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -588,7 +583,6 @@ "os": [ "freebsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -606,7 +600,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -624,7 +617,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -642,7 +634,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -660,7 +651,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -678,7 +668,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -696,7 +685,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -714,7 +702,6 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } @@ -732,27 +719,24 @@ "os": [ "linux" ], - "peer": true, "engines": { "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.28.1", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", - "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "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" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" ], - "peer": true, "engines": { - "node": ">=18" + "node": ">=12" } }, "node_modules/@esbuild/netbsd-arm64": { @@ -768,7 +752,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -786,7 +769,6 @@ "os": [ "netbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -804,7 +786,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -822,7 +803,6 @@ "os": [ "openbsd" ], - "peer": true, "engines": { "node": ">=18" } @@ -840,7 +820,6 @@ "os": [ "openharmony" ], - "peer": true, "engines": { "node": ">=18" } @@ -858,7 +837,6 @@ "os": [ "sunos" ], - "peer": true, "engines": { "node": ">=18" } @@ -876,7 +854,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -894,7 +871,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -912,7 +888,6 @@ "os": [ "win32" ], - "peer": true, "engines": { "node": ">=18" } @@ -1399,6 +1374,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1594,7 +1570,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -2107,347 +2082,249 @@ "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.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": ">=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.0", + "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/parser": { + "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": ">=20.0.0", - "pnpm": ">=9.0.0" + "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/@stellar/stellar-base": { - "version": "15.0.0", - "license": "Apache-2.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": { - "@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/tsconfig-utils": "^8.62.0", + "@typescript-eslint/types": "^8.62.0", + "debug": "^4.4.3" }, "engines": { - "node": ">=20.0.0" + "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/@stellar/stellar-sdk": { - "version": "15.0.1", - "license": "Apache-2.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==", + "dev": true, + "license": "MIT", "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" - }, - "bin": { - "stellar-js": "bin/stellar-js" + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0" }, "engines": { - "node": ">=20.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", + "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "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__generator": { - "version": "7.27.0", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz", + "integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.0.0" + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0", + "@typescript-eslint/utils": "8.62.0", + "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__template": { - "version": "7.4.4", + "node_modules/@typescript-eslint/types": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz", + "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@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" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", + "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.2" + "@typescript-eslint/project-service": "8.62.0", + "@typescript-eslint/tsconfig-utils": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", + "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/better-sqlite3": { - "version": "7.6.13", + "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/node": "*" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/@types/body-parser": { - "version": "1.19.6", + "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/connect": "*", - "@types/node": "*" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/@types/chai": { - "version": "5.2.3", + "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": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@types/connect": { - "version": "3.4.38", + "node_modules/@typescript-eslint/utils": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz", + "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.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/cookiejar": { - "version": "2.1.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "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==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz", + "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==", "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" + "eslint-visitor-keys": "^5.0.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2455,27 +2332,19 @@ "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/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", - "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0" - }, + "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" + "url": "https://opencollective.com/eslint" } }, "node_modules/@vitejs/plugin-react": { @@ -2484,15 +2353,19 @@ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", "dev": true, "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": "^14.18.0 || >=16.0.0" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "node_modules/@vitest/coverage-v8": { @@ -2789,36 +2662,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", @@ -3446,9 +3289,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "version": "1.0.30001800", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001800.tgz", + "integrity": "sha512-MMHtuAz9Ys840zAY5F4k6fV5GaivZ9sPk+nz0mY+GYVzRBnYkN0mpqkSR92oWRQ19yQWo4HvBV/FnC16AJX8MA==", "dev": true, "funding": [ { @@ -3885,9 +3728,9 @@ "license": "MIT" }, "node_modules/electron-to-chromium": { - "version": "1.5.380", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.380.tgz", - "integrity": "sha512-W6d5AbuEoRayO447cqrg6lKJIlscgRnnxOZl/08kfV71BQDoEBC7Wwis68z87LjyK6f4kWyTaubuDbhHKrZkbA==", + "version": "1.5.383", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.383.tgz", + "integrity": "sha512-I2484/KkAvl8lm9VyjH2JnbOIV0d/UCqT7gbzs6l+o6Vmn9wgB66uVcKX+Vk6HrXtY6fbWTOEXuv8waDTuFNCw==", "dev": true, "license": "ISC" }, @@ -4010,13 +3853,16 @@ "@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, + "node_modules/esbuild/node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, "os": [ "linux" ], @@ -4026,6 +3872,8 @@ }, "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", "engines": { @@ -4384,9 +4232,9 @@ "license": "MIT" }, "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.3.tgz", + "integrity": "sha512-i70LwGWUduXqzicKXWshooq+sWL1K3WUU5rKZNG/0i3a1OSoX3HqhH5WbWwTmqWfor4urUakGPiRQcleRZTwOg==", "funding": [ { "type": "github", @@ -5716,9 +5564,9 @@ } }, "node_modules/node-abi": { - "version": "3.92.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.92.0.tgz", - "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "version": "3.93.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.93.0.tgz", + "integrity": "sha512-Cu6yUpX5Iavugm8BeX7c0wgU9CvOqfd1yM6A1d2q2ZMjym7GjpASv2GdRcTq3Fx+Sb5OgBkEEpw4VnAbY6Y5RA==", "license": "MIT", "dependencies": { "semver": "^7.3.5" @@ -6334,9 +6182,9 @@ } }, "node_modules/prettier": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.1.tgz", - "integrity": "sha512-ppiDo2CSwexck1eyZUwJHg/N3nf1+6IRCv7W/VJ5vaLnVCmB7+3CdRfMwoCHBBX6xTrREDTksZ4OZl5SSf4zXA==", + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.4.tgz", + "integrity": "sha512-yWG/o/4oJfo036EKAfK6ACAoDOfHeRHx4tuxkfBZiauURiaSmYwlpOr5LQqKtIkRD2z1PLteme2WoxEnj4tHTg==", "dev": true, "license": "MIT", "bin": { @@ -7474,9 +7322,9 @@ } }, "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "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": { @@ -7767,16 +7615,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" @@ -7881,9 +7729,9 @@ } }, "node_modules/viem": { - "version": "2.53.1", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.53.1.tgz", - "integrity": "sha512-FhfJ/SW73CVosiyVLmIMVgKDRKYV1AGCLzZoHYvmNayyVff63Qi1ocPCk59LqC/cNw244RbBJjHnmxqXkE7NpA==", + "version": "2.54.1", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.54.1.tgz", + "integrity": "sha512-QRC3GBSnQit4pbb0sSBHRD9WYTPAffvhOqdqp8LgkRRktXZq8dePezZM/ZIkFwluLT7fMP90908goHQbtcga2A==", "funding": [ { "type": "github", @@ -8280,312 +8128,6 @@ "node": ">=12" } }, - "node_modules/vite/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" - ], - "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/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", diff --git a/package.json b/package.json index 3a6eb0f..a954751 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "engines": { "node": ">=20.0.0 <25.0.0" }, + "packageManager": "npm@11.9.0", "scripts": { "dev:api": "npm run dev --workspace @query402/api", "dev:web": "npm run dev --workspace @query402/web", @@ -38,5 +39,20 @@ "prettier": "^3.5.3", "typescript": "^5.8.3", "typescript-eslint": "^8.32.1" + }, + "overrides": { + "@typescript-eslint/eslint-plugin": "8.62.0", + "@typescript-eslint/parser": "8.62.0", + "@typescript-eslint/types": "8.62.0", + "@typescript-eslint/type-utils": "8.62.0", + "@typescript-eslint/visitor-keys": "8.62.0", + "@typescript-eslint/scope-manager": "8.62.0", + "@typescript-eslint/tsconfig-utils": "8.62.0", + "@typescript-eslint/project-service": "8.62.0", + "@typescript-eslint/typescript-estree": "8.62.0", + "@typescript-eslint/utils": "8.62.0", + "ignore": "5.3.2", + "@vitejs/plugin-react": "4.7.0", + "react-refresh": "0.17.0" } } 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); } }