diff --git a/frontend/lib/proof-cache.test.ts b/frontend/lib/proof-cache.test.ts new file mode 100644 index 00000000..d42eb1fb --- /dev/null +++ b/frontend/lib/proof-cache.test.ts @@ -0,0 +1,112 @@ +import { describe, it, expect, beforeEach } from "vitest"; +import { + buildProofKey, + loadProofCache, + saveProof, + getCachedProof, + markProofProved, + invalidateProof, + cacheEntryUsable, + entryToGeneratedProof, +} from "./proof-cache"; +import type { Credential } from "./credential"; + +// In-memory storage so tests never touch the real localStorage. +function memoryStorage(): Storage { + const map = new Map(); + return { + getItem: (k: string) => map.get(k) ?? null, + setItem: (k: string, v: string) => { map.set(k, v); }, + removeItem: (k: string) => { map.delete(k); }, + clear: () => { map.clear(); }, + key: (i: number) => Array.from(map.keys())[i] ?? null, + get length() { return map.size; }, + } as Storage; +} + +const CLAIM_PARAMS: Credential["claimParams"] = { threshold: "50000" }; +const KEY = buildProofKey({ type: "funds", commitment: "0xcommit", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v1" }); + +const PROOF = { proof: new Uint8Array([1, 2, 3, 4]), publicInputs: new Uint8Array([5, 6, 7, 8]) }; + +describe("buildProofKey", () => { + it("is stable regardless of claim-param ordering", () => { + const a = buildProofKey({ type: "jurisdiction", commitment: "c", claimParams: { restricted: ["840", "364"] }, vkVersion: "circuit-v1" }); + const b = buildProofKey({ type: "jurisdiction", commitment: "c", claimParams: { restricted: ["364", "840"] }, vkVersion: "circuit-v1" }); + expect(a).toBe(b); + }); + + it("changes when the VK version changes", () => { + const a = buildProofKey({ type: "funds", commitment: "c", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v1" }); + const b = buildProofKey({ type: "funds", commitment: "c", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v2" }); + expect(a).not.toBe(b); + }); + + it("changes when a claim parameter changes", () => { + const a = buildProofKey({ type: "funds", commitment: "c", claimParams: { threshold: "10000" }, vkVersion: "circuit-v1" }); + const b = buildProofKey({ type: "funds", commitment: "c", claimParams: { threshold: "50000" }, vkVersion: "circuit-v1" }); + expect(a).not.toBe(b); + }); +}); + +describe("proof cache reuse + invalidation", () => { + let storage: Storage; + + beforeEach(() => { storage = memoryStorage(); }); + + it("reuses a matching, valid cached proof instead of regenerating", () => { + saveProof(KEY, { type: "funds", commitment: "0xcommit", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v1", proof: PROOF.proof, publicInputs: PROOF.publicInputs }, storage); + const hit = getCachedProof(KEY, {}, storage); + expect(hit).not.toBeNull(); + expect(entryToGeneratedProof(hit!)).toEqual(PROOF); + }); + + it("returns null (invalidated) when a param on the credential changes", () => { + saveProof(KEY, { type: "funds", commitment: "0xcommit", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v1", proof: PROOF.proof, publicInputs: PROOF.publicInputs }, storage); + const otherKey = buildProofKey({ type: "funds", commitment: "0xcommit", claimParams: { threshold: "99999" }, vkVersion: "circuit-v1" }); + expect(getCachedProof(otherKey, {}, storage)).toBeNull(); + expect(getCachedProof(KEY, {}, storage)).not.toBeNull(); + }); + + it("returns null (invalidated) when the VK version changes", () => { + saveProof(KEY, { type: "funds", commitment: "0xcommit", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v1", proof: PROOF.proof, publicInputs: PROOF.publicInputs }, storage); + const newVk = buildProofKey({ type: "funds", commitment: "0xcommit", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v2" }); + expect(getCachedProof(newVk, {}, storage)).toBeNull(); + }); + + it("refuses reuse once the on-chain record expires", () => { + saveProof(KEY, { type: "funds", commitment: "0xcommit", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v1", proof: PROOF.proof, publicInputs: PROOF.publicInputs }, storage); + const now = Math.floor(Date.now() / 1000); + markProofProved(KEY, { ttlSecs: 90 * 86400 }, storage); + const entry = loadProofCache(storage)[0]; + // Still valid the moment it was confirmed (now < provedAt + 90d). + expect(cacheEntryUsable(entry, { now })).toBe(true); + // Invalidated once the clock passes the on-chain expiry. + expect(cacheEntryUsable(entry, { now: now + 91 * 86400 })).toBe(false); + }); + + it("refuses reuse when the on-chain record is revoked / no longer valid", () => { + saveProof(KEY, { type: "funds", commitment: "0xcommit", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v1", proof: PROOF.proof, publicInputs: PROOF.publicInputs }, storage); + expect(getCachedProof(KEY, { onChainStillValid: false }, storage)).toBeNull(); + }); + + it("still reuses when on-chain is confirmed valid", () => { + saveProof(KEY, { type: "funds", commitment: "0xcommit", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v1", proof: PROOF.proof, publicInputs: PROOF.publicInputs }, storage); + expect(getCachedProof(KEY, { onChainStillValid: true }, storage)).not.toBeNull(); + }); + + it("invalidates a proof on demand (revocation path)", () => { + saveProof(KEY, { type: "funds", commitment: "0xcommit", claimParams: CLAIM_PARAMS, vkVersion: "circuit-v1", proof: PROOF.proof, publicInputs: PROOF.publicInputs }, storage); + invalidateProof(KEY, storage); + expect(getCachedProof(KEY, {}, storage)).toBeNull(); + }); + + it("keeps proofs for multiple credentials independent", () => { + const keyA = buildProofKey({ type: "kyc", commitment: "0xA", vkVersion: "circuit-v1" }); + const keyB = buildProofKey({ type: "income", commitment: "0xB", claimParams: { threshold: "200000" }, vkVersion: "circuit-v1" }); + saveProof(keyA, { type: "kyc", commitment: "0xA", vkVersion: "circuit-v1", proof: PROOF.proof, publicInputs: PROOF.publicInputs }, storage); + saveProof(keyB, { type: "income", commitment: "0xB", claimParams: { threshold: "200000" }, vkVersion: "circuit-v1", proof: PROOF.proof, publicInputs: PROOF.publicInputs }, storage); + expect(getCachedProof(keyA, {}, storage)?.commitment).toBe("0xA"); + expect(getCachedProof(keyB, {}, storage)?.commitment).toBe("0xB"); + }); +}); \ No newline at end of file diff --git a/frontend/lib/proof-cache.ts b/frontend/lib/proof-cache.ts new file mode 100644 index 00000000..66efa780 --- /dev/null +++ b/frontend/lib/proof-cache.ts @@ -0,0 +1,292 @@ +// Local proof caching / reuse. +// +// Generating a proof is the most expensive step in the holder flow (server-side +// witness + browser WASM UltraHonk). If a holder already produced a valid proof +// for an unchanged credential, re-proving wastes time and compute. We cache the +// generated proof bytes locally and reuse them when everything that affects the +// proof is still identical. +// +// A proof is a pure function of: +// - the credential commitment (itself derived from type, value, salt, issuer) +// - the claim parameters baked into the credential (thresholds / restricted) +// - the circuit / verification-key version the proof was generated with +// +// So a cache entry is keyed by exactly those three things. We only reuse an +// entry when it still matches the on-chain record (unexpired, not revoked), +// and we invalidate it on expiry, revocation, VK version change, or any change +// to the claim parameters. + +import type { Credential } from "./credential"; +import type { CircuitArtifact } from "./proof"; + +export interface ProofCacheEntry { + /** Deterministic cache key (see {@link buildProofKey}). */ + key: string; + type: string; + commitment: string; + /** Canonicalised claim params that were part of the proof. */ + claimParams?: Credential["claimParams"]; + /** Circuit / VK version the proof was generated against. */ + vkVersion: string; + /** Proof bytes stored as a JSON-safe number[] (Uint8Array isn't JSON-safe). */ + proof: number[]; + /** Public inputs stored as a JSON-safe number[]. */ + publicInputs: number[]; + /** Unix ms when the entry was created. */ + createdAt: number; + /** Unix seconds when this proof was last submitted on-chain. */ + provedAt?: number; + /** Validity window (seconds) after provedAt while the on-chain record lives. */ + ttlSecs?: number; + /** Set when the on-chain record was observed revoked/expired — never reuse. */ + revoked?: boolean; +} + +const CACHE_KEY = "stellarcred:proof-cache"; +const MAX_ENTRIES = 50; + +/** + * The advertised circuit/VK version for cached proofs. We derive it from the + * actual circuit bytecode at write time (see {@link resolveVkVersion}), so a + * bumped circuit automatically invalidates every cached proof — but callers can + * also compare against this constant when the artifact isn't fetchable. + */ +export const DEFAULT_VK_VERSION = "circuit-v1"; + +// --------------------------------------------------------------------------- // +// Keying +// --------------------------------------------------------------------------- // + +/** Keep a stable object shape so the JSON key is order-independent. */ +function canonicalClaimParams(p?: Credential["claimParams"]): Credential["claimParams"] | undefined { + if (!p) return undefined; + const out: Credential["claimParams"] = {}; + if (p.threshold_years !== undefined) out.threshold_years = p.threshold_years; + if (p.threshold !== undefined) out.threshold = p.threshold; + if (p.restricted) out.restricted = [...p.restricted].sort(); + return out; +} + +/** + * Build the deterministic cache key for a credential. Everything that + * influences the proof output is included, so any change to the type, + * commitment, claim params, or VK version yields a different key and therefore + * a cache miss (i.e. the old proof is invalidated and a new one is generated). + */ +export function buildProofKey(input: { + type: string; + commitment: string; + claimParams?: Credential["claimParams"]; + vkVersion: string; +}): string { + return JSON.stringify({ + type: input.type, + commitment: input.commitment, + claimParams: canonicalClaimParams(input.claimParams), + vkVersion: input.vkVersion, + }); +} + +// --------------------------------------------------------------------------- // +// Storage +// --------------------------------------------------------------------------- // + +export interface ProofCacheStorage { + getItem(key: string): string | null; + setItem(key: string, value: string): void; + removeItem(key: string): void; +} + +function defaultStorage(): ProofCacheStorage | null { + if (typeof window === "undefined" || typeof localStorage === "undefined") return null; + return window.localStorage; +} + +export function loadProofCache(storage?: ProofCacheStorage | null): ProofCacheEntry[] { + const store = storage ?? defaultStorage(); + if (!store) return []; + try { + const raw = store.getItem(CACHE_KEY); + if (!raw) return []; + const parsed = JSON.parse(raw); + if (!Array.isArray(parsed)) return []; + return parsed as ProofCacheEntry[]; + } catch { + return []; + } +} + +function persistProofCache(entries: ProofCacheEntry[], storage?: ProofCacheStorage | null): void { + const store = storage ?? defaultStorage(); + if (!store) return; + try { + store.setItem(CACHE_KEY, JSON.stringify(entries)); + } catch { + // localStorage can be full or unavailable (private mode) — caching is + // best-effort and never blocks proving. + } +} + +// --------------------------------------------------------------------------- // +// Read / write +// --------------------------------------------------------------------------- // + +/** + * Whether a matching cached entry is still usable, i.e. not invalidated by + * expiry, revocation, an on-chain record that is no longer valid, or a proof + * that predates the current validity window. + */ +export function cacheEntryUsable( + entry: ProofCacheEntry, + opts: { + /** Unix seconds "now"; defaults to Date.now()/1000. */ + now?: number; + /** + * When false the on-chain record is gone/invalid/revoked, so the cached + * proof must not be reused even though the bytes would still verify. + */ + onChainStillValid?: boolean; + } = {}, +): boolean { + if (entry.revoked) return false; + if (opts.onChainStillValid === false) return false; + const now = opts.now ?? Math.floor(Date.now() / 1000); + if (entry.provedAt !== undefined && entry.ttlSecs !== undefined) { + if (now > entry.provedAt + entry.ttlSecs) return false; // expired on-chain + } + return true; +} + +/** + * Look up a usable proof for the exact cache key. Returns the entry (for reuse) + * or null. A structurally-matching but no-longer-usable entry is removed so it + * doesn't shadow a fresh proof later. + */ +export function getCachedProof( + key: string, + opts: { + onChainStillValid?: boolean; + } = {}, + storage?: ProofCacheStorage | null, +): ProofCacheEntry | null { + const entries = loadProofCache(storage); + const idx = entries.findIndex((e) => e.key === key); + if (idx === -1) return null; + const entry = entries[idx]; + if (!cacheEntryUsable(entry, { onChainStillValid: opts.onChainStillValid })) { + const next = [...entries.slice(0, idx), ...entries.slice(idx + 1)]; + persistProofCache(next, storage); + return null; + } + return entry; +} + +/** Store a freshly generated proof under the given key. */ +export function saveProof( + key: string, + data: { + type: string; + commitment: string; + claimParams?: Credential["claimParams"]; + vkVersion: string; + proof: Uint8Array; + publicInputs: Uint8Array; + }, + storage?: ProofCacheStorage | null, +): void { + const entries = loadProofCache(storage); + const next: ProofCacheEntry = { + key, + type: data.type, + commitment: data.commitment, + claimParams: canonicalClaimParams(data.claimParams), + vkVersion: data.vkVersion, + proof: Array.from(data.proof), + publicInputs: Array.from(data.publicInputs), + createdAt: Date.now(), + }; + // Keep a single entry per key (replace any stale one) and cap total size. + const withoutKey = entries.filter((e) => e.key !== key); + const next_ = [next, ...withoutKey].slice(0, MAX_ENTRIES); + persistProofCache(next_, storage); +} + +/** + * Record that a cached proof was successfully submitted on-chain, so its + * validity window is tracked and it can be reused until it expires. + */ +export function markProofProved( + key: string, + opts: { ttlSecs: number }, + storage?: ProofCacheStorage | null, +): void { + const entries = loadProofCache(storage); + const next = entries.map((e) => + e.key === key + ? { ...e, provedAt: Math.floor(Date.now() / 1000), ttlSecs: opts.ttlSecs, revoked: false } + : e, + ); + persistProofCache(next, storage); +} + +/** Invalidate (remove) the cached proof for a given key — e.g. on revocation. */ +export function invalidateProof(key: string, storage?: ProofCacheStorage | null): void { + const entries = loadProofCache(storage); + const next = entries.filter((e) => e.key !== key); + persistProofCache(next, storage); +} + +/** Remove every cached proof belonging to a credential commitment. */ +export function removeCredentialProofs(commitment: string, storage?: ProofCacheStorage | null): void { + const entries = loadProofCache(storage); + const next = entries.filter((e) => e.commitment !== commitment); + persistProofCache(next, storage); +} + +// --------------------------------------------------------------------------- // +// VK version resolution +// --------------------------------------------------------------------------- // + +/** + * Derive the VK version from the actual circuit artifact being used. Because + * the verification key is deterministic from the compiled circuit bytecode, a + * change in the deployed circuit (or toolchain) changes the bytecode and thus + * this version — which automatically invalidates every cached proof that was + * generated against any earlier circuit. + */ +export async function resolveVkVersion(type: string): Promise { + try { + const res = await fetch(`/circuits/${type}.json`); + if (!res.ok) return DEFAULT_VK_VERSION; + const artifact = (await res.json()) as Partial; + const bytecode = artifact?.bytecode; + if (!bytecode) return DEFAULT_VK_VERSION; + const digest = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(String(bytecode)), + ); + // A short, stable, comparable fingerprint of the circuit. + return "circuit-" + bytesToHex(new Uint8Array(digest).slice(0, 8)); + } catch { + // Circuit not reachable — fall back to the build-time version marker so we + // still try a cache hit rather than throwing away the optimisation. + return DEFAULT_VK_VERSION; + } +} + +export function bytesToHex(u8: Uint8Array): string { + return Array.from(u8) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); +} + +/** Rehydrate a cached entry into the shape the submission path expects. */ +export function entryToGeneratedProof(entry: ProofCacheEntry): { + proof: Uint8Array; + publicInputs: Uint8Array; +} { + return { + proof: Uint8Array.from(entry.proof), + publicInputs: Uint8Array.from(entry.publicInputs), + }; +} \ No newline at end of file diff --git a/frontend/lib/proof.ts b/frontend/lib/proof.ts index 476033fa..24e5a4bd 100644 --- a/frontend/lib/proof.ts +++ b/frontend/lib/proof.ts @@ -76,6 +76,13 @@ export function withTimeout( }); } +/** The compiled Noir circuit artifact emitted by circuits/scripts/build.sh to + * /public/circuits/.json. + */ +export interface CircuitArtifact { + bytecode: string; +} + export interface GeneratedProof { /** Raw proof bytes (456 fields × 32 = 14592 bytes), as the contract expects. */ proof: Uint8Array; @@ -163,7 +170,7 @@ async function buildBackend(type: CredentialType): Promise { `Compiled circuit "${type}" not found. Run the circuit build to emit /public/circuits/${type}.json.`, ); } - const circuit = (await circuitRes.json()) as { bytecode: string }; + const circuit = (await circuitRes.json()) as CircuitArtifact; const { UltraHonkBackend } = await loadBb(); return new UltraHonkBackend(circuit.bytecode, backendOptions()); } diff --git a/frontend/package.json b/frontend/package.json index f696431d..1d21be32 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -60,4 +60,4 @@ "vitest": "^2.1.9" }, "packageManager": "pnpm@10.32.1+sha512.a706938f0e89ac1456b6563eab4edf1d1faf3368d1191fc5c59790e96dc918e4456ab2e67d613de1043d2e8c81f87303e6b40d4ffeca9df15ef1ad567348f2be" -} \ No newline at end of file +} diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index 461a311e..37112a74 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -61,7 +61,7 @@ importers: version: 18.3.1(react@18.3.1) zod: specifier: ^4.4.3 - version: 4.4.3 + version: 4.5.4 devDependencies: '@axe-core/playwright': specifier: ^4.12.1 @@ -95,10 +95,10 @@ importers: version: 18.3.7(@types/react@18.3.31) '@typescript-eslint/eslint-plugin': specifier: ^8.68.0 - version: 8.68.0(@typescript-eslint/parser@8.68.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + version: 8.69.0(@typescript-eslint/parser@8.69.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) '@typescript-eslint/parser': specifier: ^8.68.0 - version: 8.68.0(eslint@8.57.1)(typescript@5.9.3) + version: 8.69.0(eslint@8.57.1)(typescript@5.9.3) '@vitejs/plugin-react': specifier: ^4.3.4 version: 4.7.0(vite@5.4.21(@types/node@20.19.43)) @@ -116,7 +116,7 @@ importers: version: 13.0.3 tsx: specifier: ^4.23.5 - version: 4.23.12 + version: 4.23.13 typescript: specifier: ^5 version: 5.9.3 @@ -138,7 +138,7 @@ importers: devDependencies: tsup: specifier: ^8.5.1 - version: 8.5.1(postcss@8.5.26)(tsx@4.23.12)(typescript@5.9.3) + version: 8.5.1(postcss@8.5.26)(tsx@4.23.13)(typescript@5.9.3) typescript: specifier: ^5.9.3 version: 5.9.3 @@ -848,8 +848,8 @@ packages: '@ledgerhq/hw-transport@6.31.4': resolution: {integrity: sha512-6c1ir/cXWJm5dCWdq55NPgCJ3UuKuuxRvf//Xs36Bq9BwkV2YaRQhZITAkads83l07NAdR16hkTWqqpwFMaI6A==} - '@ledgerhq/logs@6.17.0': - resolution: {integrity: sha512-yra33g5q/AU7+PwAws+GaVpQGUuxnDREjVBnviJjcaJLVKuLzI4pnj8Bd3nY3fypM5k1yZEYKEXfUuGFUjP2+w==} + '@ledgerhq/logs@6.18.0': + resolution: {integrity: sha512-PCKx6wDnjyzqIC+9O7xn7iCJinr4nCvy6NAp07D2QFTO4E+H7CBJ9uVj2br3rouDESKXqJedXvgSxyc3TGCLTQ==} '@lit-labs/ssr-dom-shim@1.6.0': resolution: {integrity: sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==} @@ -1301,8 +1301,8 @@ packages: '@scure/base@1.2.6': resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} - '@scure/base@2.3.0': - resolution: {integrity: sha512-NsG6Y03tY6R5BUis4FdVtHVkur0U6FOzskgs9ZXNl78CUc9fkZ78HmENUle1nSOkCasDmbubmWD9qwB7mm4PZA==} + '@scure/base@2.4.0': + resolution: {integrity: sha512-thZ1TuJwFwBblOhgsjDKvvGirBxNp+wSvY/DR6tJBJOTDhdAAcHJ8Vbr2eFnqaxeca4+t0i9KBf+uHYGWwZORg==} '@scure/bip32@1.7.0': resolution: {integrity: sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==} @@ -1883,63 +1883,63 @@ packages: '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@typescript-eslint/eslint-plugin@8.68.0': - resolution: {integrity: sha512-WASHDpCm6qO5jj9g1a+8NiW5+GCkAyLReR56/4VruYmNgfUmqpxOfZ2Yfb8xGfJPWv5Qi6LSD8sXdces3vbp/Q==} + '@typescript-eslint/eslint-plugin@8.69.0': + resolution: {integrity: sha512-t5jQTKPIgVW1PE6dR6H6Qz5gm8zjMlX5/2gRaOGd9eO6V7J+tQc6iWKukEe7dY8u9HyYasQ0yfF0/FSSTEO2gA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.68.0 + '@typescript-eslint/parser': ^8.69.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.68.0': - resolution: {integrity: sha512-fHq2VC1kpyYfvEcbiMjOpySY4WS7voEp89yAThrHRX5sm9j2lzYppCb2umFMEed4fWcyeLjHxrz0mpjNBaBxMQ==} + '@typescript-eslint/parser@8.69.0': + resolution: {integrity: sha512-l4b0DhWioGg6Gt2ebGlvfkFMOjRsauxtsnDRwUSRX1qHq3HdTfQHV8wW9zEXeciai6HfeaKOedQn2Zoofx3WBw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.68.0': - resolution: {integrity: sha512-5GQtWZCXFcFYux955pvoS02WLc49pXNlvIxocKjS0clvwo3in1RdlzVKyiqQH9vE5AKWFLTaUgeQkOrTS+0Qxw==} + '@typescript-eslint/project-service@8.69.0': + resolution: {integrity: sha512-yi4obFrHMmnsesWehHbkg9zMA7Jt8cXT+mKM08G999pH1yT6nqgsHx7MYm0uY1wAj8CqiBXYRJ7WAT0QdQHQXg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.68.0': - resolution: {integrity: sha512-T5eXpcaJNg8bhjHJ8Rjp68Vq/QBteYtTKY8TZqVNPaUbuz0f6jI9t6aDkylwvalpAB9XTTFeFOjrjXAZ3YvmVA==} + '@typescript-eslint/scope-manager@8.69.0': + resolution: {integrity: sha512-ewfspqWvSxKSOaplqAUNbaSFO0eB6w1EtQ+esfYFRm3614Ty4uNtExkcbgd6nWsXphbqKyf9ZYdbZdv2xEoWEQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.68.0': - resolution: {integrity: sha512-F7zrGQfiJHojPwi8vhxZQC1tWtJzvL74cK/nqri2lk8YUXvYaYwl263xOJ69jDWPUk1hmcdoayFwk9lX09npVw==} + '@typescript-eslint/tsconfig-utils@8.69.0': + resolution: {integrity: sha512-xNqK7YTDZsLniQMV/4rpFR8Z5JlqeRvVjuG1YgF/mdPVH84HSD19L8CczMA0qg2RfwEV231GHH3VnToJDo4MfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.68.0': - resolution: {integrity: sha512-X77zqoY1EjeWGs/0JNxeaMfp5C5lIz4Tw8y66F1Ne8Faq6g424sBNYM6xBAqElfGZPLpWS+CZAp0DXyKDzWiHg==} + '@typescript-eslint/type-utils@8.69.0': + resolution: {integrity: sha512-ZfoJAVg3JZndQEpEl9petVlxau3lRuElc4HRMuAlLCf8to04/iHz692RUSNmXKDjEuJmIL+KZ2/BsOcBc16dsA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.68.0': - resolution: {integrity: sha512-9RnpsGJjrAllCMefGVVsImJM24YurhC0Q1h4UbvivtvOqXmR/vEJge2OoE++z9m6hyg8T1Q8t5SNT6tHSbrxcg==} + '@typescript-eslint/types@8.69.0': + resolution: {integrity: sha512-K3VrubUPhlo9VDBS6QdI8YB5j7ClpqLRdefcz6PFrhnwicehBweqQ9Evhl4l+FYz0HdDmMqIiSX0aldGRYtDCA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.68.0': - resolution: {integrity: sha512-OKKsD0tYmoNiU5PW2zehO1yO56jYOm1ShYlxon/Z0SJNidAkdVg86eg9ruRuoXf8xfnuWZGbwDsStkoXbZtIIA==} + '@typescript-eslint/typescript-estree@8.69.0': + resolution: {integrity: sha512-AdFkgqck3Vudb/kWnxlyafU/4aBhHrbQ9locP2N4psXTy5mOBg0SHJumnLvx7r6g1gV4DKvUFwV2nJZBoqOD8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.68.0': - resolution: {integrity: sha512-PB5gJMMOg0Q5P1tsgWtEAqQacJXq0qEqRHDX/YJ4FaTMLfZPpHB3gjl2EJuiZyPABxmj4ZQYiY9m1bdAJ5y7tQ==} + '@typescript-eslint/utils@8.69.0': + resolution: {integrity: sha512-tUbx60BBqQa31kXF5MCsOOLL5E/WzUuxIn7YpAvq+eaUlqvk8/NXnXMBNAdLCr0icjkzem7iUA5QqWHe/hJ1aw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.68.0': - resolution: {integrity: sha512-YR65gGdGvTUAWLldC3xLOvOzamdGzB4A5/N8rehEaHs3Zvoe39BhgY+u0SPch1OvrVTfLcc55wsSgK2NcnTS/A==} + '@typescript-eslint/visitor-keys@8.69.0': + resolution: {integrity: sha512-+rmdgPA+EXkNgKYvHvFfhrs35utXbwaC5PGpDquSXcoXQDKUA5UjV0LmTucG/4JXkM31BTu4TilHtrN8IVBe8w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@ungap/structured-clone@1.4.0': @@ -2743,8 +2743,8 @@ packages: ecdsa-sig-formatter@1.0.11: resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - electron-to-chromium@1.5.416: - resolution: {integrity: sha512-K6bvB2BjnNrugtIih6ewlbBI9DXa976jIdiIlRLHhBoEI9a4JaQjjHyF+A1IQI543aQYR4LnmOrT/K5fZj0aPA==} + electron-to-chromium@1.5.418: + resolution: {integrity: sha512-UzS26r3AEbG5wSoGVpJKqwHIU9zwQN7LHdVIThDrJpS0I5KdlXFMEb8543fhc9dVnIIAST6ar8rhwa00AL5MlA==} elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} @@ -2992,8 +2992,8 @@ packages: fastestsmallesttextencoderdecoder@1.0.22: resolution: {integrity: sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==} - fastq@1.20.2: - resolution: {integrity: sha512-UpGiiODyCGprM8EPP6JodP6jC9Rws6TCuiDOD+nn0CJhR8guI3g/ozo4ugL0vJ+Yz1UtJuuRPqvQuybVOF1VQA==} + fastq@1.20.3: + resolution: {integrity: sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==} fdir@6.5.0: resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} @@ -3235,8 +3235,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.6: - resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} + ignore@7.0.8: + resolution: {integrity: sha512-YYNsSlXBjMk92SKnkwvB5LOVSa6OznlFUGcsvrFgNJbJCd0M1XKeFVRc8ZByeCqz32FivYNHJVooLmdqrmvp/Q==} engines: {node: '>= 4'} import-fresh@3.3.1: @@ -3268,8 +3268,8 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - ip-address@10.5.0: - resolution: {integrity: sha512-R5SnVLJmgYYvf2F2ZgwSBnelz5G4q5AxIC277GDfUaNbrZKNANcBC7RHqYYePlszf4kBolVkJauG0ZjHHFh55g==} + ip-address@10.7.0: + resolution: {integrity: sha512-BGFsyJd5mpXp3rK6jIdADLNgpJUK1jnjzvYF8lK+VyDab9JAmqN0YOKDdP17HlgKb2+ehPgDc8EtnRLbGCAMhA==} engines: {node: '>= 12'} iron-webcrypto@1.2.1: @@ -3771,8 +3771,8 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - nwsapi@2.2.24: - resolution: {integrity: sha512-7YRhZ3jS45LwmSCT4b2sVFHt/WuovaktDU07QrtOBY2PXskss5a9jfmR9jptyumwXST+rFjrmppMY1KT/yn35A==} + nwsapi@2.2.27: + resolution: {integrity: sha512-gQPNF78qebCQ6tvVFBYrvJdBNOrYZm90ZlXgpIFm06p6qHDHq/XC4TnJftN6OMbxVE0UTBAoRgcsDeJBBooITw==} object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} @@ -4376,8 +4376,8 @@ packages: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + string.prototype.matchall@4.1.0: + resolution: {integrity: sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==} engines: {node: '>= 0.4'} string.prototype.repeat@1.0.0: @@ -4577,8 +4577,8 @@ packages: typescript: optional: true - tsx@4.23.12: - resolution: {integrity: sha512-FDf4L4sYzKtzWYhU/Xm0AQFdTjdIxNo9ElTf2mxXM6k8YMHXzYUe4yODVaXP4V9uMFbVg8c0qyBccK2OOxb45Q==} + tsx@4.23.13: + resolution: {integrity: sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==} engines: {node: '>=18.0.0'} hasBin: true @@ -4974,8 +4974,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zod@4.4.3: - resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zod@4.5.4: + resolution: {integrity: sha512-sC95tT5iHHH9gtpj6A81kh+NEaRAUFN+qlUPDUbRfOMvNf5QCBqsb3WgvnpVtK5Y+4UfA6KqufotuTvMGiTlsA==} snapshots: @@ -5582,16 +5582,16 @@ snapshots: '@ledgerhq/devices': 8.17.0 '@ledgerhq/errors': 6.37.0 '@ledgerhq/hw-transport': 6.31.4 - '@ledgerhq/logs': 6.17.0 + '@ledgerhq/logs': 6.18.0 '@ledgerhq/hw-transport@6.31.4': dependencies: '@ledgerhq/devices': 8.17.0 '@ledgerhq/errors': 6.37.0 - '@ledgerhq/logs': 6.17.0 + '@ledgerhq/logs': 6.18.0 events: 3.3.0 - '@ledgerhq/logs@6.17.0': {} + '@ledgerhq/logs@6.18.0': {} '@lit-labs/ssr-dom-shim@1.6.0': {} @@ -5870,7 +5870,7 @@ snapshots: '@nodelib/fs.walk@1.2.8': dependencies: '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.2 + fastq: 1.20.3 '@noir-lang/acvm_js@1.0.0-beta.9': {} @@ -6005,7 +6005,7 @@ snapshots: '@scure/base@1.2.6': {} - '@scure/base@2.3.0': {} + '@scure/base@2.4.0': {} '@scure/bip32@1.7.0': dependencies: @@ -6914,57 +6914,57 @@ snapshots: dependencies: '@types/node': 20.19.43 - '@typescript-eslint/eslint-plugin@8.68.0(@typescript-eslint/parser@8.68.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.69.0(@typescript-eslint/parser@8.69.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.68.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/type-utils': 8.68.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 8.68.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/parser': 8.69.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/type-utils': 8.69.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 8.69.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.69.0 eslint: 8.57.1 - ignore: 7.0.6 + ignore: 7.0.8 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.68.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/parser@8.69.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.69.0 debug: 4.4.3 eslint: 8.57.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.68.0(typescript@5.9.3)': + '@typescript-eslint/project-service@8.69.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@5.9.3) - '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@5.9.3) + '@typescript-eslint/types': 8.69.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.68.0': + '@typescript-eslint/scope-manager@8.69.0': dependencies: - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/visitor-keys': 8.69.0 - '@typescript-eslint/tsconfig-utils@8.68.0(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.69.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.68.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.69.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.68.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.69.0(eslint@8.57.1)(typescript@5.9.3) debug: 4.4.3 eslint: 8.57.1 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -6972,14 +6972,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.68.0': {} + '@typescript-eslint/types@8.69.0': {} - '@typescript-eslint/typescript-estree@8.68.0(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.69.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.68.0(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.68.0(typescript@5.9.3) - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/visitor-keys': 8.68.0 + '@typescript-eslint/project-service': 8.69.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.69.0(typescript@5.9.3) + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/visitor-keys': 8.69.0 debug: 4.4.3 minimatch: 10.2.6 semver: 7.8.5 @@ -6989,20 +6989,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.68.0(eslint@8.57.1)(typescript@5.9.3)': + '@typescript-eslint/utils@8.69.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.10.1(eslint@8.57.1) - '@typescript-eslint/scope-manager': 8.68.0 - '@typescript-eslint/types': 8.68.0 - '@typescript-eslint/typescript-estree': 8.68.0(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.69.0 + '@typescript-eslint/types': 8.69.0 + '@typescript-eslint/typescript-estree': 8.69.0(typescript@5.9.3) eslint: 8.57.1 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.68.0': + '@typescript-eslint/visitor-keys@8.69.0': dependencies: - '@typescript-eslint/types': 8.68.0 + '@typescript-eslint/types': 8.69.0 eslint-visitor-keys: 5.0.1 '@ungap/structured-clone@1.4.0': {} @@ -7282,7 +7282,7 @@ snapshots: '@walletconnect/relay-api@1.0.11': dependencies: - '@walletconnect/jsonrpc-types': 1.0.3 + '@walletconnect/jsonrpc-types': 1.0.4 '@walletconnect/relay-auth@1.1.0': dependencies: @@ -7674,7 +7674,7 @@ snapshots: dependencies: baseline-browser-mapping: 2.11.20 caniuse-lite: 1.0.30001810 - electron-to-chromium: 1.5.416 + electron-to-chromium: 1.5.418 node-releases: 2.0.54 update-browserslist-db: 1.3.2(browserslist@4.28.8) @@ -7981,7 +7981,7 @@ snapshots: dependencies: safe-buffer: 5.2.1 - electron-to-chromium@1.5.416: {} + electron-to-chromium@1.5.418: {} elliptic@6.6.1: dependencies: @@ -8216,12 +8216,12 @@ snapshots: dependencies: '@next/eslint-plugin-next': 14.2.35 '@rushstack/eslint-patch': 1.16.1 - '@typescript-eslint/eslint-plugin': 8.68.0(@typescript-eslint/parser@8.68.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/parser': 8.68.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.69.0(@typescript-eslint/parser@8.69.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.69.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.10 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.68.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.69.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) eslint-plugin-react-hooks: 5.0.0-canary-7118f5dd7-20230705(eslint@8.57.1) @@ -8251,22 +8251,22 @@ snapshots: tinyglobby: 0.2.17 unrs-resolver: 1.12.2 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.68.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.69.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-module-utils@2.14.0(@typescript-eslint/parser@8.68.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-module-utils@2.14.0(@typescript-eslint/parser@8.69.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.68.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.69.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.10 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.68.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.69.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -8277,7 +8277,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.68.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) + eslint-module-utils: 2.14.0(@typescript-eslint/parser@8.69.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.10)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -8289,7 +8289,7 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.68.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': 8.69.0(eslint@8.57.1)(typescript@5.9.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack @@ -8337,7 +8337,7 @@ snapshots: prop-types: 15.8.1 resolve: 2.0.0-next.7 semver: 6.3.1 - string.prototype.matchall: 4.0.12 + string.prototype.matchall: 4.1.0 string.prototype.repeat: 1.0.0 eslint-scope@7.2.2: @@ -8440,7 +8440,7 @@ snapshots: fastestsmallesttextencoderdecoder@1.0.22: {} - fastq@1.20.2: + fastq@1.20.3: dependencies: reusify: 1.1.0 @@ -8714,7 +8714,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.6: {} + ignore@7.0.8: {} import-fresh@3.3.1: dependencies: @@ -8742,7 +8742,7 @@ snapshots: hasown: 2.0.4 side-channel: 1.1.1 - ip-address@10.5.0: {} + ip-address@10.7.0: {} iron-webcrypto@1.2.1: {} @@ -8956,7 +8956,7 @@ snapshots: http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.6 is-potential-custom-element-name: 1.0.1 - nwsapi: 2.2.24 + nwsapi: 2.2.27 parse5: 7.3.0 rrweb-cssom: 0.7.1 saxes: 6.0.0 @@ -9285,7 +9285,7 @@ snapshots: normalize-path@3.0.0: {} - nwsapi@2.2.24: {} + nwsapi@2.2.27: {} object-assign@4.1.1: {} @@ -9475,12 +9475,12 @@ snapshots: possible-typed-array-names@1.1.0: {} - postcss-load-config@6.0.1(postcss@8.5.26)(tsx@4.23.12): + postcss-load-config@6.0.1(postcss@8.5.26)(tsx@4.23.13): dependencies: lilconfig: 3.1.3 optionalDependencies: postcss: 8.5.26 - tsx: 4.23.12 + tsx: 4.23.13 postcss@8.4.31: dependencies: @@ -9677,7 +9677,7 @@ snapshots: ripple-address-codec@5.0.1(bufferutil@4.1.0)(utf-8-validate@6.0.6): dependencies: - '@scure/base': 2.3.0 + '@scure/base': 2.4.0 '@xrplf/isomorphic': 1.0.2(bufferutil@4.1.0)(utf-8-validate@6.0.6) transitivePeerDependencies: - bufferutil @@ -9906,7 +9906,7 @@ snapshots: socks@2.8.9: dependencies: - ip-address: 10.5.0 + ip-address: 10.7.0 smart-buffer: 4.2.0 sodium-native@4.3.3: @@ -9980,7 +9980,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.24.2 - string.prototype.matchall@4.0.12: + string.prototype.matchall@4.1.0: dependencies: call-bind: 1.0.9 call-bound: 1.0.4 @@ -10168,7 +10168,7 @@ snapshots: tslib@2.8.1: {} - tsup@8.5.1(postcss@8.5.26)(tsx@4.23.12)(typescript@5.9.3): + tsup@8.5.1(postcss@8.5.26)(tsx@4.23.13)(typescript@5.9.3): dependencies: bundle-require: 5.1.0(esbuild@0.27.7) cac: 6.7.14 @@ -10179,7 +10179,7 @@ snapshots: fix-dts-default-cjs-exports: 1.0.1 joycon: 3.1.1 picocolors: 1.1.1 - postcss-load-config: 6.0.1(postcss@8.5.26)(tsx@4.23.12) + postcss-load-config: 6.0.1(postcss@8.5.26)(tsx@4.23.13) resolve-from: 5.0.0 rollup: 4.63.1 source-map: 0.7.6 @@ -10196,7 +10196,7 @@ snapshots: - tsx - yaml - tsx@4.23.12: + tsx@4.23.13: dependencies: esbuild: 0.28.2 optionalDependencies: @@ -10608,4 +10608,4 @@ snapshots: yocto-queue@0.1.0: {} - zod@4.4.3: {} \ No newline at end of file + zod@4.5.4: {} diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml index ce931368..3b1ae780 100644 --- a/frontend/pnpm-workspace.yaml +++ b/frontend/pnpm-workspace.yaml @@ -9,9 +9,9 @@ packages: - 'packages/issuer' allowBuilds: + esbuild: true blake-hash: true bufferutil: true - esbuild: true msgpackr-extract: true protobufjs: true secp256k1: true diff --git a/frontend/test/setup.ts b/frontend/test/setup.ts new file mode 100644 index 00000000..8409752e --- /dev/null +++ b/frontend/test/setup.ts @@ -0,0 +1,21 @@ +// Vitest global setup. Kept minimal — matchers and polyfills that are only +// needed by specific suites should be imported there, not here, so this stays +// fast for pure unit tests. + +// jsdom does not implement matchMedia (used by some UI code under test is not +// needed for the current suites, but guarding it keeps future tests stable). +if (typeof window !== "undefined" && !window.matchMedia) { + Object.defineProperty(window, "matchMedia", { + writable: true, + value: (query: string) => ({ + matches: false, + media: query, + onchange: null, + addListener: () => {}, + removeListener: () => {}, + addEventListener: () => {}, + removeEventListener: () => {}, + dispatchEvent: () => false, + }), + }); +} \ No newline at end of file