diff --git a/.github/actions/install-dependencies/action.yml b/.github/actions/install-dependencies/action.yml index cfeed53bf..f997152b0 100644 --- a/.github/actions/install-dependencies/action.yml +++ b/.github/actions/install-dependencies/action.yml @@ -13,7 +13,7 @@ runs: cache: 'pnpm' # `lts/*` lags a few days to a week # up to date LTS can be found here https://endoflife.date/nodejs - node-version: '24.14.0' + node-version: '24.15.0' - name: Install dependencies shell: bash diff --git a/.gitignore b/.gitignore index d1e190d09..d4e7d9e5d 100644 --- a/.gitignore +++ b/.gitignore @@ -24,9 +24,9 @@ _docs *.bak _sourcify _artifacts -_tmp +**/tmp* _verify .pnpm-store .dev.vars _data -.tmp* +_todo diff --git a/.npmrc b/.npmrc new file mode 100644 index 000000000..674c0bc19 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +shell-emulator=true +node-options='--disable-warning=ExperimentalWarning --disable-warning=DeprecationWarning' +@jsr:registry=https://npm.jsr.io diff --git a/.vscode/settings.json b/.vscode/settings.json index bb23e2461..a10cad535 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,14 +1,9 @@ { - "typescript.tsdk": "node_modules/typescript/lib", - "typescript.enablePromptUseWorkspaceTsdk": true, - "typescript.preferences.importModuleSpecifierEnding": "js", - "javascript.preferences.importModuleSpecifierEnding": "js", - "typescript.preferences.importModuleSpecifier": "non-relative", - "javascript.preferences.importModuleSpecifier": "non-relative", - "typescript.preferences.autoImportFileExcludePatterns": [ - "**/node_modules/**/_*/**" - ], - "javascript.preferences.autoImportFileExcludePatterns": [ + "js/ts.tsdk.path": "node_modules/typescript/lib", + "js/ts.tsdk.promptToUseWorkspaceVersion": true, + "js/ts.preferences.importModuleSpecifierEnding": "js", + "js/ts.preferences.importModuleSpecifier": "non-relative", + "js/ts.preferences.autoImportFileExcludePatterns": [ "**/node_modules/**/_*/**" ], "editor.formatOnSave": true, @@ -68,10 +63,6 @@ "**/*.claude": true, "**/.pnpm-store/**": true }, - - "markdownlint.config": { - "no-inline-html": false - }, "tailwindCSS.showPixelEquivalents": true, "tailwindCSS.lint.cssConflict": "warning", "tailwindCSS.lint.suggestCanonicalClasses": "warning", diff --git a/.zed/settings.json b/.zed/settings.json index b335c2b56..0ee2964ca 100644 --- a/.zed/settings.json +++ b/.zed/settings.json @@ -26,23 +26,11 @@ } } }, - "svg": { - "language_servers": ["biome", "HTML"], - "code_actions_on_format": { - "source.fixAll.biome": true, - "source.organizeImports.biome": true - }, - "formatter": { - "language_server": { - "name": "biome" - } - } - }, "MDX": { "language_servers": ["typescript-language-server", "biome"] }, "JSON": { - "language_servers": ["biome"], + "language_servers": ["json-language-server", "biome"], "code_actions_on_format": { "source.fixAll.biome": true, "source.organizeImports.biome": true @@ -54,7 +42,7 @@ } }, "JSONC": { - "language_servers": ["biome"], + "language_servers": ["json-language-server", "biome"], "code_actions_on_format": { "source.fixAll.biome": true, "source.organizeImports.biome": true @@ -113,12 +101,5 @@ } } } - }, - "terminal": { - "button": true, - "dock": "bottom", - "toolbar": { - "breadcrumbs": false - } } } diff --git a/AGENTS.md b/AGENTS.md index 12fe3c8b0..74f9b5194 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -183,3 +183,12 @@ take_screenshot(filePath="/tmp/frame_01.png") ``` Then sequential screenshots for frames 02-10, combine, and upload. + +## Learned User Preferences + +* Verify before proposing. Don't hedge with "likely" — run the command, read the source, or fetch the docs to confirm the root cause before suggesting a fix. +* When a check fires (security policy, install failure, deploy validation), investigate why the offending condition exists and whether it can be removed before considering any workaround that weakens or bypasses the check. + +## Learned Workspace Facts + +* Cloudflare Workers docs pages can be fetched as markdown by appending `.md` to the URL (e.g., `https://developers.cloudflare.com/workers/observability/index.md`). Useful for pulling a specific page into context without the full `llms.txt` / `llms-full.txt`. diff --git a/apps/contract-verification/.env.example b/apps/contract-verification/.env.example index f2b64b04f..71ae93423 100644 --- a/apps/contract-verification/.env.example +++ b/apps/contract-verification/.env.example @@ -5,9 +5,14 @@ CLOUDFLARE_ACCOUNT_ID="" CLOUDFLARE_DATABASE_ID="" CLOUDFLARE_D1_TOKEN="" +VITE_VERIFIER_URL="https://contracts.tempo.xyz" + # Logging level: debug, error, fatal, info, trace, warning VITE_LOG_LEVEL="debug" VITE_BASE_URL="http://localhost:6969" WRANGLER_SEND_METRICS=false +DOCKER_DEFAULT_PLATFORM="linux/amd64" + +CONTAINER_MAX_INSTANCES=10 diff --git a/apps/contract-verification/Dockerfile b/apps/contract-verification/Dockerfile index d3de1f5ff..eafc13f33 100644 --- a/apps/contract-verification/Dockerfile +++ b/apps/contract-verification/Dockerfile @@ -1,11 +1,13 @@ # syntax=docker/dockerfile:1 -FROM oven/bun AS build +FROM oven/bun AS builder -RUN apt-get update \ - && apt-get install --yes libz1 \ +RUN apt-get update --yes \ + && apt-get install --yes --no-install-recommends libz1 \ + && apt-get clean --yes \ && rm -rf /var/lib/apt/lists/* -ENV NODE_ENV="production" +ARG NODE_ENV="production" +ENV NODE_ENV=${NODE_ENV} WORKDIR /usr/src/app @@ -19,25 +21,29 @@ RUN bun build \ /usr/src/app/gg/index.ts # Pull x86_64 libs for cross-arch QEMU execution of amd64 solc/vyper compilers -FROM --platform=linux/amd64 debian:trixie-slim AS amd64-libs -RUN apt-get update && apt-get install -y --no-install-recommends zlib1g && rm -rf /var/lib/apt/lists/* +FROM --platform=linux/amd64 debian:trixie-slim AS amd64-libs-provider +RUN apt-get update --yes \ + && apt-get install --yes --no-install-recommends zlib1g \ + && apt-get clean --yes \ + && rm -rf /var/lib/apt/lists/* -FROM gcr.io/distroless/base +FROM gcr.io/distroless/base AS runtime WORKDIR /usr/src/app -COPY --from=build /usr/src/app/container /usr/src/app/container +COPY --from=builder /usr/src/app/container /usr/src/app/container # x86_64 dynamic linker + libc + libz for amd64 solc/vyper on arm64 hosts via QEMU -COPY --from=amd64-libs /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 -COPY --from=amd64-libs /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 -COPY --from=amd64-libs /lib/x86_64-linux-gnu/libm.so.6 /lib/x86_64-linux-gnu/libm.so.6 -COPY --from=amd64-libs /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/libz.so.1 -COPY --from=amd64-libs /lib/x86_64-linux-gnu/libdl.so.2 /lib/x86_64-linux-gnu/libdl.so.2 -COPY --from=amd64-libs /lib/x86_64-linux-gnu/libpthread.so.0 /lib/x86_64-linux-gnu/libpthread.so.0 +COPY --from=amd64-libs-provider /lib64/ld-linux-x86-64.so.2 /lib64/ld-linux-x86-64.so.2 +COPY --from=amd64-libs-provider /lib/x86_64-linux-gnu/libc.so.6 /lib/x86_64-linux-gnu/libc.so.6 +COPY --from=amd64-libs-provider /lib/x86_64-linux-gnu/libm.so.6 /lib/x86_64-linux-gnu/libm.so.6 +COPY --from=amd64-libs-provider /lib/x86_64-linux-gnu/libz.so.1 /lib/x86_64-linux-gnu/libz.so.1 +COPY --from=amd64-libs-provider /lib/x86_64-linux-gnu/libdl.so.2 /lib/x86_64-linux-gnu/libdl.so.2 +COPY --from=amd64-libs-provider /lib/x86_64-linux-gnu/libpthread.so.0 /lib/x86_64-linux-gnu/libpthread.so.0 # native-arch libz for any native binaries that need it -COPY --from=build /usr/lib/*-linux-gnu/libz.so.1 /usr/local/lib/ +COPY --from=builder /usr/lib/*-linux-gnu/libz.so.1 /usr/local/lib/ -ENV NODE_ENV="production" +ARG NODE_ENV="production" +ENV NODE_ENV=${NODE_ENV} ENV LD_LIBRARY_PATH="/usr/local/lib" EXPOSE $PORT diff --git a/apps/contract-verification/env.d.ts b/apps/contract-verification/env.d.ts index 9ca5c6eb1..888275c61 100644 --- a/apps/contract-verification/env.d.ts +++ b/apps/contract-verification/env.d.ts @@ -19,6 +19,9 @@ interface EnvironmentVariables { readonly CLOUDFLARE_DATABASE_ID: string readonly CLOUDFLARE_D1_TOKEN: string readonly CLOUDFLARE_D1_ENVIRONMENT: 'local' | (string & {}) + + readonly CONTAINER_MAX_INSTANCES: string + MINIFLARE_CONTAINER_EGRESS_IMAGE: string } // Node.js `process.env` auto-completion diff --git a/apps/contract-verification/openapi.json b/apps/contract-verification/openapi.json index c25405d0f..60f120a29 100644 --- a/apps/contract-verification/openapi.json +++ b/apps/contract-verification/openapi.json @@ -20,32 +20,27 @@ "description": "Service base workers.dev domain (CACHE DISABLED!)" }, { - "url": "https://sourcify.dev/server", - "description": "Sourcify hosted API" + "url": "http://localhost:{PORT}", + "description": "Local", + "variables": { + "PORT": { + "default": "6767", + "description": "localhost port number" + } + } }, { "url": "{CUSTOM_URL}", "description": "Custom Sourcify-compatible API base URL", "variables": { "CUSTOM_URL": { - "default": "https://sourcify.dev/server", "description": "The URL of the custom Sourcify-compatible API base URL" } } }, { - "url": "https://o.tail388b2e.ts.net", - "description": "Localhost Funneled" - }, - { - "url": "http://localhost:{port}", - "description": "local", - "variables": { - "port": { - "default": "22222", - "description": "localhost port number" - } - } + "url": "https://sourcify.dev/server", + "description": "Sourcify hosted API" } ], "tags": [ @@ -7176,7 +7171,7 @@ "required": true, "schema": { "type": "integer", - "enum": [31318, 42431, 4217] + "enum": [4217, 42431, 31318] } }, "AddressPath": { @@ -7185,7 +7180,8 @@ "required": true, "schema": { "type": "string", - "pattern": "^0x[a-fA-F0-9]{40}$" + "pattern": "^0x[a-fA-F0-9]{40}$", + "example": "0x86234502292ee398a0c6ac4f060b315dd020d179" } }, "HashPath": { diff --git a/apps/contract-verification/package.json b/apps/contract-verification/package.json index 479964ffc..78c55fac0 100644 --- a/apps/contract-verification/package.json +++ b/apps/contract-verification/package.json @@ -30,7 +30,7 @@ "gen:types": "wrangler types --env-interface='CloudflareBindings' --env-file='.env.example'" }, "dependencies": { - "@cloudflare/containers": "^0.2.3", + "@cloudflare/containers": "^0.3.2", "@hono/zod-validator": "catalog:", "@logtape/config": "catalog:", "@logtape/drizzle-orm": "catalog:", @@ -38,13 +38,13 @@ "@logtape/logtape": "catalog:", "@logtape/pretty": "catalog:", "@logtape/redaction": "catalog:", + "@std/semver": "npm:@jsr/std__semver", "@wagmi/core": "catalog:", "cbor-x": "^1.6.4", "drizzle-orm": "^0.45.2", "hono": "catalog:", "hono-rate-limiter": "catalog:", "ox": "catalog:", - "semver": "^7.7.4", "viem": "catalog:", "wagmi": "catalog:", "zod": "catalog:" @@ -55,17 +55,14 @@ "@cloudflare/vitest-pool-workers": "catalog:", "@cloudflare/workers-types": "catalog:", "@total-typescript/ts-reset": "catalog:", - "@types/bun": "^1.3.11", + "@types/bun": "^1.3.12", "@types/node": "catalog:", - "@types/semver": "^7.7.1", - "@vitest/coverage-istanbul": "4.1.0", "@vitest/ui": "catalog:", "dotenv": "catalog:", "drizzle-kit": "^0.31.10", "esbuild": "catalog:", "typescript": "catalog:", "vite": "catalog:", - "vite-plugin-devtools-json": "catalog:", "vitest": "catalog:", "wrangler": "catalog:" }, diff --git a/apps/contract-verification/scripts/verify-via-curl.sh b/apps/contract-verification/scripts/verify-via-curl.sh index 3d2936b5c..677e0a555 100755 --- a/apps/contract-verification/scripts/verify-via-curl.sh +++ b/apps/contract-verification/scripts/verify-via-curl.sh @@ -126,6 +126,8 @@ fi echo -e "\n=== LOOKUP VERIFIED CONTRACT ===\n" -curl --silent \ - "${VERIFIER_URL}/v2/contract/${CHAIN_ID}/${CONTRACT_ADDRESS}?fields=all" \ - | jq '{ match, creationMatch, runtimeMatch, name, chainId, address, compiler, compilerVersion, language, deployment, abi }' +ADDRESS=$(curl --silent "$VERIFIER_URL/v2/verify/$VERIFICATION_ID" | jq --raw-output '.contract.address') + +echo "Verified contract address: $ADDRESS" + +echo -e "\n=== https://explore.$CHAIN_ID.tempo.xyz/address/$ADDRESS?tab=contract ===\n" diff --git a/apps/contract-verification/src/index.tsx b/apps/contract-verification/src/index.tsx index 865cdb35e..3990ab920 100644 --- a/apps/contract-verification/src/index.tsx +++ b/apps/contract-verification/src/index.tsx @@ -59,7 +59,7 @@ app.use('*', requestId({ headerName: 'X-Tempo-Request-Id' })) app.use(async (context, next) => { await withContext( { - requestId: context.get('requestId') as string | undefined, + requestId: context.get('requestId'), method: context.req.method, path: context.req.path, }, diff --git a/apps/contract-verification/src/job-runner.ts b/apps/contract-verification/src/job-runner.ts index 3e83fef7b..5aefce576 100644 --- a/apps/contract-verification/src/job-runner.ts +++ b/apps/contract-verification/src/job-runner.ts @@ -2,28 +2,20 @@ import { DurableObject } from 'cloudflare:workers' import { getLogger } from '#lib/logger.ts' import { formatError } from '#lib/utilities.ts' +import type { VerificationJob } from '#schema.ts' import { runVerificationJob } from '#route.verify.ts' const logger = getLogger(['tempo', 'job-runner']) -type StoredJob = { - jobId: string - chainId: number - address: string - body: { - stdJsonInput: { - language: string - sources: Record - settings: object - } - compilerVersion: string - contractIdentifier: string - creationTransactionHash?: string - } +type LegacyStoredJob = Pick< + VerificationJob, + 'jobId' | 'chainId' | 'address' +> & { + body: Omit } export class VerificationJobRunner extends DurableObject { - async enqueue(job: StoredJob): Promise { + async enqueue(job: VerificationJob): Promise { logger.info('job_enqueued', { jobId: job.jobId, chainId: job.chainId, @@ -35,12 +27,27 @@ export class VerificationJobRunner extends DurableObject { } override async alarm(): Promise { - const job = await this.ctx.storage.get('job') - if (!job) { + const storedJob = await this.ctx.storage.get< + VerificationJob | LegacyStoredJob + >('job') + if (!storedJob) { logger.warn('alarm_job_missing') return } + const job = + 'body' in storedJob + ? { + jobId: storedJob.jobId, + chainId: storedJob.chainId, + address: storedJob.address, + stdJsonInput: storedJob.body.stdJsonInput, + compilerVersion: storedJob.body.compilerVersion, + contractIdentifier: storedJob.body.contractIdentifier, + creationTransactionHash: storedJob.body.creationTransactionHash, + } + : storedJob + logger.info('job_started', { jobId: job.jobId, chainId: job.chainId, @@ -48,13 +55,7 @@ export class VerificationJobRunner extends DurableObject { }) try { - await runVerificationJob( - this.env, - job.jobId, - job.chainId, - job.address, - job.body, - ) + await runVerificationJob(this.env, job) logger.info('job_finished', { jobId: job.jobId }) } catch (error) { logger.error('job_alarm_error', { diff --git a/apps/contract-verification/src/lib/bytecode-matching.ts b/apps/contract-verification/src/lib/bytecode-matching.ts index 856fd49a7..b128734db 100644 --- a/apps/contract-verification/src/lib/bytecode-matching.ts +++ b/apps/contract-verification/src/lib/bytecode-matching.ts @@ -1,7 +1,9 @@ -import semver from 'semver' import * as CBOR from 'cbor-x/decode' +import * as Semver from '@std/semver' import { AbiParameters, Hash, Hex } from 'ox' +import type { ImmutableReferences, LinkReferences } from '#schema.ts' + // ============================================================================ // Types // ============================================================================ @@ -28,20 +30,6 @@ export interface TransformationValues { cborAuxdata?: Record } -export interface LinkReference { - start: number - length: number -} - -export type LinkReferences = Record> - -export interface ImmutableReference { - start: number - length: number -} - -export type ImmutableReferences = Record - export interface CborAuxdataPosition { offset: number value: string @@ -80,6 +68,17 @@ export enum AuxdataStyle { VYPER_LT_0_3_5 = 'vyper_lt_0_3_5', } +const VYPER_LT_0_3_5_VERSION = Semver.parse('0.3.5') +const VYPER_LT_0_3_10_VERSION = Semver.parse('0.3.10') +const VYPER_INTEGRITY_VERSION = Semver.parse('0.4.1') +const SEMVER_VERSION_PATTERN = + /v?(\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?(?:\+[0-9A-Za-z-.]+)?)/ + +function tryParseCompilerVersion(compilerVersion: string) { + const version = SEMVER_VERSION_PATTERN.exec(compilerVersion)?.at(1) + return version ? Semver.tryParse(version) : undefined +} + // ============================================================================ // CBOR Auxdata Utilities // ============================================================================ @@ -93,11 +92,13 @@ export function getVyperAuxdataStyle( | AuxdataStyle.VYPER | AuxdataStyle.VYPER_LT_0_3_10 | AuxdataStyle.VYPER_LT_0_3_5 { - const version = semver.valid(semver.coerce(compilerVersion)) + const version = tryParseCompilerVersion(compilerVersion) if (!version) return AuxdataStyle.VYPER - if (semver.lt(version, '0.3.5')) return AuxdataStyle.VYPER_LT_0_3_5 - if (semver.lt(version, '0.3.10')) return AuxdataStyle.VYPER_LT_0_3_10 + if (Semver.lessThan(version, VYPER_LT_0_3_5_VERSION)) + return AuxdataStyle.VYPER_LT_0_3_5 + if (Semver.lessThan(version, VYPER_LT_0_3_10_VERSION)) + return AuxdataStyle.VYPER_LT_0_3_10 return AuxdataStyle.VYPER } @@ -251,7 +252,8 @@ export function decodeVyperAuxdata( const lastElement = cborDecodedObject.at(-1) as { vyper: number[] } const compilerVersion = lastElement.vyper.join('.') - if (semver.gte(compilerVersion, '0.4.1')) { + const version = Semver.tryParse(compilerVersion) + if (version && Semver.greaterOrEqual(version, VYPER_INTEGRITY_VERSION)) { // >= 0.4.1: [integrity, runtimeSize, dataSizes, immutableSize, {vyper: [v]}] return { integrity: cborDecodedObject[0] as string, diff --git a/apps/contract-verification/src/lib/logger.ts b/apps/contract-verification/src/lib/logger.ts index 7c34714a7..4bd907989 100644 --- a/apps/contract-verification/src/lib/logger.ts +++ b/apps/contract-verification/src/lib/logger.ts @@ -1,14 +1,14 @@ -import { AsyncLocalStorage } from 'node:async_hooks' -import { prettyFormatter } from '@logtape/pretty' -import { getLogger as getDrizzleLogger } from '@logtape/drizzle-orm' import { - configure, dispose, - getConsoleSink, + configure, getLogger, withContext, + getConsoleSink, type LogRecord, } from '@logtape/logtape' +import { prettyFormatter } from '@logtape/pretty' +import { AsyncLocalStorage } from 'node:async_hooks' +import { getLogger as getDrizzleLogger } from '@logtape/drizzle-orm' export { dispose, getLogger, withContext } diff --git a/apps/contract-verification/src/route.docs.tsx b/apps/contract-verification/src/route.docs.tsx index 7fd3b7fc4..c27c6af3d 100644 --- a/apps/contract-verification/src/route.docs.tsx +++ b/apps/contract-verification/src/route.docs.tsx @@ -2,6 +2,7 @@ import { Hono } from 'hono' import { html, raw } from 'hono/html' import packageJSON from '#package.json' with { type: 'json' } +import openApiSchema from '#openapi.json' with { type: 'json' } const getScalarConfig = (baseUrl: string) => ({ @@ -21,18 +22,7 @@ const getScalarConfig = (baseUrl: string) => defaultHttpClient: { clientKey: 'curl', targetKey: 'shell' }, servers: [ { url: baseUrl, description: 'Current' }, - { url: 'https://contracts.tempo.xyz', description: 'Production' }, - { - url: 'https://contracts.porto.workers.dev', - description: 'workers.dev', - }, - { - url: 'http://localhost:{port}', - description: 'Local', - variables: { - port: { default: '6767', description: 'localhost port number' }, - }, - }, + ...openApiSchema.servers, ], }) as const diff --git a/apps/contract-verification/src/route.lookup.ts b/apps/contract-verification/src/route.lookup.ts index 61ee653b9..a37479700 100644 --- a/apps/contract-verification/src/route.lookup.ts +++ b/apps/contract-verification/src/route.lookup.ts @@ -1,7 +1,6 @@ import { Hono } from 'hono' -import { Address, Hex } from 'ox' +import { Address, Hex, Hash } from 'ox' import { and, asc, desc, eq, gt, lt } from 'drizzle-orm' -import { keccak256 } from 'viem' import { codeTable, @@ -116,7 +115,7 @@ function buildSignaturesPayload(abi: unknown): SignaturesPayload { .filter((type): type is string => type !== null) .join(',') const signature = `${item.name}(${inputTypes})` - const signatureHash32 = keccak256(Hex.fromString(signature)) + const signatureHash32 = Hash.keccak256(Hex.fromString(signature)) const signatureHash4 = Hex.fromBytes( Hex.toBytes(signatureHash32).slice(0, 4), ) @@ -1112,4 +1111,9 @@ lookupAllChainContractsRoute.get('/:chainId', async (context) => { } }) -export { lookupRoute, lookupAllChainContractsRoute } +export { + lookupRoute, + lookupAllChainContractsRoute, + formatAbiParameterType, + buildSignaturesPayload, +} diff --git a/apps/contract-verification/src/route.verify-legacy.ts b/apps/contract-verification/src/route.verify-legacy.ts index fdc11b843..326cd3163 100644 --- a/apps/contract-verification/src/route.verify-legacy.ts +++ b/apps/contract-verification/src/route.verify-legacy.ts @@ -26,13 +26,12 @@ import { } from '#database/schema.ts' import { matchBytecode, - type LinkReferences, getVyperAuxdataStyle, - type ImmutableReferences, getVyperImmutableReferences, } from '#lib/bytecode-matching.ts' import { chains, chainIds } from '#wagmi.config.ts' import { getLogger } from '#lib/logger.ts' +import type { ImmutableReferences, LinkReferences } from '#schema.ts' const logger = getLogger(['tempo']) diff --git a/apps/contract-verification/src/route.verify.ts b/apps/contract-verification/src/route.verify.ts index 1e252d5da..c25616911 100644 --- a/apps/contract-verification/src/route.verify.ts +++ b/apps/contract-verification/src/route.verify.ts @@ -2,11 +2,20 @@ import { Hono } from 'hono' import * as z from 'zod/mini' import { Address, Hex } from 'ox' import { and, eq, isNull } from 'drizzle-orm' +import { zValidator } from '@hono/zod-validator' import type { BatchItem } from 'drizzle-orm/batch' +import { Hash } from 'ox' +import { createPublicClient, http } from 'viem' import { getRandom } from '@cloudflare/containers' -import { createPublicClient, http, keccak256 } from 'viem' +import { + zChainId, + zAddress, + VerificationJob, + validationCustomCodes, + type CompileOutput, +} from '#schema.ts' import { getDb, formatError, @@ -30,22 +39,25 @@ import { import { AuxdataStyle, matchBytecode, - type LinkReferences, getVyperAuxdataStyle, - type ImmutableReferences, getVyperImmutableReferences, } from '#lib/bytecode-matching.ts' -import { chains, chainIds } from '#wagmi.config.ts' +import { chains } from '#wagmi.config.ts' import { getLogger } from '#lib/logger.ts' const logger = getLogger(['tempo']) -import wranglerJSON from '#wrangler.json' with { type: 'json' } + +function getValidationCustomCode(error: z.core.$ZodError): string | undefined { + return error.issues.find((issue) => issue.code === 'custom')?.params + ?.customCode +} /** Jobs older than this are considered stale and can be retried (15 minutes). */ const JOB_TTL_MS = 15 * 60 * 1_000 /** Number of container instances to load-balance across. */ -const CONTAINER_INSTANCE_COUNT = - wranglerJSON.containers.at(0)?.max_instances ?? 10 +const CONTAINER_INSTANCE_COUNT = Number( + process.env.CONTAINER_MAX_INSTANCES ?? 10, +) function timestampToMs(value: string): number { const normalized = value.includes('T') ? value : `${value.replace(' ', 'T')}Z` @@ -101,249 +113,265 @@ verifyRoute ) // POST /v2/verify/:chainId/:address - Verify Contract (Standard JSON) - .post('/:chainId/:address', async (context) => { - try { - const { chainId: _chainId, address } = context.req.param() - let body: unknown - - try { - body = await context.req.json() - } catch { - return sourcifyError(context, 400, 'invalid_json', 'Invalid JSON body') - } - - const parsedBody = z.safeParse( - z.object({ - stdJsonInput: z.object({ - language: z.string(), - sources: z.record( - z.string(), - z.object({ - content: z.string(), - }), - ), - settings: z.record(z.string(), z.unknown()), - }), - compilerVersion: z.string(), - contractIdentifier: z.string(), - creationTransactionHash: z.optional(z.string()), - }), - body, - ) - if (!parsedBody.success) { - return sourcifyError( - context, - 400, - 'missing_params', - 'stdJsonInput, compilerVersion, and contractIdentifier are required', - ) - } - - if (!/^\d+$/.test(_chainId)) { - return sourcifyError( - context, - 400, - 'invalid_chain_id', - `Invalid chainId format: ${_chainId}`, - ) - } - - const chainId = Number(_chainId) - if (!chainIds.includes(chainId)) { - return sourcifyError( - context, - 400, - 'unsupported_chain', - `The chain with chainId ${chainId} is not supported`, - ) - } - - if (!Address.validate(address, { strict: true })) { - return sourcifyError( - context, - 400, - 'invalid_address', - `Invalid address: ${address}`, - ) - } + .post( + '/:chainId/:address', + zValidator( + 'param', + z.object({ + chainId: zChainId(), + address: zAddress({ strict: true }), + }), + (value, context) => { + if (!value.success) { + const customCode = getValidationCustomCode(value.error) + const rawChainId = context.req.param('chainId') + const address = context.req.param('address') + + if (customCode === validationCustomCodes.invalidChainId) { + return sourcifyError( + context, + 400, + customCode, + `Invalid chainId format: ${rawChainId}`, + ) + } - const { contractIdentifier } = parsedBody.data + if (customCode === validationCustomCodes.unsupportedChain) { + return sourcifyError( + context, + 400, + customCode, + `The chain with chainId ${rawChainId} is not supported`, + ) + } - // Parse contractIdentifier to validate format - const lastColonIndex = contractIdentifier.lastIndexOf(':') - if (lastColonIndex === -1) { - return sourcifyError( - context, - 400, - 'invalid_contract_identifier', - 'contractIdentifier must be in format "path/to/Contract.sol:ContractName"', - ) - } + if (customCode === validationCustomCodes.invalidAddress) { + return sourcifyError( + context, + 400, + customCode, + `Invalid address: ${address}`, + ) + } - const db = getDb(context.env.CONTRACTS_DB) - const addressBytes = Hex.toBytes(address) - - // Check if already verified - const existingVerification = await db - .select({ - matchId: verifiedContractsTable.id, - runtimeMatch: verifiedContractsTable.runtimeMatch, - creationMatch: verifiedContractsTable.creationMatch, - }) - .from(verifiedContractsTable) - .innerJoin( - contractDeploymentsTable, - eq(verifiedContractsTable.deploymentId, contractDeploymentsTable.id), - ) - .where( - and( - eq(contractDeploymentsTable.chainId, chainId), - eq(contractDeploymentsTable.address, addressBytes), - ), - ) - .limit(1) + return sourcifyError( + context, + 400, + 'invalid_params', + z.prettifyError(value.error), + ) + } + }, + ), + zValidator( + 'json', + z.pick(VerificationJob, { + stdJsonInput: true, + compilerVersion: true, + contractIdentifier: true, + creationTransactionHash: true, + }), + (value, context) => { + if (!value.success) + return sourcifyError( + context, + 400, + 'missing_params', + `stdJsonInput, compilerVersion, and contractIdentifier are required - ${z.prettifyError(value.error)}`, + ) + }, + ), + async (context) => { + try { + const body = context.req.valid('json') + const { chainId, address } = context.req.valid('param') + Hex.assert(address) - if (existingVerification.length > 0) { - const existing = existingVerification.at(0) - const runtimeMatch = existing?.runtimeMatch ? 'exact matches' : 'match' - const creationMatch = existing?.creationMatch - ? 'exact matches' - : 'match' - return sourcifyError( - context, - 409, - 'already_verified', - `Contract ${address} on chain ${chainId} is already verified with runtimeMatch ${runtimeMatch} and creationMatch ${creationMatch}.`, - ) - } + // Parse contractIdentifier to validate format + const lastColonIndex = body.contractIdentifier.lastIndexOf(':') + if (lastColonIndex === -1) { + return sourcifyError( + context, + 400, + 'invalid_contract_identifier', + 'contractIdentifier must be in format "path/to/Contract.sol:ContractName"', + ) + } - // Check if there's already a pending job for this contract - const existingJob = await db - .select({ - id: verificationJobsTable.id, - startedAt: verificationJobsTable.startedAt, - }) - .from(verificationJobsTable) - .where( - and( - eq(verificationJobsTable.chainId, chainId), - eq(verificationJobsTable.contractAddress, addressBytes), - isNull(verificationJobsTable.completedAt), - ), - ) - .limit(1) + const db = getDb(context.env.CONTRACTS_DB) + const addressBytes = Hex.toBytes(address) - if (existingJob.length > 0 && existingJob[0]) { - const jobStarted = existingJob[0].startedAt - const staleThresholdMs = Date.now() - JOB_TTL_MS - const jobStartedMs = jobStarted ? timestampToMs(jobStarted) : 0 + // Check if already verified + const existingVerification = await db + .select({ + matchId: verifiedContractsTable.id, + runtimeMatch: verifiedContractsTable.runtimeMatch, + creationMatch: verifiedContractsTable.creationMatch, + }) + .from(verifiedContractsTable) + .innerJoin( + contractDeploymentsTable, + eq( + verifiedContractsTable.deploymentId, + contractDeploymentsTable.id, + ), + ) + .where( + and( + eq(contractDeploymentsTable.chainId, chainId), + eq(contractDeploymentsTable.address, addressBytes), + ), + ) + .limit(1) - if (jobStartedMs > staleThresholdMs) { + if (existingVerification.length > 0) { + const existing = existingVerification.at(0) + const runtimeMatch = existing?.runtimeMatch + ? 'exact matches' + : 'match' + const creationMatch = existing?.creationMatch + ? 'exact matches' + : 'match' return sourcifyError( context, - 429, - 'duplicate_verification_request', - `Contract ${address} on chain ${chainId} is already being verified.`, + 409, + 'already_verified', + `Contract ${address} on chain ${chainId} is already verified with runtimeMatch ${runtimeMatch} and creationMatch ${creationMatch}.`, ) } - // Expire the stale job so a new one can be created - await db - .update(verificationJobsTable) - .set({ - completedAt: new Date().toISOString(), - errorCode: 'timeout', - errorData: JSON.stringify({ - message: `Job timed out after ${JOB_TTL_MS / 1000}s`, - }), + // Check if there's already a pending job for this contract + const existingJob = await db + .select({ + id: verificationJobsTable.id, + startedAt: verificationJobsTable.startedAt, }) - .where(eq(verificationJobsTable.id, existingJob[0].id)) - - logger.info('stale_job_expired', { - jobId: existingJob[0].id, - chainId, - address, - }) - } + .from(verificationJobsTable) + .where( + and( + eq(verificationJobsTable.chainId, chainId), + eq(verificationJobsTable.contractAddress, addressBytes), + isNull(verificationJobsTable.completedAt), + ), + ) + .limit(1) - // Create verification job — re-check for pending jobs after insert to handle races. - // verification_jobs has no unique constraint on (chain_id, contract_address), - // so we guard with a select-then-insert pattern plus a post-insert duplicate check. - const jobId = globalThis.crypto.randomUUID() - await db.insert(verificationJobsTable).values({ - id: jobId, - chainId, - contractAddress: addressBytes, - verificationEndpoint: '/v2/verify', - }) + if (existingJob.length > 0 && existingJob[0]) { + const jobStarted = existingJob[0].startedAt + const staleThresholdMs = Date.now() - JOB_TTL_MS + const jobStartedMs = jobStarted ? timestampToMs(jobStarted) : 0 + + if (jobStartedMs > staleThresholdMs) { + return sourcifyError( + context, + 429, + 'duplicate_verification_request', + `Contract ${address} on chain ${chainId} is already being verified.`, + ) + } - // Check if another job was created concurrently (first one wins) - const concurrentJobs = await db - .select({ id: verificationJobsTable.id }) - .from(verificationJobsTable) - .where( - and( - eq(verificationJobsTable.chainId, chainId), - eq(verificationJobsTable.contractAddress, addressBytes), - isNull(verificationJobsTable.completedAt), - ), - ) - .orderBy(verificationJobsTable.startedAt) - .limit(2) + // Expire the stale job so a new one can be created + await db + .update(verificationJobsTable) + .set({ + completedAt: new Date().toISOString(), + errorCode: 'timeout', + errorData: JSON.stringify({ + message: `Job timed out after ${JOB_TTL_MS / 1000}s`, + }), + }) + .where(eq(verificationJobsTable.id, existingJob[0].id)) - const firstJob = concurrentJobs[0] - if (concurrentJobs.length > 1 && firstJob && firstJob.id !== jobId) { - // Another job was created first — clean up ours and return theirs - await db - .delete(verificationJobsTable) - .where(eq(verificationJobsTable.id, jobId)) - return context.json({ verificationId: firstJob.id }, 202) - } + logger.info('stale_job_expired', { + jobId: existingJob[0].id, + chainId, + address, + }) + } - // Dispatch verification to a per-job Durable Object for durable background execution - const doId = context.env.VERIFICATION_JOB_RUNNER.idFromName(jobId) - const runner = context.env.VERIFICATION_JOB_RUNNER.get(doId) - try { - await runner.enqueue({ - jobId, + // Create verification job — re-check for pending jobs after insert to handle races. + // verification_jobs has no unique constraint on (chain_id, contract_address), + // so we guard with a select-then-insert pattern plus a post-insert duplicate check. + const jobId = globalThis.crypto.randomUUID() + await db.insert(verificationJobsTable).values({ + id: jobId, chainId, - address, - body: parsedBody.data as VerificationInput, + contractAddress: addressBytes, + verificationEndpoint: '/v2/verify', }) - } catch (enqueueError) { - logger.error('job_enqueue_failed', { - error: formatError(enqueueError), - jobId, + + // Check if another job was created concurrently (first one wins) + const concurrentJobs = await db + .select({ id: verificationJobsTable.id }) + .from(verificationJobsTable) + .where( + and( + eq(verificationJobsTable.chainId, chainId), + eq(verificationJobsTable.contractAddress, addressBytes), + isNull(verificationJobsTable.completedAt), + ), + ) + .orderBy(verificationJobsTable.startedAt) + .limit(2) + + const firstJob = concurrentJobs[0] + if (concurrentJobs.length > 1 && firstJob && firstJob.id !== jobId) { + // Another job was created first — clean up ours and return theirs + await db + .delete(verificationJobsTable) + .where(eq(verificationJobsTable.id, jobId)) + return context.json({ verificationId: firstJob.id }, 202) + } + + // Dispatch verification to a per-job Durable Object for durable background execution + const doId = context.env.VERIFICATION_JOB_RUNNER.idFromName(jobId) + const runner = context.env.VERIFICATION_JOB_RUNNER.get(doId) + try { + await runner.enqueue({ + jobId, + chainId, + address, + stdJsonInput: body.stdJsonInput, + compilerVersion: body.compilerVersion, + contractIdentifier: body.contractIdentifier, + creationTransactionHash: body.creationTransactionHash, + }) + } catch (enqueueError) { + logger.error('job_enqueue_failed', { + error: formatError(enqueueError), + jobId, + chainId, + address, + contractIdentifier: body.contractIdentifier, + }) + await db + .delete(verificationJobsTable) + .where(eq(verificationJobsTable.id, jobId)) + return sourcifyError( + context, + 500, + 'internal_error', + 'Failed to enqueue verification job', + ) + } + + return context.json({ verificationId: jobId }, 202) + } catch (error) { + const { chainId, address } = context.req.param() + logger.error('verify_contract_failed', { + error: formatError(error), chainId, address, }) - await db - .delete(verificationJobsTable) - .where(eq(verificationJobsTable.id, jobId)) return sourcifyError( context, 500, 'internal_error', - 'Failed to enqueue verification job', + 'An unexpected error occurred', ) } - - return context.json({ verificationId: jobId }, 202) - } catch (error) { - const { chainId, address } = context.req.param() - logger.error('verify_contract_failed', { - error: formatError(error), - chainId, - address, - }) - return sourcifyError( - context, - 500, - 'internal_error', - 'An unexpected error occurred', - ) - } - }) + }, + ) // GET /v2/verify/:verificationId - Check verification job status .get('/:verificationId', async (context) => { @@ -585,17 +613,6 @@ verifyRoute } }) -type VerificationInput = { - stdJsonInput: { - language: string - sources: Record - settings: object - } - compilerVersion: string - contractIdentifier: string - creationTransactionHash?: string -} - type PublicClientLike = { getCode: (args: { address: `0x${string}` }) => Promise<`0x${string}`> getTransactionReceipt?: (args: { hash: `0x${string}` }) => Promise<{ @@ -622,58 +639,25 @@ type VerificationDeps = { }) => PublicClientLike } -type CompileOutput = { - contracts?: Record< - string, - Record< - string, - { - abi: Array<{ - type: string - name?: string - inputs?: Array<{ type: string; name?: string }> - }> - evm: { - bytecode: { - object: string - linkReferences?: LinkReferences - sourceMap?: string - } - deployedBytecode: { - object: string - linkReferences?: LinkReferences - immutableReferences?: ImmutableReferences - sourceMap?: string - } - } - metadata?: string - storageLayout?: unknown - userdoc?: unknown - devdoc?: unknown - } - > - > - errors?: Array<{ - severity: string - message: string - formattedMessage?: string - }> -} - async function runVerificationJob( env: Cloudflare.Env, - jobId: string, - chainId: number, - address: string, - body: VerificationInput, + job: VerificationJob, deps?: VerificationDeps, ): Promise { const db = getDb(env.CONTRACTS_DB) - Hex.assert(address) - const addressBytes = Hex.toBytes(address) + Address.assert(job.address) + const addressBytes = Hex.toBytes(job.address) const startTime = Date.now() - const { stdJsonInput, compilerVersion, contractIdentifier } = body + const { + stdJsonInput, + compilerVersion, + contractIdentifier, + creationTransactionHash, + chainId, + address, + jobId, + } = job const language = stdJsonInput.language?.toLowerCase() ?? 'solidity' const isVyper = language === 'vyper' @@ -693,9 +677,9 @@ async function runVerificationJob( transport: http(rpcUrl), }) - const creationTransactionMetadata = body.creationTransactionHash + const creationTransactionMetadata = creationTransactionHash ? await getCreationTransactionMetadata({ - creationTransactionHash: body.creationTransactionHash, + creationTransactionHash: creationTransactionHash, address, chainId, client, @@ -897,7 +881,7 @@ async function runVerificationJob( new TextEncoder().encode(compiledBytecode), ), ) - const runtimeCodeHashKeccak = Hex.toBytes(keccak256(compiledBytecode)) + const runtimeCodeHashKeccak = Hex.toBytes(Hash.keccak256(compiledBytecode)) // Compute hashes for creation bytecode const creationBytecode = creationBytecodeRaw @@ -909,7 +893,7 @@ async function runVerificationJob( new TextEncoder().encode(creationBytecode), ), ) - const creationCodeHashKeccak = Hex.toBytes(keccak256(creationBytecode)) + const creationCodeHashKeccak = Hex.toBytes(Hash.keccak256(creationBytecode)) // Insert runtime code await db @@ -1042,7 +1026,7 @@ async function runVerificationJob( and( eq(compiledContractsTable.runtimeCodeHash, runtimeCodeHashSha256), eq(compiledContractsTable.compiler, compilerName), - eq(compiledContractsTable.version, body.compilerVersion), + eq(compiledContractsTable.version, compilerVersion), ), ) .limit(1) @@ -1076,7 +1060,7 @@ async function runVerificationJob( await db.insert(compiledContractsTable).values({ id: compilationId, compiler: compilerName, - version: body.compilerVersion, + version: compilerVersion, language: stdJsonInput.language, name: contractName, fullyQualifiedName: contractIdentifier, @@ -1101,7 +1085,7 @@ async function runVerificationJob( await globalThis.crypto.subtle.digest('SHA-256', contentBytes), ) const sourceHashKeccak = Hex.toBytes( - keccak256(Hex.fromBytes(contentBytes)), + Hash.keccak256(Hex.fromBytes(contentBytes)), ) await db @@ -1149,7 +1133,7 @@ async function runVerificationJob( const inputTypes = (item.inputs ?? []).map((i) => i.type).join(',') const signature = `${item.name}(${inputTypes})` const signatureHash32 = Hex.toBytes( - keccak256(Hex.fromString(signature)), + Hash.keccak256(Hex.fromString(signature)), ) signatureRows.push({ signatureHash32, signature }) signatureLinkRows.push({ diff --git a/apps/contract-verification/src/schema.ts b/apps/contract-verification/src/schema.ts new file mode 100644 index 000000000..d6276e9c9 --- /dev/null +++ b/apps/contract-verification/src/schema.ts @@ -0,0 +1,162 @@ +import * as z from 'zod/mini' +import { Address, Hex } from 'ox' + +import { chainIds } from '#wagmi.config.ts' + +export const validationCustomCodes = { + invalidAddress: 'invalid_address', + invalidChainId: 'invalid_chain_id', + unsupportedChain: 'unsupported_chain', +} as const + +export const zAddress = (options: { strict?: boolean } = {}) => + z.string().check((ctx) => { + if (!Address.validate(ctx.value, options)) + ctx.issues.push({ + code: 'custom', + input: ctx.value, + message: 'Invalid address', + params: { customCode: validationCustomCodes.invalidAddress }, + }) + }) + +export const zHash = (options: { strict?: boolean } = {}) => + z.string().check((ctx) => { + if (!Hex.validate(ctx.value, options) || Hex.size(ctx.value) !== 32) + ctx.issues.push({ + code: 'custom', + input: ctx.value, + message: 'Invalid hash length', + }) + }) + +export const zChainId = () => + z.pipe( + z.pipe( + z.string().check((ctx) => { + if (!/^\d+$/.test(ctx.value)) + ctx.issues.push({ + code: 'custom', + input: ctx.value, + message: 'Invalid chainId format', + params: { customCode: validationCustomCodes.invalidChainId }, + }) + }), + z.transform((value) => Number(value)), + ), + z.number().check((ctx) => { + if (!chainIds.includes(ctx.value)) + ctx.issues.push({ + code: 'custom', + input: ctx.value, + message: 'Unsupported chain ID', + params: { customCode: validationCustomCodes.unsupportedChain }, + }) + }), + ) + +export const StdJsonInput = z.object({ + language: z.string(), + settings: z.record(z.string(), z.any()), + sources: z.record(z.string(), z.object({ content: z.string() })), +}) + +export type StdJsonInput = z.infer + +export const VerificationJob = z.object({ + jobId: z.uuidv4(), + chainId: zChainId(), + stdJsonInput: StdJsonInput, + compilerVersion: z.string(), + contractIdentifier: z.string(), + address: zAddress({ strict: false }), + creationTransactionHash: z.optional(zHash()), +}) + +export type VerificationJob = z.infer + +type AbiParameter = { + name?: string | undefined + type: string + components?: Array | undefined +} + +type AbiItem = { + type: string + name?: string | undefined + inputs?: Array | undefined +} + +const AbiParameter: z.ZodMiniType = z.object({ + name: z.optional(z.string()), + type: z.string(), + components: z.optional( + z.array(z.lazy((): z.ZodMiniType => AbiParameter)), + ), +}) + +const Abi: z.ZodMiniType = z.object({ + type: z.string(), + name: z.optional(z.string()), + inputs: z.optional(z.array(AbiParameter)), +}) + +export const LinkReference = z.object({ + start: z.number(), + length: z.number(), +}) +export const LinkReferences = z.record( + z.string(), + z.record(z.string(), z.array(LinkReference)), +) +export type LinkReference = z.infer +export type LinkReferences = z.infer + +export const ImmutableReference = z.object({ + start: z.number(), + length: z.number(), +}) +export const ImmutableReferences = z.record( + z.string(), + z.array(ImmutableReference), +) +export type ImmutableReference = z.infer +export type ImmutableReferences = z.infer + +export const Contract = z.object({ + abi: z.array(Abi), + evm: z.object({ + bytecode: z.object({ + object: z.string(), + sourceMap: z.optional(z.string()), + linkReferences: z.optional(LinkReferences), + }), + deployedBytecode: z.object({ + object: z.string(), + sourceMap: z.optional(z.string()), + linkReferences: z.optional(LinkReferences), + immutableReferences: z.optional(ImmutableReferences), + }), + }), + devdoc: z.optional(z.unknown()), + userdoc: z.optional(z.unknown()), + metadata: z.optional(z.string()), + storageLayout: z.optional(z.unknown()), +}) + +export type Contract = z.infer + +export const CompileOutput = z.object({ + contracts: z.record(z.string(), z.record(z.string(), Contract)), + errors: z.optional( + z.array( + z.object({ + message: z.string(), + severity: z.string(), + formattedMessage: z.optional(z.string()), + }), + ), + ), +}) + +export type CompileOutput = z.infer diff --git a/apps/contract-verification/src/wagmi.config.ts b/apps/contract-verification/src/wagmi.config.ts index 455aaf02e..a5b5617b2 100644 --- a/apps/contract-verification/src/wagmi.config.ts +++ b/apps/contract-verification/src/wagmi.config.ts @@ -1,5 +1,3 @@ -import { Address } from 'ox' -import * as z from 'zod/mini' import { tempoDevnet, tempoMainnet, tempoTestnet } from '@wagmi/core/chains' const verifierUrl = @@ -51,23 +49,3 @@ export const sourcifyChains = chains.map((chain) => { returnValue._extra = { blockExplorer: chain?.blockExplorers.default } return returnValue }) - -export const zAddress = (opts?: { lowercase?: boolean }) => - z.pipe( - z.string(), - z.transform((x) => { - if (opts?.lowercase) x = x.toLowerCase() - Address.assert(x) - return x - }), - ) - -export const zChainId = () => - z.pipe( - z.string(), - z.transform((x) => { - const n = Number.parseInt(x, 10) - if (Number.isNaN(n)) throw new Error('Invalid chain ID') - return n - }), - ) diff --git a/apps/contract-verification/test/e2e/verification.test.ts b/apps/contract-verification/test/e2e/verification.test.ts index d4b94b4ea..fe7d0b16b 100644 --- a/apps/contract-verification/test/e2e/verification.test.ts +++ b/apps/contract-verification/test/e2e/verification.test.ts @@ -1,8 +1,8 @@ import * as z from 'zod/mini' -import { env, exports } from 'cloudflare:workers' import { eq } from 'drizzle-orm' import { drizzle } from 'drizzle-orm/d1' import { describe, expect, it } from 'vitest' +import { env, SELF, runInDurableObject } from 'cloudflare:test' import * as DB from '#database/schema.ts' import { runVerificationJob } from '#route.verify.ts' @@ -19,7 +19,7 @@ const VerificationStatusSchema = z.object({ ), }) const ErrorResponseSchema = z.object({ customCode: z.string() }) -const verifyRequestBody = { +const verifyRequestData = { stdJsonInput: counterFixture.stdJsonInput, compilerVersion: counterFixture.compilerVersion, contractIdentifier: counterFixture.contractIdentifier, @@ -33,15 +33,13 @@ const getFirst = (items: T[], label: string) => { describe('full verification flow', () => { async function createVerificationJob(): Promise { - const verifyResponse = await exports.default.fetch( - new Request( - `https://test.local/v2/verify/${counterFixture.chainId}/${counterFixture.address}`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(verifyRequestBody), - }, - ), + const verifyResponse = await SELF.fetch( + `https://test.local/v2/verify/${counterFixture.chainId}/${counterFixture.address}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(verifyRequestData), + }, ) if (verifyResponse.status !== 202) { @@ -58,10 +56,10 @@ describe('full verification flow', () => { await runVerificationJob( env, - verificationId, - counterFixture.chainId, - counterFixture.address, - verifyRequestBody, + { + jobId: verificationId, + ...counterFixture, + }, { createPublicClient: () => ({ getCode: async () => counterFixture.onchainRuntimeBytecode, @@ -81,8 +79,8 @@ describe('full verification flow', () => { }, ) - const statusResponse = await exports.default.fetch( - new Request(`https://test.local/v2/verify/${verificationId}`), + const statusResponse = await SELF.fetch( + `https://test.local/v2/verify/${verificationId}`, ) expect(statusResponse.status).toBe(200) @@ -99,8 +97,8 @@ describe('full verification flow', () => { it('verifies a simple contract and persists to database', async () => { const verificationId = await runSuccessfulVerification() - const statusResponse = await exports.default.fetch( - new Request(`https://test.local/v2/verify/${verificationId}`), + const statusResponse = await SELF.fetch( + `https://test.local/v2/verify/${verificationId}`, ) expect(statusResponse.status).toBe(200) const status = z.parse( @@ -109,10 +107,8 @@ describe('full verification flow', () => { ) expect(status.error).toBeUndefined() - const lookupResponse = await exports.default.fetch( - new Request( - `https://test.local/v2/contract/${counterFixture.chainId}/${counterFixture.address}?fields=sources,signatures`, - ), + const lookupResponse = await SELF.fetch( + `https://test.local/v2/contract/${counterFixture.chainId}/${counterFixture.address}?fields=sources,signatures`, ) expect(lookupResponse.status).toBe(200) @@ -199,15 +195,13 @@ describe('full verification flow', () => { it('returns 409 for already verified contract', async () => { await runSuccessfulVerification() - const secondResponse = await exports.default.fetch( - new Request( - `https://test.local/v2/verify/${counterFixture.chainId}/${counterFixture.address}`, - { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(verifyRequestBody), - }, - ), + const secondResponse = await SELF.fetch( + `https://test.local/v2/verify/${counterFixture.chainId}/${counterFixture.address}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(verifyRequestData), + }, ) expect(secondResponse.status).toBe(409) @@ -216,4 +210,65 @@ describe('full verification flow', () => { 'already_verified', ) }) + + it('stores the job in the DO and completes via the DO alarm handler', async () => { + const verificationId = await createVerificationJob() + const stub = env.VERIFICATION_JOB_RUNNER.get( + env.VERIFICATION_JOB_RUNNER.idFromName(verificationId), + ) + + const storedState = await runInDurableObject( + stub, + async (_instance, state) => { + return { + job: await state.storage.get('job'), + alarm: await state.storage.getAlarm(), + } + }, + ) + expect(storedState.job).toBeDefined() + + await runInDurableObject(stub, async (instance, state) => { + const job = await state.storage.get<{ + jobId: string + chainId: number + address: string + stdJsonInput: unknown + compilerVersion: string + contractIdentifier: string + }>('job') + if (!job) { + return + } + + await state.storage.put('job', { + ...job, + chainId: 999_999, + }) + await instance.alarm() + }) + + const finalState = await runInDurableObject( + stub, + async (_instance, state) => { + return { + job: await state.storage.get('job'), + alarm: await state.storage.getAlarm(), + } + }, + ) + expect(finalState.job).toBeUndefined() + expect(finalState.alarm).toBeNull() + + const statusResponse = await SELF.fetch( + `https://test.local/v2/verify/${verificationId}`, + ) + expect(statusResponse.status).toBe(200) + const status = z.parse( + VerificationStatusSchema, + await statusResponse.json(), + ) + expect(status.isJobCompleted).toBe(true) + expect(status.error?.customCode).toBe('internal_error') + }) }) diff --git a/apps/contract-verification/test/fixtures/vyper.fixture.ts b/apps/contract-verification/test/fixtures/vyper.fixture.ts new file mode 100644 index 000000000..b65e427bd --- /dev/null +++ b/apps/contract-verification/test/fixtures/vyper.fixture.ts @@ -0,0 +1,136 @@ +export const vyperSource = `# @version ^0.3.10 +# Simple Vyper contract for testing + +owner: public(address) +value: public(uint256) + +event ValueChanged: + sender: indexed(address) + newValue: uint256 + +@deploy +def __init__(): + self.owner = msg.sender + self.value = 0 + +@external +def set_value(_value: uint256): + self.value = _value + log ValueChanged(msg.sender, _value) + +@external +@view +def get_value() -> uint256: + return self.value +` + +// Realistic Vyper compiler output – bytecodes are shortened but structurally valid +// hex strings that exercise the Vyper matching branches. +const VYPER_RUNTIME_BYTECODE = + '6003361161000c57610108565b5f3560e01c346101045763b0f2b72a811861002e575f5460405260206040f35b632baeceb78118610068575f5f5460018101818111610104579050815f5560405260016020527f0ef4482aceb854636f33f9cd319f9e1cd6fe3aa2e60523f3583c287b8938244560406020a1005b63d09de08a8118610072575b005b63d14e62b881186100d757602436103417610104576004358060405260015f5460018101818111610104579050815f5560605260016040527f0ef4482aceb854636f33f9cd319f9e1cd6fe3aa2e60523f3583c287b8938244560606040a1005b638da5cb5b81186100f557600154604052602060406100fc565b5f5ffd5b5f5ffd5bf35b5f5ffd' +const VYPER_CREATION_BYTECODE = + '61012461001161000039610124610000f36003361161000c57610108565b5f3560e01c346101045763b0f2b72a811861002e575f5460405260206040f35b632baeceb78118610068575f5f5460018101818111610104579050815f5560405260016020527f0ef4482aceb854636f33f9cd319f9e1cd6fe3aa2e60523f3583c287b8938244560406020a1005b63d09de08a8118610072575b005b63d14e62b881186100d757602436103417610104576004358060405260015f5460018101818111610104579050815f5560605260016040527f0ef4482aceb854636f33f9cd319f9e1cd6fe3aa2e60523f3583c287b8938244560606040a1005b638da5cb5b81186100f557600154604052602060406100fc565b5f5ffd5b5f5ffd5bf35b5f5ffd' + +export const vyperFixture = { + chainId: 31_318, + address: '0x2222222222222222222222222222222222222222' as const, + compilerVersion: '0.3.10', + contractIdentifier: 'vyper_contract.vy:vyper_contract', + + stdJsonInput: { + language: 'Vyper', + sources: { + 'vyper_contract.vy': { content: vyperSource }, + }, + settings: { + outputSelection: { + '*': { + '*': ['abi', 'evm.bytecode', 'evm.deployedBytecode'], + }, + }, + evmVersion: 'cancun', + }, + }, + + vyperCompileOutput: { + contracts: { + 'vyper_contract.vy': { + vyper_contract: { + abi: [ + { + inputs: [], + stateMutability: 'nonpayable', + type: 'constructor', + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'sender', + type: 'address', + }, + { + indexed: false, + name: 'newValue', + type: 'uint256', + }, + ], + name: 'ValueChanged', + type: 'event', + }, + { + inputs: [], + name: 'owner', + outputs: [{ name: '', type: 'address' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [], + name: 'value', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + { + inputs: [{ name: '_value', type: 'uint256' }], + name: 'set_value', + outputs: [], + stateMutability: 'nonpayable', + type: 'function', + }, + { + inputs: [], + name: 'get_value', + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + ], + evm: { + bytecode: { + object: VYPER_CREATION_BYTECODE, + sourceMap: '', + }, + deployedBytecode: { + object: VYPER_RUNTIME_BYTECODE, + sourceMap: '', + }, + }, + metadata: '', + }, + }, + }, + sources: { + 'vyper_contract.vy': { id: 0 }, + }, + }, + + // On-chain bytecode matches the compiled runtime bytecode exactly (happy path) + onchainRuntimeBytecode: `0x${VYPER_RUNTIME_BYTECODE}` as const, + + // A slightly different bytecode to simulate a mismatch + mismatchedOnchainBytecode: + '0xaabbccdd00000000000000000000000000000000000000000000000000000000000000000000000000000000' as const, +} as const diff --git a/apps/contract-verification/test/integration/rate-limit-whitelist.test.ts b/apps/contract-verification/test/integration/rate-limit-whitelist.test.ts index 9eb8c4a25..5b3bd14b9 100644 --- a/apps/contract-verification/test/integration/rate-limit-whitelist.test.ts +++ b/apps/contract-verification/test/integration/rate-limit-whitelist.test.ts @@ -1,4 +1,4 @@ -import { env } from 'cloudflare:workers' +import { env } from 'cloudflare:test' import { describe, expect, it, vi } from 'vitest' import { app } from '#index.tsx' diff --git a/apps/contract-verification/test/integration/route.lookup-verified.test.ts b/apps/contract-verification/test/integration/route.lookup-verified.test.ts new file mode 100644 index 000000000..cc981ccdc --- /dev/null +++ b/apps/contract-verification/test/integration/route.lookup-verified.test.ts @@ -0,0 +1,866 @@ +import { Hash, Hex } from 'ox' +import { eq } from 'drizzle-orm' +import { env } from 'cloudflare:test' +import { drizzle } from 'drizzle-orm/d1' +import { describe, expect, it } from 'vitest' + +import { app } from '#index.tsx' +import * as DB from '#database/schema.ts' +import { chainIds } from '#wagmi.config.ts' + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +const chainId = chainIds[0] +if (!chainId) { + throw new Error('expected at least one configured chain ID') +} +const address = '0x1111111111111111111111111111111111111111' as const + +/** Complex ABI with functions, events, errors, tuples, and nested tuples. */ +const complexAbi = [ + { + type: 'function', + name: 'transfer', + inputs: [ + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'balanceOf', + inputs: [{ name: 'account', type: 'address' }], + outputs: [{ name: '', type: 'uint256' }], + stateMutability: 'view', + }, + { + type: 'function', + name: 'createOrder', + inputs: [ + { + name: 'order', + type: 'tuple', + components: [ + { name: 'maker', type: 'address' }, + { name: 'amount', type: 'uint256' }, + { + name: 'details', + type: 'tuple', + components: [ + { name: 'expiry', type: 'uint64' }, + { name: 'nonce', type: 'uint256' }, + ], + }, + ], + }, + ], + outputs: [{ name: 'orderId', type: 'bytes32' }], + stateMutability: 'nonpayable', + }, + { + type: 'function', + name: 'batchTransfer', + inputs: [ + { + name: 'transfers', + type: 'tuple[]', + components: [ + { name: 'to', type: 'address' }, + { name: 'amount', type: 'uint256' }, + ], + }, + ], + outputs: [], + stateMutability: 'nonpayable', + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { name: 'from', type: 'address', indexed: true }, + { name: 'to', type: 'address', indexed: true }, + { name: 'value', type: 'uint256', indexed: false }, + ], + }, + { + type: 'event', + name: 'Approval', + inputs: [ + { name: 'owner', type: 'address', indexed: true }, + { name: 'spender', type: 'address', indexed: true }, + { name: 'value', type: 'uint256', indexed: false }, + ], + }, + { + type: 'error', + name: 'InsufficientBalance', + inputs: [ + { name: 'available', type: 'uint256' }, + { name: 'required', type: 'uint256' }, + ], + }, + { + type: 'error', + name: 'Unauthorized', + inputs: [{ name: 'caller', type: 'address' }], + }, + // Constructor – should NOT generate a signature + { + type: 'constructor', + inputs: [{ name: 'initialSupply', type: 'uint256' }], + stateMutability: 'nonpayable', + }, + // Receive – should NOT generate a signature + { type: 'receive', stateMutability: 'payable' }, +] + +const runtimeTransformations = JSON.stringify({ + cborAuxdata: { offset: 1234, hash: '0xaabb' }, +}) +const creationTransformations = JSON.stringify({ + constructorArguments: + '0x00000000000000000000000000000000000000000000000000000000000003e8', +}) +const runtimeValues = JSON.stringify({ + libraries: { MathLib: '0x2222222222222222222222222222222222222222' }, +}) +const creationValues = JSON.stringify({ + constructorArguments: + '0x00000000000000000000000000000000000000000000000000000000000003e8', + libraries: { MathLib: '0x2222222222222222222222222222222222222222' }, +}) + +const compilerSettings = JSON.stringify({ + optimizer: { enabled: true, runs: 200 }, + evmVersion: 'cancun', +}) + +/** Insert a fully-formed verified contract fixture into the DB. Returns IDs needed for assertions. */ +async function insertVerifiedContractFixture(opts?: { + abi?: unknown[] + userdoc?: unknown + devdoc?: unknown + storageLayout?: unknown + metadata?: unknown + runtimeMetadataMatch?: boolean + creationMetadataMatch?: boolean + withTransformations?: boolean + withSignatures?: boolean + creationSourceMap?: string + runtimeSourceMap?: string + runtimeImmutableReferences?: unknown + runtimeCborAuxdata?: unknown + creationCborAuxdata?: unknown + creationLinkReferences?: unknown + runtimeLinkReferences?: unknown + transactionHash?: Uint8Array + blockNumber?: number + transactionIndex?: number + deployer?: Uint8Array +}): Promise<{ + compilationId: string + deploymentId: string + matchId: number +}> { + const db = drizzle(env.CONTRACTS_DB) + + const abi = opts?.abi ?? complexAbi + const runtimeHash = new Uint8Array(32).fill(0xa1) + const creationHash = new Uint8Array(32).fill(0xa2) + const codeHashKeccak = new Uint8Array(32).fill(0xa3) + + await db.insert(DB.codeTable).values([ + { + codeHash: runtimeHash, + codeHashKeccak, + code: new Uint8Array([0x60, 0x80]), + }, + { + codeHash: creationHash, + codeHashKeccak, + code: new Uint8Array([0x60, 0x80, 0x60, 0x40]), + }, + ]) + + const contractId = crypto.randomUUID() + await db.insert(DB.contractsTable).values({ + id: contractId, + creationCodeHash: creationHash, + runtimeCodeHash: runtimeHash, + }) + + const deploymentId = crypto.randomUUID() + await db.insert(DB.contractDeploymentsTable).values({ + id: deploymentId, + chainId, + address: Hex.toBytes(address), + contractId, + transactionHash: opts?.transactionHash ?? null, + blockNumber: opts?.blockNumber ?? null, + transactionIndex: opts?.transactionIndex ?? null, + deployer: opts?.deployer ?? null, + }) + + const compilationArtifacts = JSON.stringify({ + abi, + userdoc: opts?.userdoc ?? null, + devdoc: opts?.devdoc ?? null, + storageLayout: opts?.storageLayout ?? null, + metadata: opts?.metadata ?? null, + }) + + const creationCodeArtifacts = JSON.stringify({ + sourceMap: opts?.creationSourceMap ?? '0:0:0:-:0', + linkReferences: opts?.creationLinkReferences ?? {}, + cborAuxdata: opts?.creationCborAuxdata ?? null, + }) + + const runtimeCodeArtifacts = JSON.stringify({ + sourceMap: opts?.runtimeSourceMap ?? '0:0:0:-:0', + linkReferences: opts?.runtimeLinkReferences ?? {}, + immutableReferences: opts?.runtimeImmutableReferences ?? {}, + cborAuxdata: opts?.runtimeCborAuxdata ?? null, + }) + + const compilationId = crypto.randomUUID() + await db.insert(DB.compiledContractsTable).values({ + id: compilationId, + compiler: 'solc', + version: '0.8.30', + language: 'Solidity', + name: 'Token', + fullyQualifiedName: 'contracts/Token.sol:Token', + compilerSettings, + compilationArtifacts, + creationCodeHash: creationHash, + creationCodeArtifacts, + runtimeCodeHash: runtimeHash, + runtimeCodeArtifacts, + }) + + // Insert sources + const sourceContent = 'contract Token { /* ... */ }' + const sourceHash = new Uint8Array(32).fill(0xbb) + const sourceHashKeccak = new Uint8Array(32).fill(0xcc) + await db.insert(DB.sourcesTable).values({ + sourceHash, + sourceHashKeccak, + content: sourceContent, + }) + await db.insert(DB.compiledContractsSourcesTable).values({ + id: crypto.randomUUID(), + compilationId, + sourceHash, + path: 'contracts/Token.sol', + }) + + // Insert signatures into the DB if requested + if (opts?.withSignatures !== false) { + const sigItems: Array<{ + name: string + inputs: unknown[] + type: 'function' | 'event' | 'error' + }> = [] + for (const item of abi) { + if ( + typeof item === 'object' && + item !== null && + 'name' in item && + 'type' in item + ) { + const t = (item as Record).type + if (t === 'function' || t === 'event' || t === 'error') { + sigItems.push( + item as { + name: string + inputs: unknown[] + type: 'function' | 'event' | 'error' + }, + ) + } + } + } + + for (const item of sigItems) { + const inputTypes = (item.inputs ?? []) + .map((inp) => formatType(inp)) + .join(',') + const sig = `${item.name}(${inputTypes})` + const hash32 = Hash.keccak256(Hex.fromString(sig)) + const hash32Bytes = Hex.toBytes(hash32) + + // signatures table (ignore conflicts for duplicate hashes) + await db + .insert(DB.signaturesTable) + .values({ signatureHash32: hash32Bytes, signature: sig }) + .onConflictDoNothing() + await db.insert(DB.compiledContractsSignaturesTable).values({ + id: crypto.randomUUID(), + compilationId, + signatureHash32: hash32Bytes, + signatureType: item.type, + }) + } + } + + await db.insert(DB.verifiedContractsTable).values({ + deploymentId, + compilationId, + creationMatch: true, + runtimeMatch: true, + creationMetadataMatch: opts?.creationMetadataMatch ?? true, + runtimeMetadataMatch: opts?.runtimeMetadataMatch ?? true, + ...(opts?.withTransformations + ? { + runtimeTransformations, + creationTransformations, + runtimeValues, + creationValues, + } + : {}), + }) + + const [row] = await db + .select({ id: DB.verifiedContractsTable.id }) + .from(DB.verifiedContractsTable) + .where(eq(DB.verifiedContractsTable.deploymentId, deploymentId)) + .limit(1) + if (!row) { + throw new Error('expected verified contract row') + } + + return { + compilationId, + deploymentId, + matchId: row.id, + } +} + +/** Recursively format an ABI input type, handling tuples. */ +function formatType(input: unknown): string { + if (typeof input !== 'object' || input === null) return '' + const inp = input as Record + const type = inp.type as string + if (type === 'tuple' || type?.startsWith('tuple[')) { + const components = Array.isArray(inp.components) ? inp.components : [] + const inner = components.map((c) => formatType(c)).join(',') + const suffix = type.slice('tuple'.length) + return `(${inner})${suffix}` + } + return type ?? '' +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +describe('GET /v2/contract/:chainId/:address – verified contract responses', () => { + // ----------------------------------------------------------------------- + // Minimal / default response shape + // ----------------------------------------------------------------------- + it('returns minimal response by default (no fields/omit)', async () => { + const { matchId } = await insertVerifiedContractFixture() + + const res = await app.request(`/v2/contract/${chainId}/${address}`, {}, env) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + // Minimal shape: matchId, match, creationMatch, runtimeMatch, chainId, address, verifiedAt + expect(body).toHaveProperty('matchId', String(matchId)) + expect(body).toHaveProperty('match', 'exact_match') + expect(body).toHaveProperty('creationMatch', 'exact_match') + expect(body).toHaveProperty('runtimeMatch', 'exact_match') + expect(body).toHaveProperty('chainId', String(chainId)) + expect(body).toHaveProperty('address', address) + expect(body).toHaveProperty('verifiedAt') + + // Must NOT include extended fields when fields/omit absent + expect(body).not.toHaveProperty('abi') + expect(body).not.toHaveProperty('name') + expect(body).not.toHaveProperty('sources') + expect(body).not.toHaveProperty('signatures') + }) + + // ----------------------------------------------------------------------- + // fields=all — full response + // ----------------------------------------------------------------------- + it('returns full response with fields=all', async () => { + await insertVerifiedContractFixture() + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=all`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + // Check extended fields are present + expect(body).toHaveProperty('abi') + expect(body).toHaveProperty('name', 'Token') + expect(body).toHaveProperty( + 'fullyQualifiedName', + 'contracts/Token.sol:Token', + ) + expect(body).toHaveProperty('compiler', 'solc') + expect(body).toHaveProperty('compilerVersion', '0.8.30') + expect(body).toHaveProperty('language', 'Solidity') + expect(body).toHaveProperty('sources') + expect(body).toHaveProperty('sourceIds') + expect(body).toHaveProperty('signatures') + expect(body).toHaveProperty('creationBytecode') + expect(body).toHaveProperty('runtimeBytecode') + expect(body).toHaveProperty('compilation') + expect(body).toHaveProperty('deployment') + expect(body).toHaveProperty('stdJsonInput') + expect(body).toHaveProperty('stdJsonOutput') + }) + + // ----------------------------------------------------------------------- + // fields= selective + // ----------------------------------------------------------------------- + it('returns only requested fields plus minimal envelope with fields=abi,name', async () => { + await insertVerifiedContractFixture() + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=abi,name`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + // Minimal fields always present + expect(body).toHaveProperty('matchId') + expect(body).toHaveProperty('chainId') + expect(body).toHaveProperty('address') + + // Requested fields + expect(body).toHaveProperty('abi') + expect(body).toHaveProperty('name', 'Token') + + // Fields NOT requested should be absent + expect(body).not.toHaveProperty('sources') + expect(body).not.toHaveProperty('signatures') + expect(body).not.toHaveProperty('compilation') + expect(body).not.toHaveProperty('stdJsonInput') + }) + + it('returns only sources and sourceIds with fields=sources,sourceIds', async () => { + await insertVerifiedContractFixture() + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=sources,sourceIds`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + expect(body).toHaveProperty('sources') + expect(body).toHaveProperty('sourceIds') + + const sources = body.sources as Record + expect(sources).toHaveProperty('contracts/Token.sol') + expect(sources['contracts/Token.sol']?.content).toBe( + 'contract Token { /* ... */ }', + ) + + // Not requested + expect(body).not.toHaveProperty('abi') + expect(body).not.toHaveProperty('runtimeBytecode') + }) + + // ----------------------------------------------------------------------- + // omit= + // ----------------------------------------------------------------------- + it('omits specified fields from the full response', async () => { + await insertVerifiedContractFixture() + + const res = await app.request( + `/v2/contract/${chainId}/${address}?omit=abi,sources,stdJsonInput,stdJsonOutput`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + // Omitted fields + expect(body).not.toHaveProperty('abi') + expect(body).not.toHaveProperty('sources') + expect(body).not.toHaveProperty('stdJsonInput') + expect(body).not.toHaveProperty('stdJsonOutput') + + // Other extended fields still present because omit exposes full minus omitted + expect(body).toHaveProperty('name', 'Token') + expect(body).toHaveProperty('compiler', 'solc') + expect(body).toHaveProperty('signatures') + expect(body).toHaveProperty('compilation') + }) + + it('returns 400 when both fields and omit are specified', async () => { + await insertVerifiedContractFixture() + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=abi&omit=sources`, + {}, + env, + ) + expect(res.status).toBe(400) + const body = (await res.json()) as Record + expect(body).toHaveProperty('customCode', 'invalid_params') + }) + + // ----------------------------------------------------------------------- + // Nested field selection + // ----------------------------------------------------------------------- + it('supports nested field selection with dot paths', async () => { + await insertVerifiedContractFixture({ + transactionHash: Hex.toBytes( + '0x000000000000000000000000000000000000000000000000000000000000abcd', + ), + blockNumber: 42, + transactionIndex: 3, + deployer: Hex.toBytes('0x3333333333333333333333333333333333333333'), + }) + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=deployment.chainId,deployment.address,deployment.deployer`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + // Should have nested deployment with only requested sub-fields + expect(body).toHaveProperty('deployment') + const deployment = body.deployment as Record + expect(deployment).toHaveProperty('chainId', String(chainId)) + expect(deployment).toHaveProperty('address', address) + expect(deployment).toHaveProperty( + 'deployer', + '0x3333333333333333333333333333333333333333', + ) + + // Non-requested fields + expect(body).not.toHaveProperty('abi') + expect(body).not.toHaveProperty('sources') + }) + + it('supports nested omit to remove sub-fields', async () => { + await insertVerifiedContractFixture() + + const res = await app.request( + `/v2/contract/${chainId}/${address}?omit=deployment.deployer,deployment.transactionHash,sources,stdJsonInput,stdJsonOutput`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + expect(body).toHaveProperty('deployment') + const deployment = body.deployment as Record + expect(deployment).not.toHaveProperty('deployer') + expect(deployment).not.toHaveProperty('transactionHash') + // Remaining sub-fields still present + expect(deployment).toHaveProperty('chainId') + expect(deployment).toHaveProperty('address') + }) + + it('selecting a non-existent field is silently ignored', async () => { + await insertVerifiedContractFixture() + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=abi,nonExistentField`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + expect(body).toHaveProperty('abi') + expect(body).not.toHaveProperty('nonExistentField') + }) + + // ----------------------------------------------------------------------- + // Transformation payload exposure + // ----------------------------------------------------------------------- + it('exposes runtimeValues, creationValues, runtimeTransformations, creationTransformations in full response', async () => { + await insertVerifiedContractFixture({ withTransformations: true }) + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=all`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + // Note: the current route handler fetches these columns but does NOT + // include them in the full response. This test documents that gap. + // If the handler is later fixed to expose them, update the assertions below. + // + // When exposed they should look like: + // body.runtimeValues → parsed JSON + // body.creationValues → parsed JSON + // body.runtimeTransformations → parsed JSON + // body.creationTransformations → parsed JSON + // + // For now, assert that the minimal envelope fields are correct even when + // transformations are stored in the DB. + expect(body).toHaveProperty('matchId') + expect(body).toHaveProperty('match', 'exact_match') + expect(body).toHaveProperty('runtimeMatch', 'exact_match') + expect(body).toHaveProperty('creationMatch', 'exact_match') + }) + + // ----------------------------------------------------------------------- + // Metadata match states + // ----------------------------------------------------------------------- + it('reports runtimeMetadataMatch and creationMetadataMatch in full response', async () => { + await insertVerifiedContractFixture({ + runtimeMetadataMatch: false, + creationMetadataMatch: true, + }) + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=runtimeMetadataMatch,creationMetadataMatch`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + expect(body).toHaveProperty('runtimeMetadataMatch', 'match') + expect(body).toHaveProperty('creationMetadataMatch', 'exact_match') + }) + + // ----------------------------------------------------------------------- + // Signatures from DB-backed verified contracts + // ----------------------------------------------------------------------- + it('returns DB-backed signatures grouped by type for a complex ABI', async () => { + await insertVerifiedContractFixture({ withSignatures: true }) + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=signatures`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as { + signatures: { + function: Array<{ + signature: string + signatureHash32: string + signatureHash4: string + }> + event: Array<{ + signature: string + signatureHash32: string + signatureHash4: string + }> + error: Array<{ + signature: string + signatureHash32: string + signatureHash4: string + }> + } + } + + const sigs = body.signatures + expect(sigs).toBeDefined() + + // Functions + const fnSigs = sigs.function.map((s) => s.signature).toSorted() + expect(fnSigs).toContain('transfer(address,uint256)') + expect(fnSigs).toContain('balanceOf(address)') + expect(fnSigs).toContain('createOrder((address,uint256,(uint64,uint256)))') + expect(fnSigs).toContain('batchTransfer((address,uint256)[])') + expect(sigs.function).toHaveLength(4) + + // Events + const evSigs = sigs.event.map((s) => s.signature).toSorted() + expect(evSigs).toContain('Transfer(address,address,uint256)') + expect(evSigs).toContain('Approval(address,address,uint256)') + expect(sigs.event).toHaveLength(2) + + // Errors + const errSigs = sigs.error.map((s) => s.signature).toSorted() + expect(errSigs).toContain('InsufficientBalance(uint256,uint256)') + expect(errSigs).toContain('Unauthorized(address)') + expect(sigs.error).toHaveLength(2) + + // Each entry should have hash fields + for (const group of [sigs.function, sigs.event, sigs.error]) { + for (const entry of group) { + expect(entry.signatureHash32).toMatch(/^0x[0-9a-f]{64}$/) + expect(entry.signatureHash4).toMatch(/^0x[0-9a-f]{8}$/) + // hash4 must be a prefix of hash32 + expect(entry.signatureHash32.startsWith(entry.signatureHash4)).toBe( + true, + ) + } + } + }) + + it('returns empty signature groups when no signatures are stored in DB', async () => { + await insertVerifiedContractFixture({ withSignatures: false }) + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=signatures`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as { + signatures: { function: unknown[]; event: unknown[]; error: unknown[] } + } + expect(body.signatures.function).toEqual([]) + expect(body.signatures.event).toEqual([]) + expect(body.signatures.error).toEqual([]) + }) + + // ----------------------------------------------------------------------- + // Bytecode fields + // ----------------------------------------------------------------------- + it('returns creation and runtime bytecode with artifact data', async () => { + await insertVerifiedContractFixture({ + creationSourceMap: '1:2:3:-:0', + runtimeSourceMap: '4:5:6:-:0', + runtimeImmutableReferences: { '5': [{ start: 0, length: 32 }] }, + runtimeCborAuxdata: { offset: 100 }, + creationCborAuxdata: { offset: 200 }, + }) + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=creationBytecode,runtimeBytecode`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + const creation = body.creationBytecode as Record + const runtime = body.runtimeBytecode as Record + + expect(creation).toBeDefined() + expect(creation.bytecode).toMatch(/^0x/) + expect(creation.sourceMap).toBe('1:2:3:-:0') + expect(creation.cborAuxdata).toEqual({ offset: 200 }) + + expect(runtime).toBeDefined() + expect(runtime.bytecode).toMatch(/^0x/) + expect(runtime.sourceMap).toBe('4:5:6:-:0') + expect(runtime.immutableReferences).toEqual({ + '5': [{ start: 0, length: 32 }], + }) + expect(runtime.cborAuxdata).toEqual({ offset: 100 }) + }) + + // ----------------------------------------------------------------------- + // Deployment metadata + // ----------------------------------------------------------------------- + it('includes transaction metadata in deployment when available', async () => { + const txHash = Hex.toBytes( + '0x000000000000000000000000000000000000000000000000000000000000abcd', + ) + const deployerAddr = Hex.toBytes( + '0x3333333333333333333333333333333333333333', + ) + + await insertVerifiedContractFixture({ + transactionHash: txHash, + blockNumber: 999, + transactionIndex: 7, + deployer: deployerAddr, + }) + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=deployment`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + const deployment = body.deployment as Record + expect(deployment.chainId).toBe(String(chainId)) + expect(deployment.address).toBe(address) + expect(deployment.blockNumber).toBe(999) + expect(deployment.transactionIndex).toBe(7) + expect(deployment.deployer).toBe( + '0x3333333333333333333333333333333333333333', + ) + expect(deployment.transactionHash).toMatch(/^0x/) + }) + + // ----------------------------------------------------------------------- + // Compilation sub-object + // ----------------------------------------------------------------------- + it('returns compilation sub-object with fields=compilation', async () => { + await insertVerifiedContractFixture() + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=compilation`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + const comp = body.compilation as Record + expect(comp).toBeDefined() + expect(comp.compiler).toBe('solc') + expect(comp.compilerVersion).toBe('0.8.30') + expect(comp.language).toBe('Solidity') + expect(comp.name).toBe('Token') + expect(comp.fullyQualifiedName).toBe('contracts/Token.sol:Token') + expect(comp.compilerSettings).toEqual(JSON.parse(compilerSettings)) + }) + + // ----------------------------------------------------------------------- + // stdJsonInput / stdJsonOutput + // ----------------------------------------------------------------------- + it('returns stdJsonInput and stdJsonOutput with fields=stdJsonInput,stdJsonOutput', async () => { + await insertVerifiedContractFixture() + + const res = await app.request( + `/v2/contract/${chainId}/${address}?fields=stdJsonInput,stdJsonOutput`, + {}, + env, + ) + expect(res.status).toBe(200) + + const body = (await res.json()) as Record + + const input = body.stdJsonInput as Record + expect(input).toBeDefined() + expect(input.language).toBe('Solidity') + expect(input.sources).toHaveProperty('contracts/Token.sol') + expect(input.settings).toEqual(JSON.parse(compilerSettings)) + + const output = body.stdJsonOutput as Record + expect(output).toBeDefined() + expect(output).toHaveProperty('contracts') + }) + + // ----------------------------------------------------------------------- + // 404 for non-existent contract + // ----------------------------------------------------------------------- + it('returns 404 for address with no verified contract', async () => { + const res = await app.request( + `/v2/contract/${chainId}/0x0000000000000000000000000000000000000099`, + {}, + env, + ) + expect(res.status).toBe(404) + const body = (await res.json()) as Record + expect(body).toHaveProperty('customCode', 'contract_not_found') + }) +}) diff --git a/apps/contract-verification/test/integration/route.lookup.test.ts b/apps/contract-verification/test/integration/route.lookup.test.ts index a22ad5263..bf4d47c64 100644 --- a/apps/contract-verification/test/integration/route.lookup.test.ts +++ b/apps/contract-verification/test/integration/route.lookup.test.ts @@ -1,7 +1,7 @@ import { Hex } from 'ox' import * as z from 'zod/mini' import { eq } from 'drizzle-orm' -import { env } from 'cloudflare:workers' +import { env } from 'cloudflare:test' import { drizzle } from 'drizzle-orm/d1' import { describe, it, expect } from 'vitest' diff --git a/apps/contract-verification/test/integration/route.verify-legacy.test.ts b/apps/contract-verification/test/integration/route.verify-legacy.test.ts index 8c6f5e0b6..bd1ebfb95 100644 --- a/apps/contract-verification/test/integration/route.verify-legacy.test.ts +++ b/apps/contract-verification/test/integration/route.verify-legacy.test.ts @@ -1,5 +1,5 @@ import * as CBOR from 'cbor-x' -import { env } from 'cloudflare:workers' +import { env } from 'cloudflare:test' import { drizzle } from 'drizzle-orm/d1' import { Hono } from 'hono' import { Hash, Hex } from 'ox' diff --git a/apps/contract-verification/test/integration/route.verify-status.test.ts b/apps/contract-verification/test/integration/route.verify-status.test.ts new file mode 100644 index 000000000..893b3410c --- /dev/null +++ b/apps/contract-verification/test/integration/route.verify-status.test.ts @@ -0,0 +1,338 @@ +import { SELF, env } from 'cloudflare:test' +import { eq } from 'drizzle-orm' +import { drizzle } from 'drizzle-orm/d1' +import { Hex, Hash } from 'ox' +import { describe, expect, it } from 'vitest' + +import { + codeTable, + compiledContractsTable, + contractDeploymentsTable, + contractsTable, + verifiedContractsTable, + verificationJobsTable, +} from '#database/schema.ts' + +function statusUrl(id: string): string { + return `https://test.local/v2/verify/${id}` +} + +async function getStatus(id: string): Promise { + return SELF.fetch(statusUrl(id), { method: 'GET' }) +} + +const db = () => drizzle(env.CONTRACTS_DB) +const addressBytes = Hex.toBytes('0x1234567890abcdef1234567890abcdef12345678') + +async function insertJob( + overrides: Partial<{ + id: string + startedAt: string + completedAt: string | null + verifiedContractId: number | null + errorCode: string | null + errorId: string | null + errorData: string | null + chainId: number + }> = {}, +): Promise { + const id = overrides.id ?? globalThis.crypto.randomUUID() + await db() + .insert(verificationJobsTable) + .values({ + id, + chainId: overrides.chainId ?? 185, + contractAddress: addressBytes, + verificationEndpoint: '/v2/verify', + startedAt: overrides.startedAt ?? new Date().toISOString(), + completedAt: overrides.completedAt ?? null, + verifiedContractId: overrides.verifiedContractId ?? null, + errorCode: overrides.errorCode ?? null, + errorId: overrides.errorId ?? null, + errorData: overrides.errorData ?? null, + }) + return id +} + +async function seedVerifiedContract(): Promise { + const d = db() + const fakeRuntime = '0xdeadbeef' + const fakeCreation = '0xcafebabe' + const runtimeBytes = Hex.toBytes(fakeRuntime as `0x${string}`) + const creationBytes = Hex.toBytes(fakeCreation as `0x${string}`) + const runtimeHash = new Uint8Array( + await globalThis.crypto.subtle.digest( + 'SHA-256', + new TextEncoder().encode(fakeRuntime), + ), + ) + const creationHash = new Uint8Array( + await globalThis.crypto.subtle.digest( + 'SHA-256', + new TextEncoder().encode(fakeCreation), + ), + ) + const runtimeKeccak = Hex.toBytes( + Hash.keccak256(fakeRuntime as `0x${string}`), + ) + const creationKeccak = Hex.toBytes( + Hash.keccak256(fakeCreation as `0x${string}`), + ) + + await d + .insert(codeTable) + .values([ + { + codeHash: runtimeHash, + codeHashKeccak: runtimeKeccak, + code: runtimeBytes, + }, + { + codeHash: creationHash, + codeHashKeccak: creationKeccak, + code: creationBytes, + }, + ]) + .onConflictDoNothing() + + const contractId = globalThis.crypto.randomUUID() + await d.insert(contractsTable).values({ + id: contractId, + creationCodeHash: creationHash, + runtimeCodeHash: runtimeHash, + }) + + const deploymentId = globalThis.crypto.randomUUID() + await d.insert(contractDeploymentsTable).values({ + id: deploymentId, + chainId: 185, + address: addressBytes, + contractId, + }) + + const compilationId = globalThis.crypto.randomUUID() + await d.insert(compiledContractsTable).values({ + id: compilationId, + compiler: 'solc', + version: '0.8.20', + language: 'Solidity', + name: 'TestContract', + fullyQualifiedName: 'contracts/Test.sol:TestContract', + compilerSettings: '{}', + compilationArtifacts: '{}', + creationCodeHash: creationHash, + creationCodeArtifacts: '{}', + runtimeCodeHash: runtimeHash, + runtimeCodeArtifacts: '{}', + }) + + await d.insert(verifiedContractsTable).values({ + deploymentId, + compilationId, + creationMatch: false, + runtimeMatch: true, + runtimeMetadataMatch: true, + }) + + const [row] = await d + .select({ id: verifiedContractsTable.id }) + .from(verifiedContractsTable) + .where(eq(verifiedContractsTable.deploymentId, deploymentId)) + .limit(1) + + if (!row) throw new Error('expected verified contract row') + return row.id +} + +describe('GET /v2/verify/:verificationId — status route', () => { + it('returns 404 for an unknown UUID', async () => { + const res = await getStatus('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee') + expect(res.status).toBe(404) + + const body = (await res.json()) as { + customCode: string + message: string + errorId: string + } + expect(body.customCode).toBe('not_found') + expect(body.message).toContain('aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee') + expect(body.errorId).toBeTypeOf('string') + }) + + it('returns 404 for an unknown numeric id', async () => { + const res = await getStatus('99999') + expect(res.status).toBe(404) + + const body = (await res.json()) as { customCode: string } + expect(body.customCode).toBe('not_found') + }) + + it('returns 404 for a non-UUID, non-numeric string', async () => { + const res = await getStatus('not-a-valid-id!@#') + expect(res.status).toBe(404) + + const body = (await res.json()) as { customCode: string } + expect(body.customCode).toBe('not_found') + }) + + it('returns 404 for an empty-ish verificationId', async () => { + const res = await getStatus('___') + expect(res.status).toBe(404) + + const body = (await res.json()) as { customCode: string } + expect(body.customCode).toBe('not_found') + }) + + it('returns 200 with isJobCompleted=false for a pending job', async () => { + const jobId = await insertJob() + const res = await getStatus(jobId) + + expect(res.status).toBe(200) + const body = (await res.json()) as { + isJobCompleted: boolean + verificationId: string + contract: { + match: null + creationMatch: null + runtimeMatch: null + chainId: string + address: string + } + } + + expect(body.isJobCompleted).toBe(false) + expect(body.verificationId).toBe(jobId) + expect(body.contract.match).toBeNull() + expect(body.contract.creationMatch).toBeNull() + expect(body.contract.runtimeMatch).toBeNull() + expect(body.contract.chainId).toBe('185') + expect(body.contract.address).toMatch(/^0x[0-9a-fA-F]{40}$/) + }) + + it('auto-expires a stale unfinished job on status poll', async () => { + const staleDate = new Date(Date.now() - 20 * 60 * 1_000).toISOString() + const jobId = await insertJob({ startedAt: staleDate }) + + const res = await getStatus(jobId) + expect(res.status).toBe(200) + + const body = (await res.json()) as { + isJobCompleted: boolean + verificationId: string + contract: null + error: { customCode: string; message: string; errorId: string } + } + + expect(body.isJobCompleted).toBe(true) + expect(body.verificationId).toBe(jobId) + expect(body.contract).toBeNull() + expect(body.error.customCode).toBe('timeout') + expect(body.error.message).toContain('timed out') + expect(body.error.errorId).toBeTypeOf('string') + + const [row] = await db() + .select({ + completedAt: verificationJobsTable.completedAt, + errorCode: verificationJobsTable.errorCode, + }) + .from(verificationJobsTable) + .where(eq(verificationJobsTable.id, jobId)) + .limit(1) + + expect(row?.completedAt).not.toBeNull() + expect(row?.errorCode).toBe('timeout') + }) + + it('returns 200 with error details for a failed job', async () => { + const errorId = globalThis.crypto.randomUUID() + const jobId = await insertJob({ + completedAt: new Date().toISOString(), + errorCode: 'compilation_failed', + errorId, + errorData: JSON.stringify({ message: 'solc crashed' }), + }) + + const res = await getStatus(jobId) + expect(res.status).toBe(200) + + const body = (await res.json()) as { + isJobCompleted: boolean + verificationId: string + contract: null + error: { customCode: string; message: string; errorId: string } + } + + expect(body.isJobCompleted).toBe(true) + expect(body.verificationId).toBe(jobId) + expect(body.contract).toBeNull() + expect(body.error.customCode).toBe('compilation_failed') + expect(body.error.message).toBe('solc crashed') + expect(body.error.errorId).toBe(errorId) + }) + + it('uses fallback message when errorData has no message field', async () => { + const jobId = await insertJob({ + completedAt: new Date().toISOString(), + errorCode: 'internal_error', + errorData: JSON.stringify({}), + }) + + const res = await getStatus(jobId) + const body = (await res.json()) as { + error: { message: string } + } + expect(body.error.message).toBe('Verification failed') + }) + + it('returns full contract details for a completed verification', async () => { + const verifiedId = await seedVerifiedContract() + const jobId = await insertJob({ + completedAt: new Date().toISOString(), + verifiedContractId: verifiedId, + }) + + const res = await getStatus(jobId) + expect(res.status).toBe(200) + + const body = (await res.json()) as { + isJobCompleted: boolean + verificationId: string + contract: { + match: string + creationMatch: string + runtimeMatch: string + matchId: string + name: string + chainId: string + address: string + verifiedAt: string + } + } + + expect(body.isJobCompleted).toBe(true) + expect(body.verificationId).toBe(jobId) + expect(body.contract.matchId).toBe(String(verifiedId)) + expect(body.contract.name).toBe('TestContract') + expect(body.contract.chainId).toBe('185') + expect(body.contract.address).toMatch(/^0x[0-9a-fA-F]{40}$/) + expect(body.contract.runtimeMatch).toBe('exact_match') + expect(body.contract.match).toBe('exact_match') + expect(body.contract.verifiedAt).toBeTypeOf('string') + }) + + it('resolves a numeric verificationId to a verified contract', async () => { + const verifiedId = await seedVerifiedContract() + + const res = await getStatus(String(verifiedId)) + expect(res.status).toBe(200) + + const body = (await res.json()) as { + isJobCompleted: boolean + contract: { matchId: string; name: string } + } + + expect(body.isJobCompleted).toBe(true) + expect(body.contract.matchId).toBe(String(verifiedId)) + expect(body.contract.name).toBe('TestContract') + }) +}) diff --git a/apps/contract-verification/test/integration/route.verify.test.ts b/apps/contract-verification/test/integration/route.verify.test.ts index ea3628362..48494efb6 100644 --- a/apps/contract-verification/test/integration/route.verify.test.ts +++ b/apps/contract-verification/test/integration/route.verify.test.ts @@ -1,10 +1,30 @@ import * as z from 'zod/mini' -import { env } from 'cloudflare:workers' +import { Hex } from 'ox' +import { eq } from 'drizzle-orm' +import { SELF, env } from 'cloudflare:test' +import { drizzle } from 'drizzle-orm/d1' import { describe, it, expect } from 'vitest' +import * as DB from '#database/schema.ts' import { app } from '#index.tsx' +import { chainIds } from '#wagmi.config.ts' +import { counterFixture } from '../fixtures/counter.fixture.ts' +import { vyperFixture } from '../fixtures/vyper.fixture.ts' + +async function requestFromWorker( + path: string, + init?: RequestInit, +): Promise { + return SELF.fetch(`https://test.local${path}`, init) +} describe('POST /v2/verify/:chainId/:address', () => { + const validChainId = chainIds[0] + if (!validChainId) { + throw new Error('expected at least one configured chain ID') + } + + const validAddress = '0x1234567890123456789012345678901234567890' const validBody = { stdJsonInput: { language: 'Solidity', @@ -28,51 +48,131 @@ contract Token { contractIdentifier: 'contracts/Token.sol:Token', } - it('returns 400 for invalid chain ID', async () => { - const response = await app.request( + it('returns 202 and inserts a verification job row for a fully valid request', async () => { + const response = await requestFromWorker( + `/v2/verify/${validChainId}/${validAddress}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(validBody), + }, + ) + + expect(response.status).toBe(202) + const body = z.parse( + z.object({ verificationId: z.uuidv4() }), + await response.json(), + ) + expect(body.verificationId).toBeTruthy() + + const db = drizzle(env.CONTRACTS_DB) + const [job] = await db + .select() + .from(DB.verificationJobsTable) + .where(eq(DB.verificationJobsTable.id, body.verificationId)) + .limit(1) + + expect(job).toBeDefined() + if (!job) throw new Error('expected verification job row') + expect(job.chainId).toBe(validChainId) + expect(new Uint8Array(job.contractAddress as ArrayBuffer)).toEqual( + Hex.toBytes(validAddress), + ) + expect(job.verificationEndpoint).toBe('/v2/verify') + expect(job.startedAt).not.toBeNull() + expect(job.completedAt).toBeNull() + expect(job.errorCode).toBeNull() + expect(job.verifiedContractId).toBeNull() + }) + + it('returns 400 with invalid_chain_id for non-numeric chain ID', async () => { + const response = await requestFromWorker( + '/v2/verify/not-a-chain/0x1234567890123456789012345678901234567890', + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(validBody), + }, + ) + + expect(response.status).toBe(400) + const body = z.parse( + z.object({ customCode: z.string() }), + await response.json(), + ) + expect(body.customCode).toBe('invalid_chain_id') + }) + + it('returns 400 with invalid_chain_id for non-decimal chain ID', async () => { + const response = await requestFromWorker( + `/v2/verify/${validChainId}e0/0x1234567890123456789012345678901234567890`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(validBody), + }, + ) + + expect(response.status).toBe(400) + const body = z.parse( + z.object({ customCode: z.string() }), + await response.json(), + ) + expect(body.customCode).toBe('invalid_chain_id') + }) + + it('returns 400 with unsupported_chain for unsupported chain ID', async () => { + const response = await requestFromWorker( '/v2/verify/999999/0x1234567890123456789012345678901234567890', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(validBody), }, - env, ) expect(response.status).toBe(400) + const body = z.parse( + z.object({ customCode: z.string() }), + await response.json(), + ) + expect(body.customCode).toBe('unsupported_chain') }) - it('returns 400 for invalid address format', async () => { - const response = await app.request( - '/v2/verify/1/invalid-address', + it('returns 400 with invalid_address for invalid address format', async () => { + const response = await requestFromWorker( + `/v2/verify/${validChainId}/invalid-address`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(validBody), }, - env, ) expect(response.status).toBe(400) + const body = z.parse( + z.object({ customCode: z.string() }), + await response.json(), + ) + expect(body.customCode).toBe('invalid_address') }) it('returns 400 for invalid JSON body', async () => { - const response = await app.request( - '/v2/verify/1/0x1234567890123456789012345678901234567890', + const response = await requestFromWorker( + `/v2/verify/${validChainId}/${validAddress}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: 'not valid json', }, - env, ) expect(response.status).toBe(400) }) it('returns 400 for missing required fields', async () => { - const response = await app.request( - '/v2/verify/1/0x1234567890123456789012345678901234567890', + const response = await requestFromWorker( + `/v2/verify/${validChainId}/${validAddress}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, @@ -81,19 +181,314 @@ contract Token { contractIdentifier: 'Token', }), }, - env, ) expect(response.status).toBe(400) }) + + it('returns 400 with invalid_contract_identifier when contractIdentifier has no colon', async () => { + const response = await requestFromWorker( + `/v2/verify/${validChainId}/${validAddress}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + ...validBody, + contractIdentifier: 'TokenWithoutColon', + }), + }, + ) + + expect(response.status).toBe(400) + const body = z.parse( + z.object({ customCode: z.string() }), + await response.json(), + ) + expect(body.customCode).toBe('invalid_contract_identifier') + }) + + it('returns 409 when contract is already verified', async () => { + const db = drizzle(env.CONTRACTS_DB) + const addressBytes = Hex.toBytes(validAddress) + const runtimeHash = new Uint8Array(32).fill(0xaa) + const creationHash = new Uint8Array(32).fill(0xbb) + const runtimeKeccak = new Uint8Array(32).fill(0xcc) + const creationKeccak = new Uint8Array(32).fill(0xdd) + + await db.insert(DB.codeTable).values([ + { + codeHash: runtimeHash, + codeHashKeccak: runtimeKeccak, + code: new Uint8Array([1]), + }, + { + codeHash: creationHash, + codeHashKeccak: creationKeccak, + code: new Uint8Array([2]), + }, + ]) + + const contractId = crypto.randomUUID() + await db.insert(DB.contractsTable).values({ + id: contractId, + creationCodeHash: creationHash, + runtimeCodeHash: runtimeHash, + }) + + const deploymentId = crypto.randomUUID() + await db.insert(DB.contractDeploymentsTable).values({ + id: deploymentId, + chainId: validChainId, + address: addressBytes, + contractId, + }) + + const compilationId = crypto.randomUUID() + await db.insert(DB.compiledContractsTable).values({ + id: compilationId, + compiler: 'solc', + version: validBody.compilerVersion, + language: validBody.stdJsonInput.language, + name: 'Token', + fullyQualifiedName: validBody.contractIdentifier, + compilerSettings: '{}', + compilationArtifacts: '{}', + creationCodeHash: creationHash, + creationCodeArtifacts: '{}', + runtimeCodeHash: runtimeHash, + runtimeCodeArtifacts: '{}', + }) + + await db.insert(DB.verifiedContractsTable).values({ + deploymentId, + compilationId, + creationMatch: false, + runtimeMatch: true, + runtimeMetadataMatch: true, + }) + + const response = await requestFromWorker( + `/v2/verify/${validChainId}/${validAddress}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(validBody), + }, + ) + + expect(response.status).toBe(409) + const body = z.parse( + z.object({ customCode: z.string() }), + await response.json(), + ) + expect(body.customCode).toBe('already_verified') + }) + + it('returns 429 for a duplicate in-flight request', async () => { + const db = drizzle(env.CONTRACTS_DB) + await db.insert(DB.verificationJobsTable).values({ + id: crypto.randomUUID(), + chainId: validChainId, + contractAddress: Hex.toBytes(validAddress), + verificationEndpoint: '/v2/verify', + }) + + const response = await requestFromWorker( + `/v2/verify/${validChainId}/${validAddress}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(validBody), + }, + ) + + expect(response.status).toBe(429) + const body = z.parse( + z.object({ customCode: z.string() }), + await response.json(), + ) + expect(body.customCode).toBe('duplicate_verification_request') + }) + + it('expires a stale pending job and replaces it with a new verification request', async () => { + const db = drizzle(env.CONTRACTS_DB) + const staleJobId = crypto.randomUUID() + await db.insert(DB.verificationJobsTable).values({ + id: staleJobId, + chainId: validChainId, + contractAddress: Hex.toBytes(validAddress), + verificationEndpoint: '/v2/verify', + startedAt: new Date(Date.now() - 20 * 60 * 1000).toISOString(), + }) + + const response = await requestFromWorker( + `/v2/verify/${validChainId}/${validAddress}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(validBody), + }, + ) + + expect(response.status).toBe(202) + const body = z.parse( + z.object({ verificationId: z.uuidv4() }), + await response.json(), + ) + expect(body.verificationId).not.toBe(staleJobId) + + const [staleJob] = await db + .select({ + completedAt: DB.verificationJobsTable.completedAt, + errorCode: DB.verificationJobsTable.errorCode, + }) + .from(DB.verificationJobsTable) + .where(eq(DB.verificationJobsTable.id, staleJobId)) + .limit(1) + + expect(staleJob?.completedAt).not.toBeNull() + expect(staleJob?.errorCode).toBe('timeout') + }) + + it('returns 500 and removes the verification_jobs row when DO enqueue throws', async () => { + const mockEnv = { + ...env, + VERIFICATION_JOB_RUNNER: { + idFromName: (_name: string) => ({ name: _name }), + get: (_id: unknown) => ({ + enqueue: async () => { + throw new Error('Simulated DO enqueue failure') + }, + }), + }, + } as unknown as typeof env + + const response = await app.request( + `/v2/verify/${counterFixture.chainId}/${counterFixture.address}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + stdJsonInput: counterFixture.stdJsonInput, + compilerVersion: counterFixture.compilerVersion, + contractIdentifier: counterFixture.contractIdentifier, + }), + }, + mockEnv, + ) + + expect(response.status).toBe(500) + const body = z.parse( + z.object({ customCode: z.string() }), + await response.json(), + ) + expect(body.customCode).toBe('internal_error') + + const db = drizzle(env.CONTRACTS_DB) + const jobs = await db.select().from(DB.verificationJobsTable) + expect(jobs).toHaveLength(0) + }) + + it('accepts a lowercase non-checksummed address and returns 202', async () => { + const lowercaseAddress = '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd' + const response = await requestFromWorker( + `/v2/verify/${validChainId}/${lowercaseAddress}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(validBody), + }, + ) + + expect(response.status).toBe(202) + }) + + it('returns 400 for a numerically valid but unsupported chain ID', async () => { + const response = await requestFromWorker(`/v2/verify/1/${validAddress}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(validBody), + }) + + expect(response.status).toBe(400) + }) +}) + +describe('POST /v2/verify/:chainId/:address – Vyper payload', () => { + const validChainId = chainIds[0] + if (!validChainId) { + throw new Error('expected at least one configured chain ID') + } + + const vyperAddress = '0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' + const vyperBody = { + stdJsonInput: vyperFixture.stdJsonInput, + compilerVersion: vyperFixture.compilerVersion, + contractIdentifier: vyperFixture.contractIdentifier, + } + + it('returns 202 and inserts a job row for a valid Vyper standard-json request', async () => { + const response = await requestFromWorker( + `/v2/verify/${validChainId}/${vyperAddress}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(vyperBody), + }, + ) + + expect(response.status).toBe(202) + const body = z.parse( + z.object({ verificationId: z.uuidv4() }), + await response.json(), + ) + expect(body.verificationId).toBeTruthy() + + const db = drizzle(env.CONTRACTS_DB) + const [job] = await db + .select() + .from(DB.verificationJobsTable) + .where(eq(DB.verificationJobsTable.id, body.verificationId)) + .limit(1) + + expect(job).toBeDefined() + if (!job) throw new Error('expected verification job row') + expect(job.chainId).toBe(validChainId) + expect(new Uint8Array(job.contractAddress as ArrayBuffer)).toEqual( + Hex.toBytes(vyperAddress), + ) + expect(job.verificationEndpoint).toBe('/v2/verify') + expect(job.startedAt).not.toBeNull() + expect(job.completedAt).toBeNull() + }) + + it('returns 400 for Vyper payload with invalid contractIdentifier (no colon)', async () => { + const response = await requestFromWorker( + `/v2/verify/${validChainId}/${vyperAddress}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + ...vyperBody, + contractIdentifier: 'vyper_contract_no_colon', + }), + }, + ) + + expect(response.status).toBe(400) + const body = z.parse( + z.object({ customCode: z.string() }), + await response.json(), + ) + expect(body.customCode).toBe('invalid_contract_identifier') + }) }) describe('POST /metadata/:chainId/:address', () => { it('returns 501 not implemented', async () => { - const response = await app.request( + const response = await requestFromWorker( '/v2/verify/metadata/1/0x1234567890123456789012345678901234567890', { method: 'POST' }, - env, ) expect(response.status).toBe(501) @@ -108,10 +503,9 @@ describe('POST /metadata/:chainId/:address', () => { describe('POST /similarity/:chainId/:address', () => { it('returns 501 not implemented', async () => { - const response = await app.request( + const response = await requestFromWorker( '/v2/verify/similarity/1/0x1234567890123456789012345678901234567890', { method: 'POST' }, - env, ) expect(response.status).toBe(501) diff --git a/apps/contract-verification/test/setup.ts b/apps/contract-verification/test/setup.ts index c95b22f5f..8a77291bc 100644 --- a/apps/contract-verification/test/setup.ts +++ b/apps/contract-verification/test/setup.ts @@ -1,5 +1,5 @@ import { applyD1Migrations } from 'cloudflare:test' -import { env } from 'cloudflare:workers' +import { env } from 'cloudflare:test' import { drizzle } from 'drizzle-orm/d1' import { beforeEach } from 'vitest' @@ -25,7 +25,5 @@ beforeEach(async () => { await applyD1Migrations(env.CONTRACTS_DB, env.TEST_MIGRATIONS) const db = drizzle(env.CONTRACTS_DB) - for (const table of tables) { - await db.delete(table) - } + for (const table of tables) await db.delete(table) }) diff --git a/apps/contract-verification/test/unit/bytecode-matching.test.ts b/apps/contract-verification/test/unit/bytecode-matching.test.ts index 47f236bd9..3bed6de79 100644 --- a/apps/contract-verification/test/unit/bytecode-matching.test.ts +++ b/apps/contract-verification/test/unit/bytecode-matching.test.ts @@ -14,10 +14,9 @@ import { extractConstructorArgumentsTransformation, extractAuxdataTransformation, matchBytecode, - type LinkReferences, - type ImmutableReferences, type CborAuxdataPositions, } from '#lib/bytecode-matching.ts' +import type { ImmutableReferences, LinkReferences } from '#schema.ts' function keccakString(str: string): `0x${string}` { return Hash.keccak256(Hex.fromString(str)) @@ -43,6 +42,15 @@ describe('getVyperAuxdataStyle', () => { expect(getVyperAuxdataStyle('1.0.0')).toBe(AuxdataStyle.VYPER) }) + it('handles prefixed versions and build metadata', () => { + expect(getVyperAuxdataStyle('v0.3.4+commit.abc123')).toBe( + AuxdataStyle.VYPER_LT_0_3_5, + ) + expect(getVyperAuxdataStyle('vyper-0.3.9')).toBe( + AuxdataStyle.VYPER_LT_0_3_10, + ) + }) + it('returns VYPER for invalid versions', () => { expect(getVyperAuxdataStyle('invalid')).toBe(AuxdataStyle.VYPER) expect(getVyperAuxdataStyle('')).toBe(AuxdataStyle.VYPER) diff --git a/apps/contract-verification/test/unit/job-runner-do.test.ts b/apps/contract-verification/test/unit/job-runner-do.test.ts new file mode 100644 index 000000000..2cae01cee --- /dev/null +++ b/apps/contract-verification/test/unit/job-runner-do.test.ts @@ -0,0 +1,235 @@ +import { eq } from 'drizzle-orm' +import { drizzle } from 'drizzle-orm/d1' +import { env } from 'cloudflare:test' +import { runDurableObjectAlarm, runInDurableObject } from 'cloudflare:test' +import { Hex } from 'ox' +import { describe, expect, it } from 'vitest' + +import * as DB from '#database/schema.ts' +import type { VerificationJobRunner } from '#job-runner.ts' +import type { VerificationJob } from '#schema.ts' +import { counterFixture } from '../fixtures/counter.fixture.ts' + +function makeJob(jobId: string): VerificationJob { + return { + jobId, + chainId: counterFixture.chainId, + address: counterFixture.address, + stdJsonInput: counterFixture.stdJsonInput, + compilerVersion: counterFixture.compilerVersion, + contractIdentifier: counterFixture.contractIdentifier, + } +} + +async function insertJobRow(jobId: string) { + const db = drizzle(env.CONTRACTS_DB) + await db.insert(DB.verificationJobsTable).values({ + id: jobId, + chainId: counterFixture.chainId, + contractAddress: Hex.toBytes(counterFixture.address), + verificationEndpoint: '/v2/verify', + }) +} + +async function getJobRow(jobId: string) { + const db = drizzle(env.CONTRACTS_DB) + const rows = await db + .select() + .from(DB.verificationJobsTable) + .where(eq(DB.verificationJobsTable.id, jobId)) + .limit(1) + return rows.at(0) ?? null +} + +function getStub(name: string) { + const id = env.VERIFICATION_JOB_RUNNER.idFromName(name) + return env.VERIFICATION_JOB_RUNNER.get( + id, + ) as DurableObjectStub +} + +describe('VerificationJobRunner Durable Object', () => { + describe('alarm – missing job', () => { + it('returns early without error when storage has no job', async () => { + const stub = getStub('missing-job-test') + + await runInDurableObject(stub, async (_instance, state) => { + await state.storage.setAlarm(Date.now() + 60_000) + }) + + const ran = await runDurableObjectAlarm(stub) + expect(ran).toBe(true) + + const hasJob = await runInDurableObject( + stub, + async (_instance, state) => await state.storage.get('job'), + ) + expect(hasJob).toBeUndefined() + }) + }) + + describe('enqueue + alarm – success path', () => { + it('stores the job and schedules an alarm via enqueue', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const stub = getStub(jobId) + const job = makeJob(jobId) + + await runInDurableObject(stub, async (_instance, state) => { + await state.storage.put('job', job) + await state.storage.setAlarm(Date.now() + 60_000) + }) + + const storedJob = await runInDurableObject( + stub, + async (_instance, state) => + await state.storage.get('job'), + ) + expect(storedJob).toBeDefined() + expect(storedJob?.jobId).toBe(jobId) + + const ran = await runDurableObjectAlarm(stub) + expect(ran).toBe(true) + + const jobAfterAlarm = await runInDurableObject( + stub, + async (_instance, state) => await state.storage.get('job'), + ) + expect(jobAfterAlarm).toBeUndefined() + + const row = await getJobRow(jobId) + expect(row).not.toBeNull() + expect(row?.completedAt).not.toBeNull() + }) + + it('enqueue() stores job and sets alarm; manually firing alarm completes the job', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const stub = getStub(jobId) + await stub.enqueue(makeJob(jobId)) + + const jobInStorage = await runInDurableObject( + stub, + async (_instance, state) => await state.storage.get('job'), + ) + expect(jobInStorage).toBeDefined() + + await runInDurableObject(stub, async (instance) => { + await instance.alarm() + }) + + const jobAfter = await runInDurableObject( + stub, + async (_instance, state) => await state.storage.get('job'), + ) + expect(jobAfter).toBeUndefined() + + const row = await getJobRow(jobId) + expect(row).not.toBeNull() + expect(row?.completedAt).not.toBeNull() + }) + }) + + describe('alarm – failure with cleanup', () => { + it('deletes the job from DO storage even when runVerificationJob encounters an error', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const stub = getStub(jobId) + const job = makeJob(jobId) + + await runInDurableObject(stub, async (_instance, state) => { + await state.storage.put('job', job) + await state.storage.setAlarm(Date.now() + 60_000) + }) + + const before = await runInDurableObject( + stub, + async (_instance, state) => await state.storage.get('job'), + ) + expect(before).toBeDefined() + + const ran = await runDurableObjectAlarm(stub) + expect(ran).toBe(true) + + const after = await runInDurableObject( + stub, + async (_instance, state) => await state.storage.get('job'), + ) + expect(after).toBeUndefined() + }) + + it('records an error in the DB row when the job has an invalid chainId', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const stub = getStub(jobId) + await runInDurableObject(stub, async (_instance, state) => { + await state.storage.put('job', { + ...makeJob(jobId), + chainId: 999_999, + }) + await state.storage.setAlarm(Date.now() + 60_000) + }) + + await runDurableObjectAlarm(stub) + + const row = await getJobRow(jobId) + expect(row).not.toBeNull() + expect(row?.completedAt).not.toBeNull() + expect(row?.errorCode).toBe('internal_error') + }) + + it('handles legacy stored job payloads written before the schema flattening change', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const stub = getStub(jobId) + await runInDurableObject(stub, async (_instance, state) => { + const job = makeJob(jobId) + await state.storage.put('job', { + jobId: job.jobId, + chainId: job.chainId, + address: job.address, + body: { + stdJsonInput: job.stdJsonInput, + compilerVersion: job.compilerVersion, + contractIdentifier: job.contractIdentifier, + creationTransactionHash: job.creationTransactionHash, + }, + }) + await state.storage.setAlarm(Date.now() + 60_000) + }) + + await runDurableObjectAlarm(stub) + + const row = await getJobRow(jobId) + expect(row).not.toBeNull() + expect(row?.completedAt).not.toBeNull() + }) + + it('does not leave a dangling alarm after processing', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const stub = getStub(jobId) + await runInDurableObject(stub, async (_instance, state) => { + await state.storage.put('job', makeJob(jobId)) + await state.storage.setAlarm(Date.now() + 60_000) + }) + + await runDurableObjectAlarm(stub) + + const alarmAfter = await runInDurableObject( + stub, + async (_instance, state) => await state.storage.getAlarm(), + ) + expect(alarmAfter).toBeNull() + + const ranAgain = await runDurableObjectAlarm(stub) + expect(ranAgain).toBe(false) + }) + }) +}) diff --git a/apps/contract-verification/test/unit/job-runner-failures.test.ts b/apps/contract-verification/test/unit/job-runner-failures.test.ts new file mode 100644 index 000000000..90c3b73ec --- /dev/null +++ b/apps/contract-verification/test/unit/job-runner-failures.test.ts @@ -0,0 +1,290 @@ +import { eq } from 'drizzle-orm' +import { drizzle } from 'drizzle-orm/d1' +import { SELF, env } from 'cloudflare:test' +import { describe, expect, it } from 'vitest' + +import * as DB from '#database/schema.ts' +import { runVerificationJob } from '#route.verify.ts' +import { counterFixture } from '../fixtures/counter.fixture.ts' + +const verifyRequestData = { + stdJsonInput: counterFixture.stdJsonInput, + compilerVersion: counterFixture.compilerVersion, + contractIdentifier: counterFixture.contractIdentifier, +} as const + +async function createJob(): Promise { + const res = await SELF.fetch( + `https://test.local/v2/verify/${counterFixture.chainId}/${counterFixture.address}`, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(verifyRequestData), + }, + ) + expect(res.status).toBe(202) + const body = (await res.json()) as { verificationId: string } + return body.verificationId +} + +async function getJob(jobId: string) { + const db = drizzle(env.CONTRACTS_DB) + const rows = await db + .select() + .from(DB.verificationJobsTable) + .where(eq(DB.verificationJobsTable.id, jobId)) + expect(rows).toHaveLength(1) + const job = rows[0] + if (!job) throw new Error('expected verification job row') + return job +} + +const validClientDeps = { + createPublicClient: () => ({ + getCode: async () => counterFixture.onchainRuntimeBytecode, + }), +} as const + +describe('runVerificationJob failure branches', () => { + it('records internal_error for unsupported chain id', async () => { + const jobId = await createJob() + + await runVerificationJob(env, { + jobId, + chainId: 999_999 as never, + address: counterFixture.address, + stdJsonInput: counterFixture.stdJsonInput, + compilerVersion: counterFixture.compilerVersion, + contractIdentifier: counterFixture.contractIdentifier, + }) + + const job = await getJob(jobId) + expect(job.completedAt).not.toBeNull() + expect(job.errorCode).toBe('internal_error') + const data = JSON.parse(job.errorData ?? '{}') as { message?: string } + expect(data.message).toMatch(/not supported/i) + }) + + it('records compilation_failed when container returns non-200', async () => { + const jobId = await createJob() + + await runVerificationJob( + env, + { + jobId, + ...counterFixture, + }, + { + ...validClientDeps, + getContainer: () => ({ + fetch: async () => + new Response('solc exited with code 1', { status: 500 }), + }), + }, + ) + + const job = await getJob(jobId) + expect(job.completedAt).not.toBeNull() + expect(job.errorCode).toBe('compilation_failed') + const data = JSON.parse(job.errorData ?? '{}') as { message?: string } + expect(data.message).toContain('solc exited with code 1') + }) + + it('records contract_not_found_in_output when target contract is absent', async () => { + const jobId = await createJob() + + const modifiedOutput = structuredClone(counterFixture.solcOutput) as { + contracts: Record> + } + modifiedOutput.contracts = { + 'Counter.sol': { + OtherContract: + counterFixture.solcOutput.contracts['Counter.sol'].Counter, + }, + } + + await runVerificationJob( + env, + { + jobId, + ...counterFixture, + }, + { + ...validClientDeps, + getContainer: () => ({ + fetch: async () => Response.json(modifiedOutput, { status: 200 }), + }), + }, + ) + + const job = await getJob(jobId) + expect(job.completedAt).not.toBeNull() + expect(job.errorCode).toBe('contract_not_found_in_output') + const data = JSON.parse(job.errorData ?? '{}') as { message?: string } + expect(data.message).toContain('Counter') + }) + + it('records no_match when compiled bytecode differs from on-chain', async () => { + const jobId = await createJob() + + await runVerificationJob( + env, + { + jobId, + ...counterFixture, + }, + { + createPublicClient: () => ({ + getCode: async () => + '0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef' as `0x${string}`, + }), + getContainer: () => ({ + fetch: async () => + Response.json(counterFixture.solcOutput, { status: 200 }), + }), + }, + ) + + const job = await getJob(jobId) + expect(job.completedAt).not.toBeNull() + expect(job.errorCode).toBe('no_match') + const data = JSON.parse(job.errorData ?? '{}') as { message?: string } + expect(data.message).toMatch(/bytecode|match/i) + }) + + it('persists internal_error with errorId when an unexpected error is thrown', async () => { + const jobId = await createJob() + + await runVerificationJob( + env, + { + jobId, + ...counterFixture, + }, + { + createPublicClient: () => ({ + getCode: async () => { + throw new Error('RPC node unreachable') + }, + }), + }, + ) + + const job = await getJob(jobId) + expect(job.completedAt).not.toBeNull() + expect(job.errorCode).toBe('internal_error') + expect(job.errorId).toBeTruthy() + const data = JSON.parse(job.errorData ?? '{}') as { message?: string } + expect(data.message).toContain('RPC node unreachable') + expect(job.compilationTime).toBeTypeOf('number') + }) + + it('records contract_not_found_in_output for empty compile output', async () => { + const jobId = await createJob() + + await runVerificationJob( + env, + { + jobId, + ...counterFixture, + }, + { + ...validClientDeps, + getContainer: () => ({ + fetch: async () => Response.json({}, { status: 200 }), + }), + }, + ) + + const job = await getJob(jobId) + expect(job.completedAt).not.toBeNull() + expect(job.errorCode).toBe('contract_not_found_in_output') + }) + + it('records internal_error when compile output is not valid JSON', async () => { + const jobId = await createJob() + + await runVerificationJob( + env, + { + jobId, + ...counterFixture, + }, + { + ...validClientDeps, + getContainer: () => ({ + fetch: async () => + new Response('not json at all', { + status: 200, + headers: { 'Content-Type': 'application/json' }, + }), + }), + }, + ) + + const job = await getJob(jobId) + expect(job.completedAt).not.toBeNull() + expect(job.errorCode).toBe('internal_error') + }) + + it('records container_error when container fetch throws', async () => { + const jobId = await createJob() + + await runVerificationJob( + env, + { + jobId, + ...counterFixture, + }, + { + ...validClientDeps, + getContainer: () => ({ + fetch: async () => { + throw new Error('connection refused') + }, + }), + }, + ) + + const job = await getJob(jobId) + expect(job.completedAt).not.toBeNull() + expect(job.errorCode).toBe('container_error') + const data = JSON.parse(job.errorData ?? '{}') as { message?: string } + expect(data.message).toContain('connection refused') + }) + + it('records compilation_error when solc output contains severity=error entries', async () => { + const jobId = await createJob() + + const errorOutput = { + contracts: {}, + errors: [ + { + severity: 'error', + message: 'ParserError: Expected pragma', + formattedMessage: 'ParserError: Expected pragma, got EOF', + }, + ], + } + + await runVerificationJob( + env, + { + jobId, + ...counterFixture, + }, + { + ...validClientDeps, + getContainer: () => ({ + fetch: async () => Response.json(errorOutput, { status: 200 }), + }), + }, + ) + + const job = await getJob(jobId) + expect(job.completedAt).not.toBeNull() + expect(job.errorCode).toBe('compilation_error') + const data = JSON.parse(job.errorData ?? '{}') as { message?: string } + expect(data.message).toContain('Expected pragma') + }) +}) diff --git a/apps/contract-verification/test/unit/run-verification-job-persistence.test.ts b/apps/contract-verification/test/unit/run-verification-job-persistence.test.ts new file mode 100644 index 000000000..2c0316e8d --- /dev/null +++ b/apps/contract-verification/test/unit/run-verification-job-persistence.test.ts @@ -0,0 +1,510 @@ +import { eq } from 'drizzle-orm' +import { drizzle } from 'drizzle-orm/d1' +import { env } from 'cloudflare:test' +import { Hex } from 'ox' +import { describe, expect, it } from 'vitest' + +import * as DB from '#database/schema.ts' +import { runVerificationJob } from '#route.verify.ts' +import { counterFixture, counterSource } from '../fixtures/counter.fixture.ts' + +const JOB_DEFAULTS = { + chainId: counterFixture.chainId, + address: counterFixture.address, + stdJsonInput: counterFixture.stdJsonInput, + compilerVersion: counterFixture.compilerVersion, + contractIdentifier: counterFixture.contractIdentifier, +} as const + +async function insertJobRow(jobId: string) { + const db = drizzle(env.CONTRACTS_DB) + await db.insert(DB.verificationJobsTable).values({ + id: jobId, + chainId: JOB_DEFAULTS.chainId, + contractAddress: Hex.toBytes(JOB_DEFAULTS.address), + verificationEndpoint: '/v2/verify', + }) +} + +async function getJobRow(jobId: string) { + const db = drizzle(env.CONTRACTS_DB) + const rows = await db + .select() + .from(DB.verificationJobsTable) + .where(eq(DB.verificationJobsTable.id, jobId)) + .limit(1) + return rows.at(0) ?? null +} + +function makeContainerStub(solcOutput?: unknown) { + return { + getContainer: () => ({ + fetch: async (request: Request) => { + const url = new URL(request.url) + if (request.method === 'POST' && url.pathname === '/compile') { + return Response.json(solcOutput ?? counterFixture.solcOutput, { + status: 200, + }) + } + throw new Error( + `Unexpected container request: ${request.method} ${url.pathname}`, + ) + }, + }), + } +} + +function makeClientStub() { + return { + createPublicClient: () => ({ + getCode: async () => counterFixture.onchainRuntimeBytecode, + }), + } +} + +// --------------------------------------------------------------------------- +// 1. Suffix-path contract lookup fallback +// --------------------------------------------------------------------------- +describe('runVerificationJob – suffix-path contract lookup fallback', () => { + it('finds the contract when the compiler output uses a prefixed path', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + // Build a solcOutput where the contract is stored under a prefixed path + // e.g. "project/Counter.sol" instead of "Counter.sol" + const originalEntry = + counterFixture.solcOutput.contracts['Counter.sol']?.Counter + if (!originalEntry) throw new Error('fixture contract entry missing') + + const prefixedOutput = { + ...counterFixture.solcOutput, + contracts: { + // Remove the original key; use a prefixed version only + 'project/Counter.sol': { + Counter: originalEntry, + }, + }, + } + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + ...makeClientStub(), + ...makeContainerStub(prefixedOutput), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBeNull() + expect(job?.verifiedContractId).not.toBeNull() + }) + + it('finds the contract when the output path ends with /contractPath', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const originalEntry = + counterFixture.solcOutput.contracts['Counter.sol']?.Counter + if (!originalEntry) throw new Error('fixture contract entry missing') + + const prefixedOutput = { + ...counterFixture.solcOutput, + contracts: { + '/home/user/repo/src/Counter.sol': { + Counter: originalEntry, + }, + }, + } + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + ...makeClientStub(), + ...makeContainerStub(prefixedOutput), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBeNull() + expect(job?.verifiedContractId).not.toBeNull() + }) + + it('returns contract_not_found_in_output when no suffix match exists', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const originalEntry = + counterFixture.solcOutput.contracts['Counter.sol']?.Counter + if (!originalEntry) throw new Error('fixture contract entry missing') + + const unrelatedOutput = { + ...counterFixture.solcOutput, + contracts: { + 'OtherFile.sol': { + Counter: originalEntry, + }, + }, + } + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + ...makeClientStub(), + ...makeContainerStub(unrelatedOutput), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBe('contract_not_found_in_output') + }) +}) + +// --------------------------------------------------------------------------- +// 2. Signature persistence for mixed ABI items (function/event/error) +// --------------------------------------------------------------------------- +describe('runVerificationJob – compiled_contracts_signatures persistence', () => { + it('persists function, event, and error signatures from ABI', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + // Extend the Counter ABI with a custom error + const originalEntry = + counterFixture.solcOutput.contracts['Counter.sol']?.Counter + if (!originalEntry) throw new Error('fixture contract entry missing') + + const extendedAbi = [ + ...originalEntry.abi, + { + inputs: [ + { internalType: 'uint256', name: 'requested', type: 'uint256' }, + { internalType: 'uint256', name: 'available', type: 'uint256' }, + ], + name: 'InsufficientBalance', + type: 'error', + }, + ] + + const solcOutputWithError = { + ...counterFixture.solcOutput, + contracts: { + 'Counter.sol': { + Counter: { + ...originalEntry, + abi: extendedAbi, + }, + }, + }, + } + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + ...makeClientStub(), + ...makeContainerStub(solcOutputWithError), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBeNull() + + const db = drizzle(env.CONTRACTS_DB) + + // Query all signatures linked to this compilation + const sigLinks = await db + .select({ + signature: DB.signaturesTable.signature, + signatureType: DB.compiledContractsSignaturesTable.signatureType, + }) + .from(DB.compiledContractsSignaturesTable) + .innerJoin( + DB.signaturesTable, + eq( + DB.compiledContractsSignaturesTable.signatureHash32, + DB.signaturesTable.signatureHash32, + ), + ) + + const byType = (t: string) => sigLinks.filter((s) => s.signatureType === t) + + const functions = byType('function') + const events = byType('event') + const errors = byType('error') + + // The Counter ABI has these named items: + // functions: count(), decrement(), increment(), owner(), setCount(uint256) + // events: CountChanged(uint256) + // errors: InsufficientBalance(uint256,uint256) (added above) + expect(functions.map((f) => f.signature).sort()).toEqual([ + 'count()', + 'decrement()', + 'increment()', + 'owner()', + 'setCount(uint256)', + ]) + + expect(events.map((e) => e.signature)).toEqual(['CountChanged(uint256)']) + + expect(errors.map((e) => e.signature)).toEqual([ + 'InsufficientBalance(uint256,uint256)', + ]) + }) +}) + +// --------------------------------------------------------------------------- +// 3. Source path normalization persistence for odd input paths +// --------------------------------------------------------------------------- +describe('runVerificationJob – source path normalization persistence', () => { + it('normalizes absolute source paths before persisting to compiled_contracts_sources', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + // Use an absolute path as the source key + const oddSourcePath = '/home/user/project/src/Counter.sol' + const stdJsonInputWithAbsPath = { + ...counterFixture.stdJsonInput, + sources: { + [oddSourcePath]: { content: counterSource }, + }, + } + + // The compiler output still references "Counter.sol" in its contracts map + await runVerificationJob( + env, + { + ...JOB_DEFAULTS, + jobId, + stdJsonInput: stdJsonInputWithAbsPath, + }, + { + ...makeClientStub(), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBeNull() + + const db = drizzle(env.CONTRACTS_DB) + const sources = await db + .select({ path: DB.compiledContractsSourcesTable.path }) + .from(DB.compiledContractsSourcesTable) + + expect(sources).toHaveLength(1) + // normalizeSourcePath strips the prefix up to /src/ yielding "src/Counter.sol" + expect(sources[0]?.path).toBe('src/Counter.sol') + }) + + it('preserves relative paths that are already clean', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + ...makeClientStub(), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.errorCode).toBeNull() + + const db = drizzle(env.CONTRACTS_DB) + const sources = await db + .select({ path: DB.compiledContractsSourcesTable.path }) + .from(DB.compiledContractsSourcesTable) + + expect(sources).toHaveLength(1) + // "Counter.sol" is already relative, should pass through unchanged + expect(sources[0]?.path).toBe('Counter.sol') + }) + + it('normalizes /contracts/ prefix in source paths', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const oddSourcePath = '/opt/build/contracts/Counter.sol' + const stdJsonInputWithAbsPath = { + ...counterFixture.stdJsonInput, + sources: { + [oddSourcePath]: { content: counterSource }, + }, + } + + await runVerificationJob( + env, + { + ...JOB_DEFAULTS, + jobId, + stdJsonInput: stdJsonInputWithAbsPath, + }, + { + ...makeClientStub(), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.errorCode).toBeNull() + + const db = drizzle(env.CONTRACTS_DB) + const sources = await db + .select({ path: DB.compiledContractsSourcesTable.path }) + .from(DB.compiledContractsSourcesTable) + + expect(sources).toHaveLength(1) + expect(sources[0]?.path).toBe('contracts/Counter.sol') + }) + + it('falls back to filename for unrecognized absolute paths', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const oddSourcePath = '/weird/deep/nested/Counter.sol' + const stdJsonInputWithAbsPath = { + ...counterFixture.stdJsonInput, + sources: { + [oddSourcePath]: { content: counterSource }, + }, + } + + await runVerificationJob( + env, + { + ...JOB_DEFAULTS, + jobId, + stdJsonInput: stdJsonInputWithAbsPath, + }, + { + ...makeClientStub(), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.errorCode).toBeNull() + + const db = drizzle(env.CONTRACTS_DB) + const sources = await db + .select({ path: DB.compiledContractsSourcesTable.path }) + .from(DB.compiledContractsSourcesTable) + + expect(sources).toHaveLength(1) + // No recognized pattern → falls back to filename + expect(sources[0]?.path).toBe('Counter.sol') + }) +}) + +// --------------------------------------------------------------------------- +// 4. ABI with unsupported / malformed items +// --------------------------------------------------------------------------- +describe('runVerificationJob – ABI with unsupported/malformed items', () => { + it('skips constructor, receive, and fallback ABI items without failing', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const originalEntry = + counterFixture.solcOutput.contracts['Counter.sol']?.Counter + if (!originalEntry) throw new Error('fixture contract entry missing') + + // Build an ABI that includes unsupported types alongside real ones + const mixedAbi = [ + // constructor – no name, no signature + { + inputs: [ + { internalType: 'uint256', name: 'initialCount', type: 'uint256' }, + ], + stateMutability: 'nonpayable', + type: 'constructor', + }, + // receive – no name, no signature + { stateMutability: 'payable', type: 'receive' }, + // fallback – no name, no signature + { stateMutability: 'nonpayable', type: 'fallback' }, + // a valid function + { + inputs: [], + name: 'count', + outputs: [{ internalType: 'uint256', name: '', type: 'uint256' }], + stateMutability: 'view', + type: 'function', + }, + // a valid event + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: 'uint256', + name: 'newCount', + type: 'uint256', + }, + ], + name: 'CountChanged', + type: 'event', + }, + // an item with an unknown type (future-proofing) + { + inputs: [], + name: 'SomeFutureThing', + type: 'someFutureType', + }, + ] + + const solcOutputMixed = { + ...counterFixture.solcOutput, + contracts: { + 'Counter.sol': { + Counter: { + ...originalEntry, + abi: mixedAbi, + }, + }, + }, + } + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + ...makeClientStub(), + ...makeContainerStub(solcOutputMixed), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBeNull() + expect(job?.verifiedContractId).not.toBeNull() + + // Only the function and event should have signatures persisted + const db = drizzle(env.CONTRACTS_DB) + const sigLinks = await db + .select({ + signature: DB.signaturesTable.signature, + signatureType: DB.compiledContractsSignaturesTable.signatureType, + }) + .from(DB.compiledContractsSignaturesTable) + .innerJoin( + DB.signaturesTable, + eq( + DB.compiledContractsSignaturesTable.signatureHash32, + DB.signaturesTable.signatureHash32, + ), + ) + + expect(sigLinks).toHaveLength(2) + const signatures = sigLinks.map((s) => s.signature).sort() + expect(signatures).toEqual(['CountChanged(uint256)', 'count()']) + }) +}) diff --git a/apps/contract-verification/test/unit/run-verification-job-vyper.test.ts b/apps/contract-verification/test/unit/run-verification-job-vyper.test.ts new file mode 100644 index 000000000..b56a1c677 --- /dev/null +++ b/apps/contract-verification/test/unit/run-verification-job-vyper.test.ts @@ -0,0 +1,247 @@ +import { eq } from 'drizzle-orm' +import { drizzle } from 'drizzle-orm/d1' +import { env } from 'cloudflare:test' +import { Hex } from 'ox' +import { describe, expect, it } from 'vitest' + +import * as DB from '#database/schema.ts' +import { runVerificationJob } from '#route.verify.ts' +import { vyperFixture } from '../fixtures/vyper.fixture.ts' + +const JOB_DEFAULTS = { + chainId: vyperFixture.chainId, + address: vyperFixture.address, + stdJsonInput: vyperFixture.stdJsonInput, + compilerVersion: vyperFixture.compilerVersion, + contractIdentifier: vyperFixture.contractIdentifier, +} as const + +async function insertJobRow(jobId: string) { + const db = drizzle(env.CONTRACTS_DB) + await db.insert(DB.verificationJobsTable).values({ + id: jobId, + chainId: JOB_DEFAULTS.chainId, + contractAddress: Hex.toBytes(JOB_DEFAULTS.address), + verificationEndpoint: '/v2/verify', + }) +} + +async function getJobRow(jobId: string) { + const db = drizzle(env.CONTRACTS_DB) + const rows = await db + .select() + .from(DB.verificationJobsTable) + .where(eq(DB.verificationJobsTable.id, jobId)) + .limit(1) + return rows.at(0) ?? null +} + +/** + * Container stub that responds to /compile/vyper (the Vyper compile endpoint). + */ +function makeVyperContainerStub( + overrideOutput?: unknown, + overrideStatus?: number, +) { + return { + getContainer: () => ({ + fetch: async (request: Request) => { + const url = new URL(request.url) + if (request.method === 'POST' && url.pathname === '/compile/vyper') { + return Response.json( + overrideOutput ?? vyperFixture.vyperCompileOutput, + { status: overrideStatus ?? 200 }, + ) + } + throw new Error( + `Unexpected container request: ${request.method} ${url.pathname}`, + ) + }, + }), + } +} + +function makeClientStub( + onchainBytecode: `0x${string}` = vyperFixture.onchainRuntimeBytecode, +) { + return { + createPublicClient: () => ({ + getCode: async () => onchainBytecode, + }), + } +} + +// ────────────────────────────────────────────────────────────────────── +// Happy path +// ────────────────────────────────────────────────────────────────────── + +describe('runVerificationJob – Vyper happy path', () => { + it('successfully verifies a Vyper contract via /compile/vyper', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { ...makeClientStub(), ...makeVyperContainerStub() }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBeNull() + expect(job?.verifiedContractId).not.toBeNull() + + // Verify the compiled_contracts row uses 'vyper' compiler + const db = drizzle(env.CONTRACTS_DB) + const compilations = await db + .select() + .from(DB.compiledContractsTable) + .limit(1) + expect(compilations).toHaveLength(1) + const comp = compilations[0] + if (!comp) throw new Error('expected compiled_contracts row') + expect(comp.compiler).toBe('vyper') + expect(comp.language).toBe('Vyper') + expect(comp.name).toBe('vyper_contract') + expect(comp.fullyQualifiedName).toBe(vyperFixture.contractIdentifier) + + // Verify sources were persisted + const sources = await db + .select() + .from(DB.compiledContractsSourcesTable) + .limit(10) + expect(sources.length).toBeGreaterThanOrEqual(1) + }) + + it('persists Vyper ABI signatures correctly', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { ...makeClientStub(), ...makeVyperContainerStub() }, + ) + + const db = drizzle(env.CONTRACTS_DB) + const sigLinks = await db + .select() + .from(DB.compiledContractsSignaturesTable) + .limit(50) + + // The fixture ABI has: owner, value, set_value, get_value (functions) + ValueChanged (event) + // constructor is excluded from signatures + const functionSigs = sigLinks.filter((s) => s.signatureType === 'function') + const eventSigs = sigLinks.filter((s) => s.signatureType === 'event') + + expect(functionSigs.length).toBe(4) // owner, value, set_value, get_value + expect(eventSigs.length).toBe(1) // ValueChanged + }) +}) + +// ────────────────────────────────────────────────────────────────────── +// Failure paths +// ────────────────────────────────────────────────────────────────────── + +describe('runVerificationJob – Vyper compilation failure', () => { + it('records compilation_failed when /compile/vyper returns non-200', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + ...makeClientStub(), + ...makeVyperContainerStub({ error: 'Vyper compiler not found' }, 500), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBe('compilation_failed') + }) + + it('records compilation_error when Vyper output contains errors', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const outputWithErrors = { + ...vyperFixture.vyperCompileOutput, + errors: [ + { + severity: 'error', + message: 'SyntaxError: invalid syntax', + formattedMessage: 'SyntaxError: invalid syntax at line 5', + }, + ], + } + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { ...makeClientStub(), ...makeVyperContainerStub(outputWithErrors) }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBe('compilation_error') + const errorData = JSON.parse(job?.errorData ?? '{}') as { + message?: string + } + expect(errorData.message).toContain('SyntaxError') + }) +}) + +describe('runVerificationJob – Vyper bytecode mismatch', () => { + it('records no_match when on-chain bytecode differs from compiled Vyper output', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + ...makeClientStub(vyperFixture.mismatchedOnchainBytecode), + ...makeVyperContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBe('no_match') + const errorData = JSON.parse(job?.errorData ?? '{}') as { + message?: string + } + expect(errorData.message).toBeTruthy() + }) +}) + +describe('runVerificationJob – Vyper contract not found in output', () => { + it('records contract_not_found_in_output when contract name is absent', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + // Compile output with a different contract name + const wrongNameOutput = { + contracts: { + 'vyper_contract.vy': { + wrong_name: + vyperFixture.vyperCompileOutput.contracts['vyper_contract.vy'] + ?.vyper_contract, + }, + }, + sources: vyperFixture.vyperCompileOutput.sources, + } + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { ...makeClientStub(), ...makeVyperContainerStub(wrongNameOutput) }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBe('contract_not_found_in_output') + }) +}) diff --git a/apps/contract-verification/test/unit/run-verification-job.test.ts b/apps/contract-verification/test/unit/run-verification-job.test.ts new file mode 100644 index 000000000..3390d5051 --- /dev/null +++ b/apps/contract-verification/test/unit/run-verification-job.test.ts @@ -0,0 +1,306 @@ +import { and, eq } from 'drizzle-orm' +import { drizzle } from 'drizzle-orm/d1' +import { env } from 'cloudflare:test' +import { Hex } from 'ox' +import { describe, expect, it } from 'vitest' + +import * as DB from '#database/schema.ts' +import { runVerificationJob } from '#route.verify.ts' +import { counterFixture } from '../fixtures/counter.fixture.ts' + +const JOB_DEFAULTS = { + chainId: counterFixture.chainId, + address: counterFixture.address, + stdJsonInput: counterFixture.stdJsonInput, + compilerVersion: counterFixture.compilerVersion, + contractIdentifier: counterFixture.contractIdentifier, +} as const + +async function insertJobRow(jobId: string) { + const db = drizzle(env.CONTRACTS_DB) + await db.insert(DB.verificationJobsTable).values({ + id: jobId, + chainId: JOB_DEFAULTS.chainId, + contractAddress: Hex.toBytes(JOB_DEFAULTS.address), + verificationEndpoint: '/v2/verify', + }) +} + +async function getJobRow(jobId: string) { + const db = drizzle(env.CONTRACTS_DB) + const rows = await db + .select() + .from(DB.verificationJobsTable) + .where(eq(DB.verificationJobsTable.id, jobId)) + .limit(1) + return rows.at(0) ?? null +} + +function makeContainerStub() { + return { + getContainer: () => ({ + fetch: async (request: Request) => { + const url = new URL(request.url) + if (request.method === 'POST' && url.pathname === '/compile') { + return Response.json(counterFixture.solcOutput, { status: 200 }) + } + throw new Error( + `Unexpected container request: ${request.method} ${url.pathname}`, + ) + }, + }), + } +} + +describe('runVerificationJob – creation tx metadata success', () => { + it('persists deployer/block/txIndex from a valid creation transaction receipt', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const fakeTxHash = + '0x0000000000000000000000000000000000000000000000000000000000000001' as const + const fakeDeployer = '0x00000000000000000000000000000000000000aa' as const + + await runVerificationJob( + env, + { + ...JOB_DEFAULTS, + jobId, + creationTransactionHash: fakeTxHash, + }, + { + createPublicClient: () => ({ + getCode: async () => counterFixture.onchainRuntimeBytecode, + getTransactionReceipt: async () => ({ + transactionHash: fakeTxHash, + blockNumber: 42n, + transactionIndex: 7, + from: fakeDeployer, + contractAddress: counterFixture.address, + }), + }), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBeNull() + + const db = drizzle(env.CONTRACTS_DB) + const deployments = await db + .select() + .from(DB.contractDeploymentsTable) + .where( + and( + eq(DB.contractDeploymentsTable.chainId, JOB_DEFAULTS.chainId), + eq( + DB.contractDeploymentsTable.address, + Hex.toBytes(JOB_DEFAULTS.address), + ), + ), + ) + .limit(1) + + expect(deployments).toHaveLength(1) + const dep = deployments[0] + if (!dep) throw new Error('expected deployment row') + expect(dep.blockNumber).toBe(42) + expect(dep.transactionIndex).toBe(7) + expect(dep.deployer).not.toBeNull() + expect(dep.transactionHash).not.toBeNull() + }) +}) + +describe('runVerificationJob – creation tx metadata failure', () => { + it('still verifies when getTransactionReceipt throws', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const fakeTxHash = + '0x0000000000000000000000000000000000000000000000000000000000000002' as const + + await runVerificationJob( + env, + { + ...JOB_DEFAULTS, + jobId, + creationTransactionHash: fakeTxHash, + }, + { + createPublicClient: () => ({ + getCode: async () => counterFixture.onchainRuntimeBytecode, + getTransactionReceipt: async () => { + throw new Error('RPC receipt error') + }, + }), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBeNull() + + const db = drizzle(env.CONTRACTS_DB) + const deployments = await db + .select() + .from(DB.contractDeploymentsTable) + .where( + and( + eq(DB.contractDeploymentsTable.chainId, JOB_DEFAULTS.chainId), + eq( + DB.contractDeploymentsTable.address, + Hex.toBytes(JOB_DEFAULTS.address), + ), + ), + ) + .limit(1) + + expect(deployments).toHaveLength(1) + expect(deployments[0]?.transactionHash).toBeNull() + expect(deployments[0]?.deployer).toBeNull() + }) + + it('still verifies when receipt contractAddress does not match', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + const fakeTxHash = + '0x0000000000000000000000000000000000000000000000000000000000000003' as const + + await runVerificationJob( + env, + { + ...JOB_DEFAULTS, + jobId, + creationTransactionHash: fakeTxHash, + }, + { + createPublicClient: () => ({ + getCode: async () => counterFixture.onchainRuntimeBytecode, + getTransactionReceipt: async () => ({ + transactionHash: fakeTxHash, + blockNumber: 10n, + transactionIndex: 0, + from: '0x00000000000000000000000000000000000000bb' as const, + contractAddress: + '0x0000000000000000000000000000000000099999' as const, + }), + }), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.errorCode).toBeNull() + + const db = drizzle(env.CONTRACTS_DB) + const deployments = await db + .select() + .from(DB.contractDeploymentsTable) + .where( + and( + eq(DB.contractDeploymentsTable.chainId, JOB_DEFAULTS.chainId), + eq( + DB.contractDeploymentsTable.address, + Hex.toBytes(JOB_DEFAULTS.address), + ), + ), + ) + .limit(1) + + expect(deployments).toHaveLength(1) + expect(deployments[0]?.transactionHash).toBeNull() + }) +}) + +describe('runVerificationJob – getCode returns empty', () => { + it('records contract_not_found when getCode returns undefined', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + createPublicClient: () => ({ + getCode: async () => undefined as unknown as `0x${string}`, + }), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBe('contract_not_found') + const errorData = JSON.parse(job?.errorData ?? '{}') as { message?: string } + expect(errorData.message).toContain('No bytecode found') + }) + + it('records contract_not_found when getCode returns 0x', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + createPublicClient: () => ({ + getCode: async () => '0x' as `0x${string}`, + }), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.errorCode).toBe('contract_not_found') + }) +}) + +describe('runVerificationJob – getCode throws', () => { + it('records internal_error when getCode rejects', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + createPublicClient: () => ({ + getCode: async () => { + throw new Error('RPC transport failure') + }, + }), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.completedAt).not.toBeNull() + expect(job?.errorCode).toBe('internal_error') + const errorData = JSON.parse(job?.errorData ?? '{}') as { message?: string } + expect(errorData.message).toContain('RPC transport failure') + }) + + it('records internal_error when getCode returns non-Error rejection', async () => { + const jobId = globalThis.crypto.randomUUID() + await insertJobRow(jobId) + + await runVerificationJob( + env, + { ...JOB_DEFAULTS, jobId }, + { + createPublicClient: () => ({ + getCode: async () => { + throw 'string rejection' + }, + }), + ...makeContainerStub(), + }, + ) + + const job = await getJobRow(jobId) + expect(job?.errorCode).toBe('internal_error') + }) +}) diff --git a/apps/contract-verification/test/unit/signature-reconstruction.test.ts b/apps/contract-verification/test/unit/signature-reconstruction.test.ts new file mode 100644 index 000000000..4d3441530 --- /dev/null +++ b/apps/contract-verification/test/unit/signature-reconstruction.test.ts @@ -0,0 +1,317 @@ +import { describe, expect, it } from 'vitest' +import { Hash, Hex } from 'ox' + +import { + buildSignaturesPayload, + formatAbiParameterType, +} from '#route.lookup.ts' + +describe('formatAbiParameterType', () => { + it('returns primitive types unchanged', () => { + expect(formatAbiParameterType({ type: 'uint256' })).toBe('uint256') + expect(formatAbiParameterType({ type: 'address' })).toBe('address') + expect(formatAbiParameterType({ type: 'bool' })).toBe('bool') + expect(formatAbiParameterType({ type: 'bytes32' })).toBe('bytes32') + expect(formatAbiParameterType({ type: 'string' })).toBe('string') + }) + + it('returns array types unchanged', () => { + expect(formatAbiParameterType({ type: 'uint256[]' })).toBe('uint256[]') + expect(formatAbiParameterType({ type: 'address[3]' })).toBe('address[3]') + }) + + it('reconstructs a flat tuple', () => { + expect( + formatAbiParameterType({ + type: 'tuple', + components: [{ type: 'uint256' }, { type: 'address' }], + }), + ).toBe('(uint256,address)') + }) + + it('reconstructs tuple[]', () => { + expect( + formatAbiParameterType({ + type: 'tuple[]', + components: [{ type: 'uint256' }, { type: 'bool' }], + }), + ).toBe('(uint256,bool)[]') + }) + + it('reconstructs tuple with fixed-size array suffix', () => { + expect( + formatAbiParameterType({ + type: 'tuple[5]', + components: [{ type: 'address' }], + }), + ).toBe('(address)[5]') + }) + + it('reconstructs nested tuple inside tuple', () => { + expect( + formatAbiParameterType({ + type: 'tuple', + components: [ + { type: 'address' }, + { + type: 'tuple', + components: [{ type: 'uint256' }, { type: 'bool' }], + }, + ], + }), + ).toBe('(address,(uint256,bool))') + }) + + it('reconstructs nested tuple[] inside tuple', () => { + expect( + formatAbiParameterType({ + type: 'tuple', + components: [ + { type: 'bytes32' }, + { + type: 'tuple[]', + components: [{ type: 'address' }, { type: 'uint256' }], + }, + ], + }), + ).toBe('(bytes32,(address,uint256)[])') + }) + + it('treats tuple with missing components as empty tuple', () => { + expect(formatAbiParameterType({ type: 'tuple' })).toBe('()') + }) + + it('returns null for non-object input', () => { + expect(formatAbiParameterType(null)).toBeNull() + expect(formatAbiParameterType(undefined)).toBeNull() + expect(formatAbiParameterType(42)).toBeNull() + expect(formatAbiParameterType('uint256')).toBeNull() + }) + + it('returns null when type field is missing', () => { + expect(formatAbiParameterType({ name: 'x' })).toBeNull() + }) + + it('filters out invalid components', () => { + expect( + formatAbiParameterType({ + type: 'tuple', + components: [{ type: 'uint256' }, null, { type: 'bool' }], + }), + ).toBe('(uint256,bool)') + }) +}) + +describe('buildSignaturesPayload', () => { + function hash4(signature: string): string { + const hash32 = Hash.keccak256(Hex.fromString(signature)) + return Hex.fromBytes(Hex.toBytes(hash32).slice(0, 4)) + } + + function hash32(signature: string): string { + return Hash.keccak256(Hex.fromString(signature)) + } + + it('returns empty buckets for non-array input', () => { + const result = buildSignaturesPayload(null) + expect(result).toStrictEqual({ function: [], event: [], error: [] }) + }) + + it('returns empty buckets for empty array', () => { + const result = buildSignaturesPayload([]) + expect(result).toStrictEqual({ function: [], event: [], error: [] }) + }) + + it('classifies function, event, and error', () => { + const abi = [ + { + type: 'function', + name: 'transfer', + inputs: [{ type: 'address' }, { type: 'uint256' }], + }, + { + type: 'event', + name: 'Transfer', + inputs: [{ type: 'address' }, { type: 'address' }, { type: 'uint256' }], + }, + { + type: 'error', + name: 'InsufficientBalance', + inputs: [{ type: 'uint256' }, { type: 'uint256' }], + }, + ] + + const result = buildSignaturesPayload(abi) + + expect(result.function).toHaveLength(1) + expect(result.event).toHaveLength(1) + expect(result.error).toHaveLength(1) + expect(result.function[0]?.signature).toBe('transfer(address,uint256)') + expect(result.function[0]?.signatureHash4).toBe( + hash4('transfer(address,uint256)'), + ) + expect(result.function[0]?.signatureHash32).toBe( + hash32('transfer(address,uint256)'), + ) + expect(result.event[0]?.signature).toBe('Transfer(address,address,uint256)') + expect(result.error[0]?.signature).toBe( + 'InsufficientBalance(uint256,uint256)', + ) + }) + + it('skips constructor, fallback, and receive entries', () => { + const abi = [ + { type: 'constructor', inputs: [{ type: 'uint256' }] }, + { type: 'fallback' }, + { type: 'receive' }, + ] + const result = buildSignaturesPayload(abi) + expect(result.function).toHaveLength(0) + expect(result.event).toHaveLength(0) + expect(result.error).toHaveLength(0) + }) + + it('skips items without a name', () => { + const abi = [{ type: 'function', inputs: [{ type: 'uint256' }] }] + const result = buildSignaturesPayload(abi) + expect(result.function).toHaveLength(0) + }) + + it('builds correct signature for tuple input', () => { + const abi = [ + { + type: 'function', + name: 'doStuff', + inputs: [ + { + type: 'tuple', + components: [{ type: 'address' }, { type: 'uint256' }], + }, + { type: 'bool' }, + ], + }, + ] + + const result = buildSignaturesPayload(abi) + expect(result.function[0]?.signature).toBe( + 'doStuff((address,uint256),bool)', + ) + expect(result.function[0]?.signatureHash4).toBe( + hash4('doStuff((address,uint256),bool)'), + ) + }) + + it('builds correct signature for tuple[] input', () => { + const abi = [ + { + type: 'function', + name: 'batchTransfer', + inputs: [ + { + type: 'tuple[]', + components: [{ type: 'address' }, { type: 'uint256' }], + }, + ], + }, + ] + + const result = buildSignaturesPayload(abi) + expect(result.function[0]?.signature).toBe( + 'batchTransfer((address,uint256)[])', + ) + }) + + it('builds correct signature for nested tuple[] inside tuple', () => { + const abi = [ + { + type: 'function', + name: 'complex', + inputs: [ + { + type: 'tuple', + components: [ + { type: 'bytes32' }, + { + type: 'tuple[]', + components: [{ type: 'address' }, { type: 'uint256' }], + }, + ], + }, + ], + }, + ] + + const result = buildSignaturesPayload(abi) + expect(result.function[0]?.signature).toBe( + 'complex((bytes32,(address,uint256)[]))', + ) + expect(result.function[0]?.signatureHash4).toBe( + hash4('complex((bytes32,(address,uint256)[]))'), + ) + }) + + it('builds correct signature for deeply nested tuples', () => { + const abi = [ + { + type: 'event', + name: 'Deep', + inputs: [ + { + type: 'tuple', + components: [ + { + type: 'tuple', + components: [ + { + type: 'tuple', + components: [{ type: 'uint8' }], + }, + ], + }, + ], + }, + ], + }, + ] + + const result = buildSignaturesPayload(abi) + expect(result.event[0]?.signature).toBe('Deep((((uint8))))') + }) + + it('builds correct signature for error with tuple input', () => { + const abi = [ + { + type: 'error', + name: 'BadOrder', + inputs: [ + { + type: 'tuple', + components: [ + { type: 'address' }, + { type: 'uint256' }, + { type: 'bytes' }, + ], + }, + ], + }, + ] + + const result = buildSignaturesPayload(abi) + expect(result.error[0]?.signature).toBe('BadOrder((address,uint256,bytes))') + expect(result.error[0]?.signatureHash32).toBe( + hash32('BadOrder((address,uint256,bytes))'), + ) + }) + + it('handles function with no inputs', () => { + const abi = [{ type: 'function', name: 'pause', inputs: [] }] + const result = buildSignaturesPayload(abi) + expect(result.function[0]?.signature).toBe('pause()') + }) + + it('handles function with missing inputs field', () => { + const abi = [{ type: 'function', name: 'pause' }] + const result = buildSignaturesPayload(abi) + expect(result.function[0]?.signature).toBe('pause()') + }) +}) diff --git a/apps/contract-verification/vite.config.ts b/apps/contract-verification/vite.config.ts index e7e561429..8a9ae0c30 100644 --- a/apps/contract-verification/vite.config.ts +++ b/apps/contract-verification/vite.config.ts @@ -2,7 +2,6 @@ import NodeChildProcess from 'node:child_process' import NodeProcess from 'node:process' import { cloudflare } from '@cloudflare/vite-plugin' import { defineConfig, loadEnv } from 'vite' -import vitePluginChromiumDevTools from 'vite-plugin-devtools-json' const commitSha = NodeChildProcess.execSync('git rev-parse --short HEAD').toString().trim() || @@ -21,7 +20,7 @@ export default defineConfig((config) => { return { resolve: { tsconfigPaths: true }, - plugins: [cloudflare(), vitePluginChromiumDevTools()], + plugins: [cloudflare()], server: { port, // https://hono.dev/docs/middleware/builtin/cors#using-with-vite diff --git a/apps/contract-verification/vitest.config.ts b/apps/contract-verification/vitest.config.ts index 7543cae30..d274c3ddc 100644 --- a/apps/contract-verification/vitest.config.ts +++ b/apps/contract-verification/vitest.config.ts @@ -13,9 +13,9 @@ export default defineConfig(async () => { return { test: { - setupFiles: ['./test/setup.ts'], - include: ['test/**/*.test.ts'], exclude: ['**/_/**'], + include: ['test/**/*.test.ts'], + setupFiles: ['./test/setup.ts'], }, plugins: [ cloudflareTest({ @@ -23,10 +23,8 @@ export default defineConfig(async () => { configPath: './wrangler.json', }, main: './src/index.tsx', - miniflare: { compatibilityFlags: ['service_binding_extra_handlers'], - bindings: { NODE_ENV: 'test', BUN_VERSION: '1.3.8', diff --git a/apps/contract-verification/wrangler.json b/apps/contract-verification/wrangler.json index b5de1f4bc..bd87f0d61 100644 --- a/apps/contract-verification/wrangler.json +++ b/apps/contract-verification/wrangler.json @@ -1,7 +1,7 @@ { "$schema": "https://esm.sh/wrangler/config-schema.json", "name": "contracts", - "compatibility_date": "2026-03-01", + "compatibility_date": "2026-04-20", "compatibility_flags": ["nodejs_compat"], "main": "./src/index.tsx", "keep_vars": true, @@ -9,6 +9,7 @@ "preview_urls": true, "logpush": true, "vars": { + "CONTAINER_MAX_INSTANCES": 20, "WHITELISTED_ORIGINS": "https://tempo.xyz,https://*.tempo.xyz,https://*.porto.workers.dev" }, "ratelimits": [ diff --git a/apps/explorer/package.json b/apps/explorer/package.json index a0bc4a7a2..d0d91434c 100644 --- a/apps/explorer/package.json +++ b/apps/explorer/package.json @@ -49,6 +49,7 @@ "@tanstack/react-start": "catalog:", "@tanstack/router-plugin": "catalog:", "@tempo/rpc-utils": "workspace:*", + "@wagmi/core": "catalog:", "abitype": "catalog:", "animejs": "catalog:", "cva": "catalog:", diff --git a/apps/explorer/src/lib/domain/contract-source.ts b/apps/explorer/src/lib/domain/contract-source.ts index c49eb5432..9e0c406bd 100644 --- a/apps/explorer/src/lib/domain/contract-source.ts +++ b/apps/explorer/src/lib/domain/contract-source.ts @@ -206,7 +206,7 @@ export async function fetchContractSourceDirect(params: { const { address, chainId, signal } = params const apiUrl = new URL( - `${clientEnv.CONTRACT_VERIFICATION_API_BASE_URL}/v2/contract/${chainId}/${address.toLowerCase()}`, + `${clientEnv.VITE_CONTRACT_VERIFICATION_API_BASE_URL}/v2/contract/${chainId}/${address.toLowerCase()}`, ) apiUrl.searchParams.set('fields', CONTRACT_SOURCE_FIELDS) diff --git a/apps/explorer/src/lib/env.ts b/apps/explorer/src/lib/env.ts index 5ba3cfbec..0092b7b43 100644 --- a/apps/explorer/src/lib/env.ts +++ b/apps/explorer/src/lib/env.ts @@ -3,7 +3,7 @@ import { createIsomorphicFn } from '@tanstack/react-start' import { getRequestUrl } from '@tanstack/react-start/server' const clientEnvSchema = z.object({ - CONTRACT_VERIFICATION_API_BASE_URL: z.prefault( + VITE_CONTRACT_VERIFICATION_API_BASE_URL: z.prefault( z.url(), 'https://contracts.tempo.xyz', ), diff --git a/apps/explorer/src/routes/api/code.ts b/apps/explorer/src/routes/api/code.ts index eaff3a6c7..e3e2d4c53 100644 --- a/apps/explorer/src/routes/api/code.ts +++ b/apps/explorer/src/routes/api/code.ts @@ -10,7 +10,7 @@ import { import { zAddress } from '#lib/zod.ts' import { getRequestURL, clientEnv } from '#lib/env.ts' -const CONTRACT_VERIFICATION_API_BASE_URL = `${clientEnv.CONTRACT_VERIFICATION_API_BASE_URL}/v2/contract` +const CONTRACT_VERIFICATION_API_BASE_URL = `${clientEnv.VITE_CONTRACT_VERIFICATION_API_BASE_URL}/v2/contract` const CONTRACT_SOURCE_FIELDS = [ 'stdJsonInput', @@ -150,12 +150,19 @@ export const Route = createFileRoute('/api/code')({ apiUrl.searchParams.set('fields', CONTRACT_SOURCE_FIELDS) const response = await fetch(apiUrl.toString()) - if (!response.ok) + if (!response.ok) { + const errorText = await response.text() + console.error( + [ + `Failed to fetch contract code: ${response.status} ${response.statusText} - ${errorText}`, + `API URL: ${apiUrl.toString()}`, + ].join('\n'), + ) return Response.json( { error: 'Failed to fetch contract code' }, { status: response.status }, ) - + } const responseData = await response.json() let data: ContractSource diff --git a/package.json b/package.json index e29e159ce..2b1b3036e 100644 --- a/package.json +++ b/package.json @@ -25,15 +25,15 @@ "@biomejs/biome": "catalog:", "@j178/prek": "^0.3.6", "@tempoxyz/lints": "github:tempoxyz/lints", - "@typescript/native-preview": "^7.0.0-dev.20260319.1", + "@typescript/native-preview": "^7.0.0-dev.20260420.1", "simple-git-hooks": "^2.13.1", "tsx": "^4.21.0", - "typescript": "^6.0.2" + "typescript": "^6.0.3" }, "devEngines": { "runtime": { "name": "node", - "version": "24.14.0", + "version": "24.15.0", "onFail": "download" } }, diff --git a/patches/@cloudflare__vite-plugin@1.30.3.patch b/patches/@cloudflare__vite-plugin@1.33.0.patch similarity index 59% rename from patches/@cloudflare__vite-plugin@1.30.3.patch rename to patches/@cloudflare__vite-plugin@1.33.0.patch index c3848c6bb..b7653bc38 100644 --- a/patches/@cloudflare__vite-plugin@1.30.3.patch +++ b/patches/@cloudflare__vite-plugin@1.33.0.patch @@ -1,38 +1,38 @@ --- a/dist/index.mjs +++ b/dist/index.mjs -@@ -26586,7 +26586,7 @@ +@@ -26810,7 +26810,7 @@ //#endregion //#region ../containers-shared/src/build.ts async function constructBuildCommand(options, logger) { -- const platform = options.platform ?? "linux/amd64"; -+ const platform = options.platform ?? (process.arch === "arm64" ? "linux/arm64" : "linux/amd64"); +- const platform$1 = options.platform ?? "linux/amd64"; ++ const platform$1 = options.platform ?? (process.arch === "arm64" ? "linux/arm64" : "linux/amd64"); const buildCmd = [ "build", "--load", -@@ -26656,7 +26656,7 @@ +@@ -26880,7 +26880,7 @@ pathToDockerfile: options.dockerfile, buildContext: options.image_build_context, args: options.image_vars, - platform: "linux/amd64" -+ platform: (process.arch === "arm64" ? "linux/arm64" : "linux/amd64") ++ platform: process.arch === "arm64" ? "linux/arm64" : "linux/amd64" }); return dockerBuild(dockerPath, { buildCmd, -@@ -26931,7 +26931,7 @@ +@@ -27155,7 +27155,7 @@ "pull", getEgressInterceptorImage(), "--platform", - "linux/amd64" -+ (process.arch === "arm64" ? "linux/arm64" : "linux/amd64") ++ process.arch === "arm64" ? "linux/arm64" : "linux/amd64" ]); } async function pullImage(dockerPath, options, logger, isVite) { -@@ -26952,7 +26952,7 @@ +@@ -27176,7 +27176,7 @@ "pull", options.image_uri, "--platform", - "linux/amd64" -+ (process.arch === "arm64" ? "linux/arm64" : "linux/amd64") ++ process.arch === "arm64" ? "linux/arm64" : "linux/amd64" ]); return { abort: () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b4d2aefa9..d4de9b02f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,14 +13,14 @@ catalogs: specifier: ^1.0.6 version: 1.0.6 '@cloudflare/vite-plugin': - specifier: 1.30.3 - version: 1.30.3 + specifier: 1.33.0 + version: 1.33.0 '@cloudflare/vitest-pool-workers': - specifier: ^0.13.5 - version: 0.13.5 + specifier: 0.14.8 + version: 0.14.8 '@cloudflare/workers-types': - specifier: ^4.20260408.1 - version: 4.20260408.1 + specifier: ^4.20260421.1 + version: 4.20260426.1 '@hono/zod-validator': specifier: ^0.7.6 version: 0.7.6 @@ -112,8 +112,8 @@ catalogs: specifier: ^3.1.13 version: 3.1.13 '@types/node': - specifier: ^25.5.2 - version: 25.5.2 + specifier: ^25.6.0 + version: 25.6.0 '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -124,8 +124,8 @@ catalogs: specifier: ^6.0.1 version: 6.0.1 '@vitest/ui': - specifier: 4.1.0 - version: 4.1.0 + specifier: 4.1.4 + version: 4.1.4 '@wagmi/core': specifier: ^3.4.8 version: 3.4.8 @@ -142,8 +142,8 @@ catalogs: specifier: ^1.0.0-beta.4 version: 1.0.0-beta.4 dotenv: - specifier: ^17.3.1 - version: 17.3.1 + specifier: ^17.4.2 + version: 17.4.2 eruda: specifier: ^3.4.3 version: 3.4.3 @@ -151,8 +151,8 @@ catalogs: specifier: ^0.28.0 version: 0.28.0 globals: - specifier: ^17.4.0 - version: 17.4.0 + specifier: ^17.5.0 + version: 17.5.0 hono: specifier: ^4.12.14 version: 4.12.14 @@ -211,8 +211,8 @@ catalogs: specifier: ^2.12.1 version: 2.12.1 typescript: - specifier: ^6.0.2 - version: 6.0.2 + specifier: ^6.0.3 + version: 6.0.3 unplugin-icons: specifier: ^23.0.1 version: 23.0.1 @@ -220,20 +220,17 @@ catalogs: specifier: ^2.48.4 version: 2.48.4 vite: - specifier: ^8.0.7 - version: 8.0.7 - vite-plugin-devtools-json: - specifier: ^1.0.0 - version: 1.0.0 + specifier: ^8.0.9 + version: 8.0.10 vitest: - specifier: 4.1.0 - version: 4.1.0 + specifier: 4.1.4 + version: 4.1.4 wagmi: specifier: ^3.6.9 version: 3.6.9 wrangler: - specifier: 4.79.0 - version: 4.79.0 + specifier: 4.84.0 + version: 4.84.0 zod: specifier: ^4.3.6 version: 4.3.6 @@ -241,19 +238,24 @@ catalogs: overrides: basic-ftp: 5.3.0 flatted: ^3.4.2 - tar: ^7.5.13 + follow-redirects@<=1.15.11: '>=1.16.0' + h3@>=2.0.0-beta.0 <=2.0.1-rc.16: '>=2.0.1-rc.17' + h3@>=2.0.0-beta.4 <2.0.1-rc.18: '>=2.0.1-rc.18' + h3@>=2.0.1-alpha.0 <=2.0.1-rc.16: '>=2.0.1-rc.17' + lodash: ^4.18.1 picomatch@<3: ^2.3.2 picomatch@>=4: ^4.0.4 - lodash: ^4.18.1 - brace-expansion@<3: ^2.0.3 - brace-expansion@>=5: ^5.0.5 - yaml: ^2.8.3 protobufjs: 7.5.5 + protobufjs@<7.5.5: '>=7.5.5' + semver@<7: ^7.7.4 + srvx@<0.11.13: '>=0.11.13' + tar: ^7.5.13 + yaml: ^2.8.3 patchedDependencies: - '@cloudflare/vite-plugin@1.30.3': - hash: e06eefb6e9635561829ac5e497cea44db1c8cfe738d2336f3f1de75842284333 - path: patches/@cloudflare__vite-plugin@1.30.3.patch + '@cloudflare/vite-plugin@1.33.0': + hash: 67ee821cad9ba5e8e7df586b8e9c9c184176999c7d9cb1b856dc34af6243b198 + path: patches/@cloudflare__vite-plugin@1.33.0.patch importers: @@ -267,13 +269,13 @@ importers: version: 0.3.6 '@tempoxyz/lints': specifier: github:tempoxyz/lints - version: https://codeload.github.com/tempoxyz/lints/tar.gz/8af5001866a36967523ece2b8cdc33ba719aad84 + version: https://codeload.github.com/tempoxyz/lints/tar.gz/03cac25d02c1aaa0c6ca87860183879069abb921 '@typescript/native-preview': - specifier: ^7.0.0-dev.20260319.1 - version: 7.0.0-dev.20260408.1 + specifier: ^7.0.0-dev.20260420.1 + version: 7.0.0-dev.20260427.1 node: - specifier: runtime:24.14.0 - version: runtime:24.14.0 + specifier: runtime:24.15.0 + version: runtime:24.15.0 simple-git-hooks: specifier: ^2.13.1 version: 2.13.1 @@ -281,14 +283,14 @@ importers: specifier: ^4.21.0 version: 4.21.0 typescript: - specifier: ^6.0.2 - version: 6.0.2 + specifier: ^6.0.3 + version: 6.0.3 apps/contract-verification: dependencies: '@cloudflare/containers': - specifier: ^0.2.3 - version: 0.2.4 + specifier: ^0.3.2 + version: 0.3.3 '@hono/zod-validator': specifier: 'catalog:' version: 0.7.6(hono@4.12.14)(zod@4.3.6) @@ -310,15 +312,18 @@ importers: '@logtape/redaction': specifier: 'catalog:' version: 2.0.4(@logtape/logtape@2.0.4) + '@std/semver': + specifier: npm:@jsr/std__semver + version: '@jsr/std__semver@1.0.8' '@wagmi/core': specifier: 'catalog:' - version: 3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)) + version: 3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) cbor-x: specifier: ^1.6.4 version: 1.6.4 drizzle-orm: specifier: ^0.45.2 - version: 0.45.2(@cloudflare/workers-types@4.20260408.1)(@libsql/client@0.17.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(@opentelemetry/api@1.9.0)(bun-types@1.3.11)(kysely@0.28.15) + version: 0.45.2(@cloudflare/workers-types@4.20260426.1)(@libsql/client@0.17.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(@opentelemetry/api@1.9.0)(bun-types@1.3.13)(kysely@0.28.15) hono: specifier: 'catalog:' version: 4.12.14 @@ -327,16 +332,13 @@ importers: version: 0.5.3(hono@4.12.14) ox: specifier: 'catalog:' - version: 0.14.20(typescript@6.0.2)(zod@4.3.6) - semver: - specifier: ^7.7.4 - version: 7.7.4 + version: 0.14.20(typescript@6.0.3)(zod@4.3.6) viem: specifier: 'catalog:' - version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) wagmi: specifier: 'catalog:' - version: 3.6.9(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)) + version: 3.6.9(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) zod: specifier: 'catalog:' version: 4.3.6 @@ -346,34 +348,28 @@ importers: version: 2.4.10 '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.30.3(patch_hash=e06eefb6e9635561829ac5e497cea44db1c8cfe738d2336f3f1de75842284333)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260329.1)(wrangler@4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + version: 1.33.0(patch_hash=67ee821cad9ba5e8e7df586b8e9c9c184176999c7d9cb1b856dc34af6243b198)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260420.1)(wrangler@4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@cloudflare/vitest-pool-workers': specifier: 'catalog:' - version: 0.13.5(@cloudflare/workers-types@4.20260408.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.0) + version: 0.14.8(@cloudflare/workers-types@4.20260426.1)(@vitest/runner@4.1.4)(@vitest/snapshot@4.1.4)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.4) '@cloudflare/workers-types': specifier: 'catalog:' - version: 4.20260408.1 + version: 4.20260426.1 '@total-typescript/ts-reset': specifier: 'catalog:' version: 0.6.1 '@types/bun': - specifier: ^1.3.11 - version: 1.3.11 + specifier: ^1.3.12 + version: 1.3.13 '@types/node': specifier: 'catalog:' - version: 25.5.2 - '@types/semver': - specifier: ^7.7.1 - version: 7.7.1 - '@vitest/coverage-istanbul': - specifier: 4.1.0 - version: 4.1.0(vitest@4.1.0) + version: 25.6.0 '@vitest/ui': specifier: 'catalog:' - version: 4.1.0(vitest@4.1.0) + version: 4.1.4(vitest@4.1.4) dotenv: specifier: 'catalog:' - version: 17.3.1 + version: 17.4.2 drizzle-kit: specifier: ^0.31.10 version: 0.31.10 @@ -382,31 +378,28 @@ importers: version: 0.28.0 typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 vite: specifier: 'catalog:' - version: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) - vite-plugin-devtools-json: - specifier: 'catalog:' - version: 1.0.0(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.1.0)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/ui@4.1.4)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) wrangler: specifier: 'catalog:' - version: 4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) + version: 4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) apps/explorer: dependencies: '@sentry/cloudflare': specifier: 'catalog:' - version: 10.45.0(@cloudflare/workers-types@4.20260408.1) + version: 10.45.0(@cloudflare/workers-types@4.20260426.1) '@sentry/react': specifier: 'catalog:' version: 10.45.0(react@19.2.5) '@shazow/whatsabi': specifier: 'catalog:' - version: 0.26.0(@noble/hashes@1.8.0)(typescript@6.0.2)(zod@4.3.6) + version: 0.26.0(@noble/hashes@1.8.0)(typescript@6.0.3)(zod@4.3.6) '@shikijs/langs': specifier: 'catalog:' version: 4.0.2 @@ -415,7 +408,7 @@ importers: version: 4.0.2 '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.2(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.2.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/query-async-storage-persister': specifier: 'catalog:' version: 5.96.2 @@ -433,37 +426,40 @@ importers: version: 1.166.10(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@tanstack/router-core@1.168.9)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tanstack/react-start': specifier: 'catalog:' - version: 1.167.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.167.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/router-plugin': specifier: 'catalog:' - version: 1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) '@tempo/rpc-utils': specifier: workspace:* version: link:../../packages/rpc-utils + '@wagmi/core': + specifier: 'catalog:' + version: 3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) abitype: specifier: 'catalog:' - version: 1.2.4(typescript@6.0.2)(zod@4.3.6) + version: 1.2.4(typescript@6.0.3)(zod@4.3.6) accounts: specifier: 'catalog:' - version: 0.8.5(@types/react@19.2.14)(@wagmi/core@3.4.8)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))(wagmi@3.6.9) + version: 0.8.5(@types/react@19.2.14)(@wagmi/core@3.4.8)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))(wagmi@3.6.9) animejs: specifier: 'catalog:' version: 4.3.6 cva: specifier: 'catalog:' - version: 1.0.0-beta.4(typescript@6.0.2) + version: 1.0.0-beta.4(typescript@6.0.3) hono: specifier: 'catalog:' version: 4.12.14 idxs: specifier: 'catalog:' - version: 0.0.6(typescript@6.0.2) + version: 0.0.6(typescript@6.0.3) midcut: specifier: 'catalog:' version: 0.2.0(react@19.2.5) ox: specifier: 'catalog:' - version: 0.14.20(typescript@6.0.2)(zod@4.3.6) + version: 0.14.20(typescript@6.0.3)(zod@4.3.6) react: specifier: 'catalog:' version: 19.2.5 @@ -475,13 +471,13 @@ importers: version: 4.2.2 tidx.ts: specifier: 'catalog:' - version: 0.1.1(typescript@6.0.2) + version: 0.1.1(typescript@6.0.3) viem: specifier: 'catalog:' - version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) wagmi: specifier: 'catalog:' - version: 3.6.9(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)) + version: 3.6.9(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) zod: specifier: 'catalog:' version: 4.3.6 @@ -491,10 +487,10 @@ importers: version: 1.0.6(bufferutil@4.1.0)(utf-8-validate@5.0.10) '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.30.3(patch_hash=e06eefb6e9635561829ac5e497cea44db1c8cfe738d2336f3f1de75842284333)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260405.1)(wrangler@4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + version: 1.33.0(patch_hash=67ee821cad9ba5e8e7df586b8e9c9c184176999c7d9cb1b856dc34af6243b198)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260420.1)(wrangler@4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@cloudflare/vitest-pool-workers': specifier: 'catalog:' - version: 0.13.5(@cloudflare/workers-types@4.20260408.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.0) + version: 0.14.8(@cloudflare/workers-types@4.20260426.1)(@vitest/runner@4.1.4)(@vitest/snapshot@4.1.4)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.4) '@iconify/json': specifier: 'catalog:' version: 2.2.460 @@ -503,13 +499,13 @@ importers: version: 5.1.1(rollup@4.59.0) '@svgr/core': specifier: 'catalog:' - version: 8.1.0(typescript@6.0.2) + version: 8.1.0(typescript@6.0.3) '@svgr/plugin-jsx': specifier: 'catalog:' - version: 8.1.0(@svgr/core@8.1.0(typescript@6.0.2)) + version: 8.1.0(@svgr/core@8.1.0(typescript@6.0.3)) '@tanstack/devtools-vite': specifier: 'catalog:' - version: 0.6.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 0.6.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/react-devtools': specifier: 'catalog:' version: 0.10.2(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(bufferutil@4.1.0)(csstype@3.2.3)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(solid-js@1.9.11)(utf-8-validate@5.0.10) @@ -527,7 +523,7 @@ importers: version: 3.1.13 '@types/node': specifier: 'catalog:' - version: 25.5.2 + version: 25.6.0 '@types/react': specifier: 'catalog:' version: 19.2.14 @@ -536,13 +532,13 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.0.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) eruda: specifier: 'catalog:' version: 3.4.3 rollup-plugin-visualizer: specifier: 'catalog:' - version: 7.0.1(rolldown@1.0.0-rc.13)(rollup@4.59.0) + version: 7.0.1(rolldown@1.0.0-rc.17)(rollup@4.59.0) shiki: specifier: 'catalog:' version: 4.0.2 @@ -560,19 +556,19 @@ importers: version: 2.12.1 unplugin-icons: specifier: 'catalog:' - version: 23.0.1(@svgr/core@8.1.0(typescript@6.0.2))(@vue/compiler-sfc@3.5.30) + version: 23.0.1(@svgr/core@8.1.0(typescript@6.0.3))(@vue/compiler-sfc@3.5.30) vite: specifier: 'catalog:' - version: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) vite-plugin-devtools-json: specifier: ^1.0.0 - version: 1.0.0(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.0.0(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) vitest: specifier: 'catalog:' - version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.1.0)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/ui@4.1.4)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) wrangler: specifier: 'catalog:' - version: 4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) + version: 4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) apps/fee-payer: dependencies: @@ -584,38 +580,38 @@ importers: version: 4.12.14 idxs: specifier: 'catalog:' - version: 0.0.6(typescript@6.0.2) + version: 0.0.6(typescript@6.0.3) kysely: specifier: 'catalog:' version: 0.28.15 ox: specifier: 'catalog:' - version: 0.14.20(typescript@6.0.2)(zod@4.3.6) + version: 0.14.20(typescript@6.0.3)(zod@4.3.6) tempo.ts: specifier: 'catalog:' - version: 0.14.2(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))(zod@4.3.6) + version: 0.14.2(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))(zod@4.3.6) viem: specifier: 'catalog:' - version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) zod: specifier: 'catalog:' version: 4.3.6 devDependencies: '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.30.3(patch_hash=e06eefb6e9635561829ac5e497cea44db1c8cfe738d2336f3f1de75842284333)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260405.1)(wrangler@4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + version: 1.33.0(patch_hash=67ee821cad9ba5e8e7df586b8e9c9c184176999c7d9cb1b856dc34af6243b198)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260420.1)(wrangler@4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@cloudflare/vitest-pool-workers': specifier: 'catalog:' - version: 0.13.5(@cloudflare/workers-types@4.20260408.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.0) + version: 0.14.8(@cloudflare/workers-types@4.20260426.1)(@vitest/runner@4.1.4)(@vitest/snapshot@4.1.4)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.4) '@cloudflare/workers-types': specifier: 'catalog:' - version: 4.20260408.1 + version: 4.20260426.1 '@types/node': specifier: 'catalog:' - version: 25.5.2 + version: 25.6.0 dotenv: specifier: 'catalog:' - version: 17.3.1 + version: 17.4.2 prool: specifier: 'catalog:' version: 0.2.4(testcontainers@11.13.0) @@ -624,16 +620,16 @@ importers: version: 11.13.0 typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 vite: specifier: 'catalog:' - version: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) vitest: specifier: 'catalog:' - version: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.1.0)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/ui@4.1.4)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) wrangler: specifier: 'catalog:' - version: 4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) + version: 4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) apps/fee-payer/playground: dependencies: @@ -648,17 +644,17 @@ importers: version: 19.2.5(react@19.2.5) tempo.ts: specifier: 'catalog:' - version: 0.14.2(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))(zod@4.3.6) + version: 0.14.2(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))(zod@4.3.6) viem: specifier: 'catalog:' - version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) wagmi: specifier: 'catalog:' - version: 3.6.9(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)) + version: 3.6.9(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) devDependencies: '@types/node': specifier: 'catalog:' - version: 25.5.2 + version: 25.6.0 '@types/react': specifier: 'catalog:' version: 19.2.14 @@ -667,35 +663,35 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.0.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) globals: specifier: 'catalog:' - version: 17.4.0 + version: 17.5.0 typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 vite: specifier: 'catalog:' - version: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) apps/key-manager: dependencies: tempo.ts: specifier: 'catalog:' - version: 0.14.2(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))(zod@4.3.6) + version: 0.14.2(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))(zod@4.3.6) devDependencies: '@cloudflare/workers-types': specifier: 'catalog:' - version: 4.20260408.1 + version: 4.20260426.1 '@types/node': specifier: 'catalog:' - version: 25.5.2 + version: 25.6.0 typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 wrangler: specifier: 'catalog:' - version: 4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) + version: 4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) apps/og: dependencies: @@ -713,10 +709,10 @@ importers: version: 4.12.14 ox: specifier: 'catalog:' - version: 0.14.20(typescript@6.0.2)(zod@4.3.6) + version: 0.14.20(typescript@6.0.3)(zod@4.3.6) viem: specifier: 'catalog:' - version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) zod: specifier: 'catalog:' version: 4.3.6 @@ -726,22 +722,22 @@ importers: version: 2.4.10 '@cloudflare/workers-types': specifier: 'catalog:' - version: 4.20260408.1 + version: 4.20260426.1 '@types/node': specifier: 'catalog:' - version: 25.5.2 + version: 25.6.0 wrangler: specifier: 'catalog:' - version: 4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) + version: 4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) apps/perf: dependencies: '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.30.3(patch_hash=e06eefb6e9635561829ac5e497cea44db1c8cfe738d2336f3f1de75842284333)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260405.1)(wrangler@4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + version: 1.33.0(patch_hash=67ee821cad9ba5e8e7df586b8e9c9c184176999c7d9cb1b856dc34af6243b198)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260420.1)(wrangler@4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@tailwindcss/vite': specifier: 'catalog:' - version: 4.2.2(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 4.2.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/react-query': specifier: 'catalog:' version: 5.96.2(react@19.2.5) @@ -753,13 +749,13 @@ importers: version: 1.166.10(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(@tanstack/router-core@1.168.9)(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tanstack/react-start': specifier: 'catalog:' - version: 1.167.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.167.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/router-plugin': specifier: 'catalog:' - version: 1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) cva: specifier: 'catalog:' - version: 1.0.0-beta.4(typescript@6.0.2) + version: 1.0.0-beta.4(typescript@6.0.3) hono: specifier: 'catalog:' version: 4.12.14 @@ -781,7 +777,7 @@ importers: version: 0.6.1 '@types/node': specifier: 'catalog:' - version: 25.5.2 + version: 25.6.0 '@types/react': specifier: 'catalog:' version: 19.2.14 @@ -790,7 +786,7 @@ importers: version: 19.2.3(@types/react@19.2.14) '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + version: 6.0.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) tailwind-merge: specifier: 'catalog:' version: 3.5.0 @@ -799,13 +795,13 @@ importers: version: 2.12.1 unplugin-icons: specifier: 'catalog:' - version: 23.0.1(@svgr/core@8.1.0(typescript@6.0.2))(@vue/compiler-sfc@3.5.30) + version: 23.0.1(@svgr/core@8.1.0(typescript@6.0.3))(@vue/compiler-sfc@3.5.30) vite: specifier: 'catalog:' - version: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) wrangler: specifier: 'catalog:' - version: 4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) + version: 4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) apps/tokenlist: dependencies: @@ -817,42 +813,42 @@ importers: version: 4.0.1 viem: specifier: 'catalog:' - version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) devDependencies: '@biomejs/biome': specifier: 'catalog:' version: 2.4.10 '@cloudflare/vite-plugin': specifier: 'catalog:' - version: 1.30.3(patch_hash=e06eefb6e9635561829ac5e497cea44db1c8cfe738d2336f3f1de75842284333)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260405.1)(wrangler@4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10)) + version: 1.33.0(patch_hash=67ee821cad9ba5e8e7df586b8e9c9c184176999c7d9cb1b856dc34af6243b198)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260420.1)(wrangler@4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10)) '@cloudflare/workers-types': specifier: 'catalog:' - version: 4.20260408.1 + version: 4.20260426.1 '@total-typescript/ts-reset': specifier: 'catalog:' version: 0.6.1 '@types/node': specifier: 'catalog:' - version: 25.5.2 + version: 25.6.0 typescript: specifier: 'catalog:' - version: 6.0.2 + version: 6.0.3 vite: specifier: 'catalog:' - version: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + version: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) wrangler: specifier: 'catalog:' - version: 4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) + version: 4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) packages/rpc-utils: dependencies: viem: specifier: 'catalog:' - version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + version: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) devDependencies: '@types/node': specifier: 'catalog:' - version: 25.5.2 + version: 25.6.0 packages: @@ -1091,8 +1087,8 @@ packages: '@cfworker/json-schema@4.1.1': resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==} - '@cloudflare/containers@0.2.4': - resolution: {integrity: sha512-G/Wsl0EuLbSgXyr9zv7cetUO90NyinKIMZvXae/C5R5BY3XMl9ZUdplvcofylAwRZjBEUm+aUugWxIiiKQU0ZA==} + '@cloudflare/containers@0.3.3': + resolution: {integrity: sha512-ZSXmArCoo5bVTp8pGAJdl5WKmwtZDcffJqr4JcZEbSmMIFjU+AlBqgysuxXMgu03Rp239cOdqerbjK7H0K2krQ==} '@cloudflare/kv-asset-handler@0.4.2': resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} @@ -1111,111 +1107,51 @@ packages: workerd: optional: true - '@cloudflare/vite-plugin@1.30.3': - resolution: {integrity: sha512-KFjdec5QlleBjt/hJx+9kAJbyCTFKn1IgZAaEwShSVl46GqTfOu2xiabGVv4at+FKZmw6/e++8xZ+YKbbyoEDw==} + '@cloudflare/vite-plugin@1.33.0': + resolution: {integrity: sha512-x7JwilJdTK5BbN/9OovJpA82xIhFaPmdo33e7Ns/mZSOrtGSLja7nUK/fz2oykq9mwqVbSlFZnLFMSTWSSYFug==} peerDependencies: vite: ^6.1.0 || ^7.0.0 || ^8.0.0 - wrangler: ^4.79.0 + wrangler: ^4.84.0 - '@cloudflare/vitest-pool-workers@0.13.5': - resolution: {integrity: sha512-T9yRcJXbJOguWvwCv4NjjPt0iOwUorbuR1pkEPWhmRKWBmZJ9ag/WFrIxCjQ6ZIFkKprJR3y+nels4jRQVZNiA==} + '@cloudflare/vitest-pool-workers@0.14.8': + resolution: {integrity: sha512-A48YYGoJkJHMj5iTv1pK9LpiBWD5gK/cI5N1NmUYx+bVllfJL5LKGXPEp9CETlhQCTEa8XkSy0zjNf7KF/7LsA==} peerDependencies: '@vitest/runner': ^4.1.0 '@vitest/snapshot': ^4.1.0 vitest: ^4.1.0 - '@cloudflare/workerd-darwin-64@1.20260317.1': - resolution: {integrity: sha512-8hjh3sPMwY8M/zedq3/sXoA2Q4BedlGufn3KOOleIG+5a4ReQKLlUah140D7J6zlKmYZAFMJ4tWC7hCuI/s79g==} + '@cloudflare/workerd-darwin-64@1.20260420.1': + resolution: {integrity: sha512-Y6HtAY+pS5INiD9HyO1JvvujZO24mD3eqRwPZlLXBkcT+wW8bTOve/8mVKErEzEtZ5LkuT3tJqG9py8TxQEBgw==} engines: {node: '>=16'} cpu: [x64] os: [darwin] - '@cloudflare/workerd-darwin-64@1.20260329.1': - resolution: {integrity: sha512-oyDXYlPBuGXKkZ85+M3jFz0/qYmvA4AEURN8USIGPDCR5q+HFSRwywSd9neTx3Wi7jhey2wuYaEpD3fEFWyWUA==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@cloudflare/workerd-darwin-64@1.20260405.1': - resolution: {integrity: sha512-EbmdBcmeIGogKG4V1odSWQe7z4rHssUD4iaXv0cXA22/MFrzH3iQT0R+FJFyhucGtih/9B9E+6j0QbSQD8xT3w==} - engines: {node: '>=16'} - cpu: [x64] - os: [darwin] - - '@cloudflare/workerd-darwin-arm64@1.20260317.1': - resolution: {integrity: sha512-M/MnNyvO5HMgoIdr3QHjdCj2T1ki9gt0vIUnxYxBu9ISXS/jgtMl6chUVPJ7zHYBn9MyYr8ByeN6frjYxj0MGg==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@cloudflare/workerd-darwin-arm64@1.20260329.1': - resolution: {integrity: sha512-++ZxVa3ovzYeDLEG6zMqql9gzZAG8vak6ZSBQgprGKZp7akr+GKTpw9f3RrMP552NSi3gTisroLobrrkPBtYLQ==} - engines: {node: '>=16'} - cpu: [arm64] - os: [darwin] - - '@cloudflare/workerd-darwin-arm64@1.20260405.1': - resolution: {integrity: sha512-r44r418bOQtoP+Odu+L/BQM9q5cRSXRd1N167PgZQIo4MlqzTwHO4L0wwXhxbcV/PF46rrQre/uTFS8R0R+xSQ==} + '@cloudflare/workerd-darwin-arm64@1.20260420.1': + resolution: {integrity: sha512-7aiRtZTc5S4aKcL6uIx+B3tCzb/bULjQmE67/03k0HtaDNzP20GnYmYpFCqleFqsdmIb4Tx8PkKPmsXI3AJLvQ==} engines: {node: '>=16'} cpu: [arm64] os: [darwin] - '@cloudflare/workerd-linux-64@1.20260317.1': - resolution: {integrity: sha512-1ltuEjkRcS3fsVF7CxsKlWiRmzq2ZqMfqDN0qUOgbUwkpXsLVJsXmoblaLf5OP00ELlcgF0QsN0p2xPEua4Uug==} + '@cloudflare/workerd-linux-64@1.20260420.1': + resolution: {integrity: sha512-J/DW149FPmug1wSM32zBF7My14xg+inIYwzS4bSAxyXR6tBiTxbhgFWQQz99nt08ZMstdKHRD6f6C/KQaleQcA==} engines: {node: '>=16'} cpu: [x64] os: [linux] - '@cloudflare/workerd-linux-64@1.20260329.1': - resolution: {integrity: sha512-kkeywAgIHwbqHkVILqbj/YkfbrA6ARbmutjiYzZA2MwMSfNXlw6/kedAKOY8YwcymZIgepx3YTIPnBP50pOotw==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@cloudflare/workerd-linux-64@1.20260405.1': - resolution: {integrity: sha512-Aaq3RWnaTCzMBo77wC8fjOx+SFdO/rlcXa6HAf+PJs51LyMISFOBCJKqSlS6Irphen0WHHxFKPHUO9bjfj8g2g==} - engines: {node: '>=16'} - cpu: [x64] - os: [linux] - - '@cloudflare/workerd-linux-arm64@1.20260317.1': - resolution: {integrity: sha512-3QrNnPF1xlaNwkHpasvRvAMidOvQs2NhXQmALJrEfpIJ/IDL2la8g499yXp3eqhG3hVMCB07XVY149GTs42Xtw==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@cloudflare/workerd-linux-arm64@1.20260329.1': - resolution: {integrity: sha512-eYBN20+B7XOUSWEe0mlqkMUbfLoIKjKZnpqQiSxnLbL72JKY0D/KlfN/b7RVGLpewB7i8rTrwTNr0szCKnZzSQ==} - engines: {node: '>=16'} - cpu: [arm64] - os: [linux] - - '@cloudflare/workerd-linux-arm64@1.20260405.1': - resolution: {integrity: sha512-Lbp9Z2wiMzy3Sji3YwMHK5WDlejsH3jF4swAFEv7+jIf3NowZHga3GzwTypNRmcwnfz/XrqQ7Hc0Ul9OoU/lCw==} + '@cloudflare/workerd-linux-arm64@1.20260420.1': + resolution: {integrity: sha512-a5I147McRM/L4YHu9EwOsoAyIExZndPRQoLx/33dbw/yUEnO825gvn5QZkCGXBVL2JwsPAyowB0Xliqrj+71Sw==} engines: {node: '>=16'} cpu: [arm64] os: [linux] - '@cloudflare/workerd-windows-64@1.20260317.1': - resolution: {integrity: sha512-MfZTz+7LfuIpMGTa3RLXHX8Z/pnycZLItn94WRdHr8LPVet+C5/1Nzei399w/jr3+kzT4pDKk26JF/tlI5elpQ==} + '@cloudflare/workerd-windows-64@1.20260420.1': + resolution: {integrity: sha512-ZrHqlHbJNU8P24EAOBaZ6B44G9P+po2z0DBwbAr8965aWR+vohy3cfmgE9uzNPAQfKNmvq7fmc4VwsRpERkg0w==} engines: {node: '>=16'} cpu: [x64] os: [win32] - '@cloudflare/workerd-windows-64@1.20260329.1': - resolution: {integrity: sha512-5R+/oxrDhS9nL3oA3ZWtD6ndMOqm7RfKknDNxLcmYW5DkUu7UH3J/s1t/Dz66iFePzr5BJmE7/8gbmve6TjtZQ==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - - '@cloudflare/workerd-windows-64@1.20260405.1': - resolution: {integrity: sha512-FhE0kt93kj5JnSPVqi4BAXpQQENyKnuSOoJLd35mkMMGhtPrwv5EsReJdck0S8hUocCBlb+U0RmP8ta6k41HjQ==} - engines: {node: '>=16'} - cpu: [x64] - os: [win32] - - '@cloudflare/workers-types@4.20260408.1': - resolution: {integrity: sha512-kE1tKfHUyIldsj3ea2XEqvLRHkDwc83YM7nar6SS5+cj81IoAFR/OZNDwZWHb6vx+pC31PBJGtROlfZzsgxudQ==} + '@cloudflare/workers-types@4.20260426.1': + resolution: {integrity: sha512-cBYeQaWwv/jFV8ualmwp6wIxmAf0rDe2DPPQwPbslKmPHqgv861YpAvm45r05K40QboZgxNQVIPgNkmtHqZeJQ==} '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} @@ -1224,14 +1160,17 @@ packages: '@drizzle-team/brocli@0.10.2': resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==} - '@emnapi/core@1.9.1': - resolution: {integrity: sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} '@emnapi/runtime@1.9.1': resolution: {integrity: sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==} - '@emnapi/wasi-threads@1.2.0': - resolution: {integrity: sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==} + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} '@esbuild-kit/core-utils@3.3.2': resolution: {integrity: sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ==} @@ -2187,10 +2126,6 @@ packages: resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} engines: {node: '>=18.0.0'} - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} - engines: {node: '>=8'} - '@j178/prek@0.3.6': resolution: {integrity: sha512-+INLkoAR2axZ8MXbHMxcGOv6RIgyGGErAFMpsTcHoNtocEIjGAdTVhAz+JpHC3I6yBmIDYG4ztzuOV1Q2mYvFQ==} engines: {node: '>=14', npm: '>=6'} @@ -2218,6 +2153,9 @@ packages: '@js-sdsl/ordered-map@4.4.2': resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + '@jsr/std__semver@1.0.8': + resolution: {integrity: sha512-YhkykPU2Majz66e+rQbP0okYc7kKv+U32aguLPCXZZAL+vEVmBA+khHjPHhLBpWR073gzU3WHqGRgB7a/aXCjg==, tarball: https://npm.jsr.io/~/11/@jsr/std__semver/1.0.8.tgz} + '@kwsites/file-exists@1.1.1': resolution: {integrity: sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==} @@ -2316,8 +2254,8 @@ packages: '@cfworker/json-schema': optional: true - '@napi-rs/wasm-runtime@1.1.3': - resolution: {integrity: sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==} + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} peerDependencies: '@emnapi/core': ^1.7.1 '@emnapi/runtime': ^1.7.1 @@ -2357,8 +2295,8 @@ packages: resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} - '@oxc-project/types@0.123.0': - resolution: {integrity: sha512-YtECP/y8Mj1lSHiUWGSRzy/C6teUKlS87dEfuVKT09LgQbUsBW1rNg+MiJ4buGu3yuADV60gbIvo9/HplA56Ew==} + '@oxc-project/types@0.127.0': + resolution: {integrity: sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==} '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} @@ -2420,97 +2358,97 @@ packages: '@remix-run/session@0.4.1': resolution: {integrity: sha512-Bm6aKYgutb/raHZ3laloz8g/Qu7f3CeK3o4gUVDMxtEiAdWCzJamwHoTpGOc5+g1Kuy7z85v4M6nGrF06MFDSg==} - '@rolldown/binding-android-arm64@1.0.0-rc.13': - resolution: {integrity: sha512-5ZiiecKH2DXAVJTNN13gNMUcCDg4Jy8ZjbXEsPnqa248wgOVeYRX0iqXXD5Jz4bI9BFHgKsI2qmyJynstbmr+g==} + '@rolldown/binding-android-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@rolldown/binding-darwin-arm64@1.0.0-rc.13': - resolution: {integrity: sha512-tz/v/8G77seu8zAB3A5sK3UFoOl06zcshEzhUO62sAEtrEuW/H1CcyoupOrD+NbQJytYgA4CppXPzlrmp4JZKA==} + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@rolldown/binding-darwin-x64@1.0.0-rc.13': - resolution: {integrity: sha512-8DakphqOz8JrMYWTJmWA+vDJxut6LijZ8Xcdc4flOlAhU7PNVwo2MaWBF9iXjJAPo5rC/IxEFZDhJ3GC7NHvug==} + '@rolldown/binding-darwin-x64@1.0.0-rc.17': + resolution: {integrity: sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@rolldown/binding-freebsd-x64@1.0.0-rc.13': - resolution: {integrity: sha512-4wBQFfjDuXYN/SVI8inBF3Aa+isq40rc6VMFbk5jcpolUBTe5cYnMsHZ51nFWsx3PVyyNN3vgoESki0Hmr/4BA==} + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': + resolution: {integrity: sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.13': - resolution: {integrity: sha512-JW/e4yPIXLms+jmnbwwy5LA/LxVwZUWLN8xug+V200wzaVi5TEGIWQlh8o91gWYFxW609euI98OCCemmWGuPrw==} + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': + resolution: {integrity: sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.13': - resolution: {integrity: sha512-ZfKWpXiUymDnavepCaM6KG/uGydJ4l2nBmMxg60Ci4CbeefpqjPWpfaZM7PThOhk2dssqBAcwLc6rAyr0uTdXg==} + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.13': - resolution: {integrity: sha512-bmRg3O6Z0gq9yodKKWCIpnlH051sEfdVwt+6m5UDffAQMUUqU0xjnQqqAUm+Gu7ofAAly9DqiQDtKu2nPDEABA==} + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.13': - resolution: {integrity: sha512-8Wtnbw4k7pMYN9B/mOEAsQ8HOiq7AZ31Ig4M9BKn2So4xRaFEhtCSa4ZJaOutOWq50zpgR4N5+L/opnlaCx8wQ==} + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.13': - resolution: {integrity: sha512-D/0Nlo8mQuxSMohNJUF2lDXWRsFDsHldfRRgD9bRgktj+EndGPj4DOV37LqDKPYS+osdyhZEH7fTakTAEcW7qg==} + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.13': - resolution: {integrity: sha512-eRrPvat2YaVQcwwKi/JzOP6MKf1WRnOCr+VaI3cTWz3ZoLcP/654z90lVCJ4dAuMEpPdke0n+qyAqXDZdIC4rA==} + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': + resolution: {integrity: sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@rolldown/binding-linux-x64-musl@1.0.0-rc.13': - resolution: {integrity: sha512-PsdONiFRp8hR8KgVjTWjZ9s7uA3uueWL0t74/cKHfM4dR5zXYv4AjB8BvA+QDToqxAFg4ZkcVEqeu5F7inoz5w==} + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': + resolution: {integrity: sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@rolldown/binding-openharmony-arm64@1.0.0-rc.13': - resolution: {integrity: sha512-hCNXgC5dI3TVOLrPT++PKFNZ+1EtS0mLQwfXXXSUD/+rGlB65gZDwN/IDuxLpQP4x8RYYHqGomlUXzpO8aVI2w==} + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': + resolution: {integrity: sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@rolldown/binding-wasm32-wasi@1.0.0-rc.13': - resolution: {integrity: sha512-viLS5C5et8NFtLWw9Sw3M/w4vvnVkbWkO7wSNh3C+7G1+uCkGpr6PcjNDSFcNtmXY/4trjPBqUfcOL+P3sWy/g==} - engines: {node: '>=14.0.0'} + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': + resolution: {integrity: sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==} + engines: {node: ^20.19.0 || >=22.12.0} cpu: [wasm32] - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.13': - resolution: {integrity: sha512-Fqa3Tlt1xL4wzmAYxGNFV36Hb+VfPc9PYU+E25DAnswXv3ODDu/yyWjQDbXMo5AGWkQVjLgQExuVu8I/UaZhPQ==} + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.13': - resolution: {integrity: sha512-/pLI5kPkGEi44TDlnbio3St/5gUFeN51YWNAk/Gnv6mEQBOahRBh52qVFVBpmrnU01n2yysvBML9Ynu7K4kGAQ==} + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': + resolution: {integrity: sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -2518,8 +2456,8 @@ packages: '@rolldown/pluginutils@1.0.0-beta.40': resolution: {integrity: sha512-s3GeJKSQOwBlzdUrj4ISjJj5SfSh+aqn0wjOar4Bx95iV1ETI7F6S/5hLcfAxZ9kXDcyrAkxPlqmd1ZITttf+w==} - '@rolldown/pluginutils@1.0.0-rc.13': - resolution: {integrity: sha512-3ngTAv6F/Py35BsYbeeLeecvhMKdsKm4AoOETVhAA+Qc8nrA2I0kF7oa93mE9qnIurngOSpMnQ0x2nQY2FPviA==} + '@rolldown/pluginutils@1.0.0-rc.17': + resolution: {integrity: sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==} '@rolldown/pluginutils@1.0.0-rc.7': resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} @@ -3307,8 +3245,8 @@ packages: engines: {node: '>=20.19'} hasBin: true - '@tempoxyz/lints@https://codeload.github.com/tempoxyz/lints/tar.gz/8af5001866a36967523ece2b8cdc33ba719aad84': - resolution: {tarball: https://codeload.github.com/tempoxyz/lints/tar.gz/8af5001866a36967523ece2b8cdc33ba719aad84} + '@tempoxyz/lints@https://codeload.github.com/tempoxyz/lints/tar.gz/03cac25d02c1aaa0c6ca87860183879069abb921': + resolution: {tarball: https://codeload.github.com/tempoxyz/lints/tar.gz/03cac25d02c1aaa0c6ca87860183879069abb921} version: 0.1.1 engines: {node: '>=18'} hasBin: true @@ -3328,8 +3266,8 @@ packages: '@types/animejs@3.1.13': resolution: {integrity: sha512-yWg9l1z7CAv/TKpty4/vupEh24jDGUZXv4r26StRkpUPQm04ztJaftgpto8vwdFs8SiTq6XfaPKCSI+wjzNMvQ==} - '@types/bun@1.3.11': - resolution: {integrity: sha512-5vPne5QvtpjGpsGYXiFyycfpDF2ECyPcTSsFBMa0fraoxiQyMJ3SmuQIGhzPg2WJuWxVBoxWJ2kClYTcw/4fAg==} + '@types/bun@1.3.13': + resolution: {integrity: sha512-9fqXWk5YIHGGnUau9TEi+qdlTYDAnOj+xLCmSTwXfAIqXr2x4tytJb43E9uCvt09zJURKXwAtkoH4nLQfzeTXw==} '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -3355,8 +3293,8 @@ packages: '@types/node@18.19.130': resolution: {integrity: sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==} - '@types/node@25.5.2': - resolution: {integrity: sha512-tO4ZIRKNC+MDWV4qKVZe3Ql/woTnmHDr5JD8UI5hn2pwBrHEwOEMZK7WlNb5RKB6EoJ02gwmQS9OrjuFnZYdpg==} + '@types/node@25.6.0': + resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==} '@types/react-dom@19.2.3': resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} @@ -3366,9 +3304,6 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - '@types/semver@7.7.1': - resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - '@types/ssh2-streams@0.1.13': resolution: {integrity: sha512-faHyY3brO9oLEA0QlcO8N2wT7R0+1sHWZvQ+y3rMLwdY1ZyS1z0W3t65j9PqT4HmQ6ALzNe7RZlNuCNE0wBSWA==} @@ -3387,43 +3322,51 @@ packages: '@types/yauzl@2.10.3': resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260408.1': - resolution: {integrity: sha512-YcPczNLfPDB13eUBYHkTOkL7HyWqqqEhho4eSxhAvigZuxvtHQ1uyILIvLVAwipEVzhJ8QciKmLdLucpfi4XyA==} + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260427.1': + resolution: {integrity: sha512-8zxaaEgIpHSadCoCAvUsp0C6WDH0dUXix7Mm7IBjh+EhSxI2clhXwPZTqgtDqbowXHeE82BG5mBbQx+CXDwGOQ==} + engines: {node: '>=16.20.0'} cpu: [arm64] os: [darwin] - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260408.1': - resolution: {integrity: sha512-cHqkDg53xxxz21MThLBf4vx1kyIpRPEYNdEiQlvu9O35Tth49+aub6F+/YEMd9MG4TYZmxh1bEjkjErTUIElpA==} + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260427.1': + resolution: {integrity: sha512-6MjekGfajPtny/bBoBYJ+8dTOlgw6nhSSgJ3Us4R/4L8R90ll803Krz+iz907r1SnYeK5eWubDMV/p1ryLNXkQ==} + engines: {node: '>=16.20.0'} cpu: [x64] os: [darwin] - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260408.1': - resolution: {integrity: sha512-iHG0FEXq/QFsn+qlTPllxdcbvfQ9aRYggy4lc1z0+f11Nyk4YDNCSiR8WW7pbnOTx/VreGbbXhlpuJXTidqL8g==} + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260427.1': + resolution: {integrity: sha512-a1yG/vrLaN3dORvaMuNqXz5jcTaTEPBfhmq77vzqRn8As7EdqxtizPosfxB9K1s7PEB8NeGQKqHEQroPUCsPFg==} + engines: {node: '>=16.20.0'} cpu: [arm64] os: [linux] - '@typescript/native-preview-linux-arm@7.0.0-dev.20260408.1': - resolution: {integrity: sha512-w26Gv9yq9LIYIhxjkQC+i0wBPDdQdX+H06ZhyVRL5grKWTIsk9Xwjp9mDRB/dGlXBKcvnM25JH16OyAA0rFH3A==} + '@typescript/native-preview-linux-arm@7.0.0-dev.20260427.1': + resolution: {integrity: sha512-3bhv/NxU9FHIN3MSmoplIAkIHF62mlF9l5XooAFawwj8yscvPZih/m5fkYIiP5qGri3828XwGyT1Cksaft6FWQ==} + engines: {node: '>=16.20.0'} cpu: [arm] os: [linux] - '@typescript/native-preview-linux-x64@7.0.0-dev.20260408.1': - resolution: {integrity: sha512-hMcUlUIzYbvbdq6j/B4RPL+kZR917NGnE9AgPZ7dJ92yamH/7LGT1Mnlc6McUx31yqTFBFHdTc7Cfx+ynua7Iw==} + '@typescript/native-preview-linux-x64@7.0.0-dev.20260427.1': + resolution: {integrity: sha512-lqaA9oF9ZSw1jn87+Ncxo0Sf0d65eVXMjAD0z44ne7QKFRgWd+QpvK4AXAG4lxnFR+XdndWlVm6O1/tdvcG7xQ==} + engines: {node: '>=16.20.0'} cpu: [x64] os: [linux] - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260408.1': - resolution: {integrity: sha512-avJWIEKSx4rdBLZD1FOOTuxTU51dQfYb3jZvZMaXD4thJjq+6eSwfzu2elwL36AZDlnaxggGjB5nBxp0t54iOA==} + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260427.1': + resolution: {integrity: sha512-ZGXRDC0WPVK/Ky2fZRhy2EcNmdHg22biVYWcWgOUK5tCbJd/KJs3VXk758gn0UbFHEQAR5d7dsvDucCCjZkWpA==} + engines: {node: '>=16.20.0'} cpu: [arm64] os: [win32] - '@typescript/native-preview-win32-x64@7.0.0-dev.20260408.1': - resolution: {integrity: sha512-gpvEHkF/WoxkA3711c4uWNCZO9WAuwrq49COdNwxgOTzYHnMc1yCj8CpkCUJwU0f/Ydwp2s6/efn6gTMvtckPg==} + '@typescript/native-preview-win32-x64@7.0.0-dev.20260427.1': + resolution: {integrity: sha512-Ut4Hncq1IuSeNIfcPs1s719j8H3ZA+ogsJ53W3s/Wy1UF5BIhu5Hkspdc7TzGgJgYqGJKo/+pr4vsRnbBPdWgQ==} + engines: {node: '>=16.20.0'} cpu: [x64] os: [win32] - '@typescript/native-preview@7.0.0-dev.20260408.1': - resolution: {integrity: sha512-N0MZLEUnAoP/aRVk7MY119LDsESkbtEwIw+YeXi/jjx2XCqf7ni3GxIVsUYtf/troyuSedq3V/OUrkoCh5A9gA==} + '@typescript/native-preview@7.0.0-dev.20260427.1': + resolution: {integrity: sha512-g6L7hed1Y2OGwAzZ+vXoGSvtJUdWUtTqtsn/16+UjYbu3+6pol0cggdWj26SFxI41R+jLfnT2+JGtoXRBdH+RQ==} + engines: {node: '>=16.20.0'} hasBin: true '@ungap/structured-clone@1.3.0': @@ -3442,44 +3385,39 @@ packages: babel-plugin-react-compiler: optional: true - '@vitest/coverage-istanbul@4.1.0': - resolution: {integrity: sha512-0+67gA94YToxd+Pc3XgIA/2c8HN2hXNSg3T+1FI4HW7W/2gPitYCtktsY6Ke7vrt5caboMq3TUf0/vwbHRb0og==} - peerDependencies: - vitest: 4.1.0 - - '@vitest/expect@4.1.0': - resolution: {integrity: sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==} + '@vitest/expect@4.1.4': + resolution: {integrity: sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==} - '@vitest/mocker@4.1.0': - resolution: {integrity: sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==} + '@vitest/mocker@4.1.4': + resolution: {integrity: sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==} peerDependencies: msw: ^2.4.9 - vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: msw: optional: true vite: optional: true - '@vitest/pretty-format@4.1.0': - resolution: {integrity: sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==} + '@vitest/pretty-format@4.1.4': + resolution: {integrity: sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==} - '@vitest/runner@4.1.0': - resolution: {integrity: sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==} + '@vitest/runner@4.1.4': + resolution: {integrity: sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==} - '@vitest/snapshot@4.1.0': - resolution: {integrity: sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==} + '@vitest/snapshot@4.1.4': + resolution: {integrity: sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==} - '@vitest/spy@4.1.0': - resolution: {integrity: sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==} + '@vitest/spy@4.1.4': + resolution: {integrity: sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==} - '@vitest/ui@4.1.0': - resolution: {integrity: sha512-sTSDtVM1GOevRGsCNhp1mBUHKo9Qlc55+HCreFT4fe99AHxl1QQNXSL3uj4Pkjh5yEuWZIx8E2tVC94nnBZECQ==} + '@vitest/ui@4.1.4': + resolution: {integrity: sha512-EgFR7nlj5iTDYZYCvavjFokNYwr3c3ry0sFiCg+N7B233Nwp+NNx7eoF/XvMWDCKY71xXAG3kFkt97ZHBJVL8A==} peerDependencies: - vitest: 4.1.0 + vitest: 4.1.4 - '@vitest/utils@4.1.0': - resolution: {integrity: sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==} + '@vitest/utils@4.1.4': + resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} '@vue/compiler-core@3.5.30': resolution: {integrity: sha512-s3DfdZkcu/qExZ+td75015ljzHc6vE+30cFMGRPROYjqkroYI5NV2X1yAMX9UeyBNWB9MxCfPcsjpLS11nzkkw==} @@ -3802,8 +3740,8 @@ packages: resolution: {integrity: sha512-lHblz4ahamxpTmnsk+MNTRWsjYKv965MwOrSJyeD588rR3Jcu7swE+0wN5F+PbL5cjgu/9ObkhfzEPuofEMwLA==} engines: {node: '>=10.0.0'} - bun-types@1.3.11: - resolution: {integrity: sha512-1KGPpoxQWl9f6wcZh57LvrPIInQMn2TQ7jsgxqpRzg+l0QPOFvJVH7HmvHo/AiPgwXy+/Thf6Ov3EdVn1vOabg==} + bun-types@1.3.13: + resolution: {integrity: sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA==} bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} @@ -4097,8 +4035,8 @@ packages: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} engines: {node: '>=12'} - dotenv@17.3.1: - resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} drizzle-kit@0.31.10: @@ -4395,8 +4333,8 @@ packages: flatted@3.4.2: resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} engines: {node: '>=4.0'} peerDependencies: debug: '*' @@ -4479,8 +4417,8 @@ packages: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} - globals@17.4.0: - resolution: {integrity: sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==} + globals@17.5.0: + resolution: {integrity: sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==} engines: {node: '>=18'} goober@2.1.18: @@ -4505,10 +4443,6 @@ packages: crossws: optional: true - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} @@ -4540,9 +4474,6 @@ packages: resolution: {integrity: sha512-am5zfg3yu6sqn5yjKBNqhnTX7Cv+m00ox+7jbaKkrLMRJ4rAdldd1xPd/JzbBWspqaQv6RSTrgFN95EsfhC+7w==} engines: {node: '>=16.9.0'} - html-escaper@2.0.2: - resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} @@ -4670,18 +4601,6 @@ packages: peerDependencies: ws: '*' - istanbul-lib-coverage@3.2.2: - resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} - engines: {node: '>=8'} - - istanbul-lib-report@3.0.1: - resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} - engines: {node: '>=10'} - - istanbul-reports@3.2.0: - resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} - engines: {node: '>=8'} - jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} @@ -4846,13 +4765,6 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - magicast@0.5.2: - resolution: {integrity: sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ==} - - make-dir@4.0.0: - resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} - engines: {node: '>=10'} - math-intrinsics@1.1.0: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} @@ -4894,13 +4806,8 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} - miniflare@4.20260317.3: - resolution: {integrity: sha512-tK78D3X4q30/SXqVwMhWrUfH+ffRou9dJLC+jkhNy5zh1I7i7T4JH6xihOvYxdCSBavJ5fQXaaxDJz6orh09BA==} - engines: {node: '>=18.0.0'} - hasBin: true - - miniflare@4.20260329.0: - resolution: {integrity: sha512-+G+1YFVeuEpw/gZZmUHQR7IfzJV+DDGvnSl0yXzhgvHh8Nbr8Go5uiWIwl17EyZ1Uors3FKUMDUyU6+ejeKZOw==} + miniflare@4.20260420.0: + resolution: {integrity: sha512-w8s3eh2W7EEsFh2uGdddZLkbTwiPI8MCSMXKtuLSA9btW8xmQsVVSkrFuLXFyTKcX0QkstS5dhcWjQPQRJ2WKg==} engines: {node: '>=18.0.0'} hasBin: true @@ -5016,94 +4923,94 @@ packages: node-releases@2.0.36: resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} - node@runtime:24.14.0: + node@runtime:24.15.0: resolution: type: variations variants: - resolution: archive: tarball bin: bin/node - integrity: sha256-J6xI+Ux+iPSwxdkHfw/sECViicfcoh4Nn4LLqtE9bj0= + integrity: sha256-3UvHfctfTJoslkNzm7WTUAzLCUE+xCyqD47w5e8RYJU= type: binary - url: https://nodejs.org/download/release/v24.14.0/node-v24.14.0-aix-ppc64.tar.gz + url: https://nodejs.org/download/release/v24.15.0/node-v24.15.0-aix-ppc64.tar.gz targets: - cpu: ppc64 os: aix - resolution: archive: tarball bin: bin/node - integrity: sha256-oaVPRqdQ0lI9Yo2SSqthdYpRydrT4COL6xQUG+lhXdM= + integrity: sha256-NyMxuWl3mrXRW5SYhPxur4jVr+h73ouogdZAC5EA/8Q= type: binary - url: https://nodejs.org/download/release/v24.14.0/node-v24.14.0-darwin-arm64.tar.gz + url: https://nodejs.org/download/release/v24.15.0/node-v24.15.0-darwin-arm64.tar.gz targets: - cpu: arm64 os: darwin - resolution: archive: tarball bin: bin/node - integrity: sha256-8oeeuBDiWZOgV45dh4kwJm/S6vz/6fKDmz2Ns1TUh54= + integrity: sha256-/9XuKTRnkn8+5zGlU+uI/R9Iz3TuvC10prq+SvIoZzs= type: binary - url: https://nodejs.org/download/release/v24.14.0/node-v24.14.0-darwin-x64.tar.gz + url: https://nodejs.org/download/release/v24.15.0/node-v24.15.0-darwin-x64.tar.gz targets: - cpu: x64 os: darwin - resolution: archive: tarball bin: bin/node - integrity: sha256-9EdAzSGN6BJ/HETEFRCjp0D6XJyNHNzhw77a2nnzzec= + integrity: sha256-c6/CNNVYwkkZh19RwtHqACoq2k6m+DYBo4OGn++mTu0= type: binary - url: https://nodejs.org/download/release/v24.14.0/node-v24.14.0-linux-arm64.tar.gz + url: https://nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-arm64.tar.gz targets: - cpu: arm64 os: linux - resolution: archive: tarball bin: bin/node - integrity: sha256-g7Jj+cLqlGwMShXDyupkcNxJ/gvrbzPf0pqpEoJQY3o= + integrity: sha256-sfiJAKSxY2XqulYmkwT8Edp1gdvwNVLUSV+azh/AX20= type: binary - url: https://nodejs.org/download/release/v24.14.0/node-v24.14.0-linux-ppc64le.tar.gz + url: https://nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-ppc64le.tar.gz targets: - cpu: ppc64le os: linux - resolution: archive: tarball bin: bin/node - integrity: sha256-j6Igofe3dpYFwukp/b9zaCKZe/TPiKPbBRiOq9dxIyg= + integrity: sha256-+YVFVDnVL+m43mqPbQe/7MxzbepSfofqyv5ajXUWo4A= type: binary - url: https://nodejs.org/download/release/v24.14.0/node-v24.14.0-linux-s390x.tar.gz + url: https://nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-s390x.tar.gz targets: - cpu: s390x os: linux - resolution: archive: tarball bin: bin/node - integrity: sha256-2/W4Zl3sFeWeY1mlF/77R7I/25FS2N75dbm8o9/G01U= + integrity: sha256-RINoctmuxJ8ea1KpqSKHLbmisC0jWmFqVoG2qF/sjYk= type: binary - url: https://nodejs.org/download/release/v24.14.0/node-v24.14.0-linux-x64.tar.gz + url: https://nodejs.org/download/release/v24.15.0/node-v24.15.0-linux-x64.tar.gz targets: - cpu: x64 os: linux - resolution: archive: zip bin: node.exe - integrity: sha256-iNNugQlzai+pvcWW8s9QejxSxpzfluVPis1HPsFL6FM= - prefix: node-v24.14.0-win-arm64 + integrity: sha256-yet0Au2ibiun5EtnJ/yFqN5WxQlbH3Hr0wYokiEaoRY= + prefix: node-v24.15.0-win-arm64 type: binary - url: https://nodejs.org/download/release/v24.14.0/node-v24.14.0-win-arm64.zip + url: https://nodejs.org/download/release/v24.15.0/node-v24.15.0-win-arm64.zip targets: - cpu: arm64 os: win32 - resolution: archive: zip bin: node.exe - integrity: sha256-MT+kDA17GFdYId6MsXSDAx/gfZXeWZT29DXzs0X4XGY= - prefix: node-v24.14.0-win-x64 + integrity: sha256-zFFJ6r1Td5zh573FQBZDYi0MfmgAreGJKKdn6UC7DmI= + prefix: node-v24.15.0-win-x64 type: binary - url: https://nodejs.org/download/release/v24.14.0/node-v24.14.0-win-x64.zip + url: https://nodejs.org/download/release/v24.15.0/node-v24.15.0-win-x64.zip targets: - cpu: x64 os: win32 - version: 24.14.0 + version: 24.15.0 hasBin: true normalize-path@3.0.0: @@ -5250,8 +5157,8 @@ packages: pkg-types@2.3.0: resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - postcss@8.5.8: - resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + postcss@8.5.12: + resolution: {integrity: sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==} engines: {node: ^10 || ^12 || >=14} powershell-utils@0.1.0: @@ -5383,8 +5290,8 @@ packages: engines: {node: 20 || >=22} hasBin: true - rolldown@1.0.0-rc.13: - resolution: {integrity: sha512-bvVj8YJmf0rq4pSFmH7laLa6pYrhghv3PRzrCdRAr23g66zOKVJ4wkvFtgohtPLWmthgg8/rkaqRHrpUEh0Zbw==} + rolldown@1.0.0-rc.17: + resolution: {integrity: sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -5429,10 +5336,6 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -5530,8 +5433,8 @@ packages: split-ca@1.0.1: resolution: {integrity: sha512-Q5thBSxp5t8WPTTJQS59LrGqOZqOsrhDGDVm8azCqIBjSBd7nd9o2PM+mDulQQkh8h//4U6hFZnc/mul8t5pWQ==} - srvx@0.11.12: - resolution: {integrity: sha512-AQfrGqntqVPXgP03pvBDN1KyevHC+KmYVqb8vVf4N+aomQqdhaZxjvoVp+AOm4u6x+GgNQY3MVzAUIn+TqwkOA==} + srvx@0.11.15: + resolution: {integrity: sha512-iXsux0UcOjdvs0LCMa2Ws3WwcDUozA3JN3BquNXkaFPP7TpRqgunKdEgoZ/uwb1J6xaYHfxtz9Twlh6yzwM6Tg==} engines: {node: '>=20.16.0'} hasBin: true @@ -5588,10 +5491,6 @@ packages: resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} engines: {node: '>=18'} - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - svg-parser@2.0.4: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} @@ -5664,6 +5563,10 @@ packages: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + tinyrainbow@3.1.0: resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} engines: {node: '>=14.0.0'} @@ -5710,8 +5613,8 @@ packages: typed-query-selector@2.12.1: resolution: {integrity: sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==} - typescript@6.0.2: - resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} engines: {node: '>=14.17'} hasBin: true @@ -5724,17 +5627,17 @@ packages: undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - - undici@7.24.4: - resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==} - engines: {node: '>=20.18.1'} + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} undici@7.24.5: resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==} engines: {node: '>=20.18.1'} + undici@7.24.8: + resolution: {integrity: sha512-6KQ/+QxK49Z/p3HO6E5ZCZWNnCasyZLa5ExaVYyvPxUwKtbCPMKELJOqh7EqOle0t9cH/7d2TaaTRRa6Nhs4YQ==} + engines: {node: '>=20.18.1'} + unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} @@ -5828,8 +5731,8 @@ packages: peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 - vite@8.0.7: - resolution: {integrity: sha512-P1PbweD+2/udplnThz3btF4cf6AgPky7kk23RtHUkJIU5BIxwPprhRGmOAHs6FTI7UiGbTNrgNP6jSYD6JaRnw==} + vite@8.0.10: + resolution: {integrity: sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -5879,21 +5782,23 @@ packages: vite: optional: true - vitest@4.1.0: - resolution: {integrity: sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==} + vitest@4.1.4: + resolution: {integrity: sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: '@edge-runtime/vm': '*' '@opentelemetry/api': ^1.9.0 '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 - '@vitest/browser-playwright': 4.1.0 - '@vitest/browser-preview': 4.1.0 - '@vitest/browser-webdriverio': 4.1.0 - '@vitest/ui': 4.1.0 + '@vitest/browser-playwright': 4.1.4 + '@vitest/browser-preview': 4.1.4 + '@vitest/browser-webdriverio': 4.1.4 + '@vitest/coverage-istanbul': 4.1.4 + '@vitest/coverage-v8': 4.1.4 + '@vitest/ui': 4.1.4 happy-dom: '*' jsdom: '*' - vite: ^6.0.0 || ^7.0.0 || ^8.0.0-0 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 peerDependenciesMeta: '@edge-runtime/vm': optional: true @@ -5907,6 +5812,10 @@ packages: optional: true '@vitest/browser-webdriverio': optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true '@vitest/ui': optional: true happy-dom: @@ -5963,37 +5872,17 @@ packages: engines: {node: '>=8'} hasBin: true - workerd@1.20260317.1: - resolution: {integrity: sha512-ZuEq1OdrJBS+NV+L5HMYPCzVn49a2O60slQiiLpG44jqtlOo+S167fWC76kEXteXLLLydeuRrluRel7WdOUa4g==} - engines: {node: '>=16'} - hasBin: true - - workerd@1.20260329.1: - resolution: {integrity: sha512-+ifMv3uBuD33ee7pan5n8+sgVxm2u5HnbgfXzHKwMNTKw86znqBJSnJoBqtP88+2T5U2Lu11xXUt+khPYioXwQ==} - engines: {node: '>=16'} - hasBin: true - - workerd@1.20260405.1: - resolution: {integrity: sha512-bSaRWCv9iO8/FWpgZRjHLGZLolX5s1AErRSYaTECMMHOZKuCbl2+ehnSyc+ZZ/70y+9owADmN6HoYEWvBlJdYw==} + workerd@1.20260420.1: + resolution: {integrity: sha512-1AOJgng169u4fiFrEd5WjrAGpdwd3A4ZJtP8PMvf+RF9NUKy+mdwrKdz4qPZ6Tt/Bya99vsLn6UX33fjAEVoaA==} engines: {node: '>=16'} hasBin: true - wrangler@4.78.0: - resolution: {integrity: sha512-He/vUhk4ih0D0eFmtNnlbT6Od8j+BEokaSR+oYjbVsH0SWIrIch+eHqfLRSBjBQaOoh6HCNxcafcIkBm2u0Hag==} + wrangler@4.84.0: + resolution: {integrity: sha512-lYScYXeHZ385rDzbTF7QfP4FWu2vQuD7uDQRUjDZuutyq5fZVCR6ZxLLsySbqFiFjvKsF5RoxVPeJtI78blz4w==} engines: {node: '>=20.3.0'} hasBin: true peerDependencies: - '@cloudflare/workers-types': ^4.20260317.1 - peerDependenciesMeta: - '@cloudflare/workers-types': - optional: true - - wrangler@4.79.0: - resolution: {integrity: sha512-NMinIdB1pXIqdk+NLw4+RjzB7K5z4+lWMxhTxFTfZomwJu3Pm6N+kZ+a66D3nI7w0oCjsdv/umrZVmSHCBp2cg==} - engines: {node: '>=20.3.0'} - hasBin: true - peerDependencies: - '@cloudflare/workers-types': ^4.20260329.1 + '@cloudflare/workers-types': ^4.20260420.1 peerDependenciesMeta: '@cloudflare/workers-types': optional: true @@ -6227,7 +6116,7 @@ snapshots: debug: 4.4.3 gensync: 1.0.0-beta.2 json5: 2.2.3 - semver: 6.3.1 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -6245,7 +6134,7 @@ snapshots: '@babel/helper-validator-option': 7.27.1 browserslist: 4.28.1 lru-cache: 5.1.1 - semver: 6.3.1 + semver: 7.7.4 '@babel/helper-globals@7.28.0': {} @@ -6372,7 +6261,7 @@ snapshots: '@cfworker/json-schema@4.1.1': {} - '@cloudflare/containers@0.2.4': {} + '@cloudflare/containers@0.3.3': {} '@cloudflare/kv-asset-handler@0.4.2': {} @@ -6390,111 +6279,56 @@ snapshots: - supports-color - utf-8-validate - '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260317.1)': + '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260420.1)': dependencies: unenv: 2.0.0-rc.24 optionalDependencies: - workerd: 1.20260317.1 + workerd: 1.20260420.1 - '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260329.1)': + '@cloudflare/vite-plugin@1.33.0(patch_hash=67ee821cad9ba5e8e7df586b8e9c9c184176999c7d9cb1b856dc34af6243b198)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260420.1)(wrangler@4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10))': dependencies: + '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260420.1) + miniflare: 4.20260420.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) unenv: 2.0.0-rc.24 - optionalDependencies: - workerd: 1.20260329.1 - - '@cloudflare/unenv-preset@2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260405.1)': - dependencies: - unenv: 2.0.0-rc.24 - optionalDependencies: - workerd: 1.20260405.1 - - '@cloudflare/vite-plugin@1.30.3(patch_hash=e06eefb6e9635561829ac5e497cea44db1c8cfe738d2336f3f1de75842284333)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260329.1)(wrangler@4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10))': - dependencies: - '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260329.1) - miniflare: 4.20260329.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - unenv: 2.0.0-rc.24 - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) - wrangler: 4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + wrangler: 4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) ws: 8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) transitivePeerDependencies: - bufferutil - utf-8-validate - workerd - '@cloudflare/vite-plugin@1.30.3(patch_hash=e06eefb6e9635561829ac5e497cea44db1c8cfe738d2336f3f1de75842284333)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))(workerd@1.20260405.1)(wrangler@4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10))': + '@cloudflare/vitest-pool-workers@0.14.8(@cloudflare/workers-types@4.20260426.1)(@vitest/runner@4.1.4)(@vitest/snapshot@4.1.4)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.4)': dependencies: - '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260405.1) - miniflare: 4.20260329.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - unenv: 2.0.0-rc.24 - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) - wrangler: 4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) - ws: 8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - workerd - - '@cloudflare/vitest-pool-workers@0.13.5(@cloudflare/workers-types@4.20260408.1)(@vitest/runner@4.1.0)(@vitest/snapshot@4.1.0)(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vitest@4.1.0)': - dependencies: - '@vitest/runner': 4.1.0 - '@vitest/snapshot': 4.1.0 + '@vitest/runner': 4.1.4 + '@vitest/snapshot': 4.1.4 cjs-module-lexer: 1.4.3 esbuild: 0.27.3 - miniflare: 4.20260317.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) - vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.1.0)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) - wrangler: 4.78.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) + miniflare: 4.20260420.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/ui@4.1.4)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + wrangler: 4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10) zod: 3.25.76 transitivePeerDependencies: - '@cloudflare/workers-types' - bufferutil - utf-8-validate - '@cloudflare/workerd-darwin-64@1.20260317.1': - optional: true - - '@cloudflare/workerd-darwin-64@1.20260329.1': + '@cloudflare/workerd-darwin-64@1.20260420.1': optional: true - '@cloudflare/workerd-darwin-64@1.20260405.1': + '@cloudflare/workerd-darwin-arm64@1.20260420.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260317.1': + '@cloudflare/workerd-linux-64@1.20260420.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260329.1': + '@cloudflare/workerd-linux-arm64@1.20260420.1': optional: true - '@cloudflare/workerd-darwin-arm64@1.20260405.1': + '@cloudflare/workerd-windows-64@1.20260420.1': optional: true - '@cloudflare/workerd-linux-64@1.20260317.1': - optional: true - - '@cloudflare/workerd-linux-64@1.20260329.1': - optional: true - - '@cloudflare/workerd-linux-64@1.20260405.1': - optional: true - - '@cloudflare/workerd-linux-arm64@1.20260317.1': - optional: true - - '@cloudflare/workerd-linux-arm64@1.20260329.1': - optional: true - - '@cloudflare/workerd-linux-arm64@1.20260405.1': - optional: true - - '@cloudflare/workerd-windows-64@1.20260317.1': - optional: true - - '@cloudflare/workerd-windows-64@1.20260329.1': - optional: true - - '@cloudflare/workerd-windows-64@1.20260405.1': - optional: true - - '@cloudflare/workers-types@4.20260408.1': {} + '@cloudflare/workers-types@4.20260426.1': {} '@cspotcode/source-map-support@0.8.1': dependencies: @@ -6502,9 +6336,14 @@ snapshots: '@drizzle-team/brocli@0.10.2': {} - '@emnapi/core@1.9.1': + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': dependencies: - '@emnapi/wasi-threads': 1.2.0 tslib: 2.8.1 optional: true @@ -6513,7 +6352,7 @@ snapshots: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.2.0': + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true @@ -7052,8 +6891,6 @@ snapshots: dependencies: minipass: 7.1.3 - '@istanbuljs/schema@0.1.3': {} - '@j178/prek@0.3.6': dependencies: axios: 1.13.6 @@ -7090,6 +6927,8 @@ snapshots: '@js-sdsl/ordered-map@4.4.2': {} + '@jsr/std__semver@1.0.8': {} + '@kwsites/file-exists@1.1.1': dependencies: debug: 4.4.3 @@ -7191,10 +7030,10 @@ snapshots: optionalDependencies: '@cfworker/json-schema': 4.1.1 - '@napi-rs/wasm-runtime@1.1.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1)': + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': dependencies: - '@emnapi/core': 1.9.1 - '@emnapi/runtime': 1.9.1 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 '@tybys/wasm-util': 0.10.1 optional: true @@ -7228,7 +7067,7 @@ snapshots: '@opentelemetry/api@1.9.0': {} - '@oxc-project/types@0.123.0': {} + '@oxc-project/types@0.127.0': {} '@pkgjs/parseargs@0.11.0': optional: true @@ -7295,58 +7134,58 @@ snapshots: '@remix-run/session@0.4.1': {} - '@rolldown/binding-android-arm64@1.0.0-rc.13': + '@rolldown/binding-android-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-arm64@1.0.0-rc.13': + '@rolldown/binding-darwin-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-darwin-x64@1.0.0-rc.13': + '@rolldown/binding-darwin-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-freebsd-x64@1.0.0-rc.13': + '@rolldown/binding-freebsd-x64@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.13': + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.13': + '@rolldown/binding-linux-arm64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-arm64-musl@1.0.0-rc.13': + '@rolldown/binding-linux-arm64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.13': + '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.13': + '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-gnu@1.0.0-rc.13': + '@rolldown/binding-linux-x64-gnu@1.0.0-rc.17': optional: true - '@rolldown/binding-linux-x64-musl@1.0.0-rc.13': + '@rolldown/binding-linux-x64-musl@1.0.0-rc.17': optional: true - '@rolldown/binding-openharmony-arm64@1.0.0-rc.13': + '@rolldown/binding-openharmony-arm64@1.0.0-rc.17': optional: true - '@rolldown/binding-wasm32-wasi@1.0.0-rc.13': + '@rolldown/binding-wasm32-wasi@1.0.0-rc.17': dependencies: - '@emnapi/core': 1.9.1 - '@emnapi/runtime': 1.9.1 - '@napi-rs/wasm-runtime': 1.1.3(@emnapi/core@1.9.1)(@emnapi/runtime@1.9.1) + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) optional: true - '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.13': + '@rolldown/binding-win32-arm64-msvc@1.0.0-rc.17': optional: true - '@rolldown/binding-win32-x64-msvc@1.0.0-rc.13': + '@rolldown/binding-win32-x64-msvc@1.0.0-rc.17': optional: true '@rolldown/pluginutils@1.0.0-beta.40': {} - '@rolldown/pluginutils@1.0.0-rc.13': {} + '@rolldown/pluginutils@1.0.0-rc.17': {} '@rolldown/pluginutils@1.0.0-rc.7': {} @@ -7525,12 +7364,12 @@ snapshots: - encoding - supports-color - '@sentry/cloudflare@10.45.0(@cloudflare/workers-types@4.20260408.1)': + '@sentry/cloudflare@10.45.0(@cloudflare/workers-types@4.20260426.1)': dependencies: '@opentelemetry/api': 1.9.0 '@sentry/core': 10.45.0 optionalDependencies: - '@cloudflare/workers-types': 4.20260408.1 + '@cloudflare/workers-types': 4.20260426.1 '@sentry/core@10.45.0': {} @@ -7558,10 +7397,10 @@ snapshots: - rollup - supports-color - '@shazow/whatsabi@0.26.0(@noble/hashes@1.8.0)(typescript@6.0.2)(zod@4.3.6)': + '@shazow/whatsabi@0.26.0(@noble/hashes@1.8.0)(typescript@6.0.3)(zod@4.3.6)': dependencies: '@noble/hashes': 1.8.0 - ox: 0.9.17(typescript@6.0.2)(zod@4.3.6) + ox: 0.9.17(typescript@6.0.3)(zod@4.3.6) transitivePeerDependencies: - typescript - zod @@ -7692,12 +7531,12 @@ snapshots: '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.29.0) '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.29.0) - '@svgr/core@8.1.0(typescript@6.0.2)': + '@svgr/core@8.1.0(typescript@6.0.3)': dependencies: '@babel/core': 7.29.0 '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) camelcase: 6.3.0 - cosmiconfig: 8.3.6(typescript@6.0.2) + cosmiconfig: 8.3.6(typescript@6.0.3) snake-case: 3.0.4 transitivePeerDependencies: - supports-color @@ -7708,11 +7547,11 @@ snapshots: '@babel/types': 7.29.0 entities: 4.5.0 - '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@6.0.2))': + '@svgr/plugin-jsx@8.1.0(@svgr/core@8.1.0(typescript@6.0.3))': dependencies: '@babel/core': 7.29.0 '@svgr/babel-preset': 8.1.0(@babel/core@7.29.0) - '@svgr/core': 8.1.0(typescript@6.0.2) + '@svgr/core': 8.1.0(typescript@6.0.3) '@svgr/hast-util-to-babel-ast': 8.0.0 svg-parser: 2.0.4 transitivePeerDependencies: @@ -7779,12 +7618,12 @@ snapshots: '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 - '@tailwindcss/vite@4.2.2(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': + '@tailwindcss/vite@4.2.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@tailwindcss/node': 4.2.2 '@tailwindcss/oxide': 4.2.2 tailwindcss: 4.2.2 - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) '@takumi-rs/core-darwin-arm64@0.73.1': optional: true @@ -7857,7 +7696,7 @@ snapshots: transitivePeerDependencies: - csstype - '@tanstack/devtools-vite@0.6.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': + '@tanstack/devtools-vite@0.6.0(bufferutil@4.1.0)(utf-8-validate@5.0.10)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@babel/generator': 7.29.1 @@ -7869,7 +7708,7 @@ snapshots: chalk: 5.6.2 launch-editor: 2.13.1 picomatch: 4.0.4 - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - bufferutil - supports-color @@ -7987,19 +7826,19 @@ snapshots: transitivePeerDependencies: - crossws - '@tanstack/react-start@1.167.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': + '@tanstack/react-start@1.167.16(react-dom@19.2.5(react@19.2.5))(react@19.2.5)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@tanstack/react-router': 1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tanstack/react-start-client': 1.166.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tanstack/react-start-server': 1.166.25(react-dom@19.2.5(react@19.2.5))(react@19.2.5) '@tanstack/router-utils': 1.161.6 '@tanstack/start-client-core': 1.167.9 - '@tanstack/start-plugin-core': 1.167.17(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + '@tanstack/start-plugin-core': 1.167.17(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/start-server-core': 1.167.9 pathe: 2.0.3 react: 19.2.5 react-dom: 19.2.5(react@19.2.5) - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - '@rsbuild/core' - crossws @@ -8042,7 +7881,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': + '@tanstack/router-plugin@1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/core': 7.29.0 '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) @@ -8059,7 +7898,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5) - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) transitivePeerDependencies: - supports-color @@ -8091,7 +7930,7 @@ snapshots: '@tanstack/start-fn-stubs@1.161.6': {} - '@tanstack/start-plugin-core@1.167.17(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': + '@tanstack/start-plugin-core@1.167.17(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@babel/code-frame': 7.27.1 '@babel/core': 7.29.0 @@ -8099,7 +7938,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.40 '@tanstack/router-core': 1.168.9 '@tanstack/router-generator': 1.166.24 - '@tanstack/router-plugin': 1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + '@tanstack/router-plugin': 1.167.12(@tanstack/react-router@1.168.10(react-dom@19.2.5(react@19.2.5))(react@19.2.5))(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) '@tanstack/router-utils': 1.161.6 '@tanstack/start-client-core': 1.167.9 '@tanstack/start-server-core': 1.167.9 @@ -8108,11 +7947,11 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.4 source-map: 0.7.6 - srvx: 0.11.12 + srvx: 0.11.15 tinyglobby: 0.2.15 ufo: 1.6.3 - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) - vitefu: 1.1.2(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vitefu: 1.1.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) xmlbuilder2: 4.0.3 zod: 3.25.76 transitivePeerDependencies: @@ -8142,7 +7981,7 @@ snapshots: '@tanstack/virtual-file-routes@1.161.7': {} - '@tempoxyz/lints@https://codeload.github.com/tempoxyz/lints/tar.gz/8af5001866a36967523ece2b8cdc33ba719aad84': + '@tempoxyz/lints@https://codeload.github.com/tempoxyz/lints/tar.gz/03cac25d02c1aaa0c6ca87860183879069abb921': dependencies: '@ast-grep/cli': 0.40.5 commander: 12.1.0 @@ -8160,9 +7999,9 @@ snapshots: '@types/animejs@3.1.13': {} - '@types/bun@1.3.11': + '@types/bun@1.3.13': dependencies: - bun-types: 1.3.11 + bun-types: 1.3.13 '@types/chai@5.2.3': dependencies: @@ -8173,13 +8012,13 @@ snapshots: '@types/docker-modem@3.0.6': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/ssh2': 1.15.5 '@types/dockerode@4.0.1': dependencies: '@types/docker-modem': 3.0.6 - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/ssh2': 1.15.5 '@types/estree@1.0.8': {} @@ -8196,9 +8035,9 @@ snapshots: dependencies: undici-types: 5.26.5 - '@types/node@25.5.2': + '@types/node@25.6.0': dependencies: - undici-types: 7.18.2 + undici-types: 7.19.2 '@types/react-dom@19.2.3(@types/react@19.2.14)': dependencies: @@ -8208,15 +8047,13 @@ snapshots: dependencies: csstype: 3.2.3 - '@types/semver@7.7.1': {} - '@types/ssh2-streams@0.1.13': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/ssh2@0.5.52': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 '@types/ssh2-streams': 0.1.13 '@types/ssh2@1.15.5': @@ -8227,117 +8064,101 @@ snapshots: '@types/ws@8.18.1': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 optional: true '@types/yauzl@2.10.3': dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 optional: true - '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260408.1': + '@typescript/native-preview-darwin-arm64@7.0.0-dev.20260427.1': optional: true - '@typescript/native-preview-darwin-x64@7.0.0-dev.20260408.1': + '@typescript/native-preview-darwin-x64@7.0.0-dev.20260427.1': optional: true - '@typescript/native-preview-linux-arm64@7.0.0-dev.20260408.1': + '@typescript/native-preview-linux-arm64@7.0.0-dev.20260427.1': optional: true - '@typescript/native-preview-linux-arm@7.0.0-dev.20260408.1': + '@typescript/native-preview-linux-arm@7.0.0-dev.20260427.1': optional: true - '@typescript/native-preview-linux-x64@7.0.0-dev.20260408.1': + '@typescript/native-preview-linux-x64@7.0.0-dev.20260427.1': optional: true - '@typescript/native-preview-win32-arm64@7.0.0-dev.20260408.1': + '@typescript/native-preview-win32-arm64@7.0.0-dev.20260427.1': optional: true - '@typescript/native-preview-win32-x64@7.0.0-dev.20260408.1': + '@typescript/native-preview-win32-x64@7.0.0-dev.20260427.1': optional: true - '@typescript/native-preview@7.0.0-dev.20260408.1': + '@typescript/native-preview@7.0.0-dev.20260427.1': optionalDependencies: - '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260408.1 - '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260408.1 - '@typescript/native-preview-linux-arm': 7.0.0-dev.20260408.1 - '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260408.1 - '@typescript/native-preview-linux-x64': 7.0.0-dev.20260408.1 - '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260408.1 - '@typescript/native-preview-win32-x64': 7.0.0-dev.20260408.1 + '@typescript/native-preview-darwin-arm64': 7.0.0-dev.20260427.1 + '@typescript/native-preview-darwin-x64': 7.0.0-dev.20260427.1 + '@typescript/native-preview-linux-arm': 7.0.0-dev.20260427.1 + '@typescript/native-preview-linux-arm64': 7.0.0-dev.20260427.1 + '@typescript/native-preview-linux-x64': 7.0.0-dev.20260427.1 + '@typescript/native-preview-win32-arm64': 7.0.0-dev.20260427.1 + '@typescript/native-preview-win32-x64': 7.0.0-dev.20260427.1 '@ungap/structured-clone@1.3.0': {} - '@vitejs/plugin-react@6.0.1(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitejs/plugin-react@6.0.1(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/coverage-istanbul@4.1.0(vitest@4.1.0)': - dependencies: - '@babel/core': 7.29.0 - '@istanbuljs/schema': 0.1.3 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - istanbul-lib-coverage: 3.2.2 - istanbul-lib-report: 3.0.1 - istanbul-reports: 3.2.0 - magicast: 0.5.2 - obug: 2.1.1 - tinyrainbow: 3.1.0 - vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.1.0)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) - transitivePeerDependencies: - - supports-color - - '@vitest/expect@4.1.0': + '@vitest/expect@4.1.4': dependencies: '@standard-schema/spec': 1.1.0 '@types/chai': 5.2.3 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.0(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': + '@vitest/mocker@4.1.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3))': dependencies: - '@vitest/spy': 4.1.0 + '@vitest/spy': 4.1.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) - '@vitest/pretty-format@4.1.0': + '@vitest/pretty-format@4.1.4': dependencies: tinyrainbow: 3.1.0 - '@vitest/runner@4.1.0': + '@vitest/runner@4.1.4': dependencies: - '@vitest/utils': 4.1.0 + '@vitest/utils': 4.1.4 pathe: 2.0.3 - '@vitest/snapshot@4.1.0': + '@vitest/snapshot@4.1.4': dependencies: - '@vitest/pretty-format': 4.1.0 - '@vitest/utils': 4.1.0 + '@vitest/pretty-format': 4.1.4 + '@vitest/utils': 4.1.4 magic-string: 0.30.21 pathe: 2.0.3 - '@vitest/spy@4.1.0': {} + '@vitest/spy@4.1.4': {} - '@vitest/ui@4.1.0(vitest@4.1.0)': + '@vitest/ui@4.1.4(vitest@4.1.4)': dependencies: - '@vitest/utils': 4.1.0 + '@vitest/utils': 4.1.4 fflate: 0.8.2 flatted: 3.4.2 pathe: 2.0.3 sirv: 3.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vitest: 4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.1.0)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + vitest: 4.1.4(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/ui@4.1.4)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/utils@4.1.0': + '@vitest/utils@4.1.4': dependencies: - '@vitest/pretty-format': 4.1.0 + '@vitest/pretty-format': 4.1.4 convert-source-map: 2.0.0 tinyrainbow: 3.1.0 @@ -8365,7 +8186,7 @@ snapshots: '@vue/shared': 3.5.30 estree-walker: 2.0.2 magic-string: 0.30.21 - postcss: 8.5.8 + postcss: 8.5.12 source-map-js: 1.2.1 optional: true @@ -8378,75 +8199,75 @@ snapshots: '@vue/shared@3.5.30': optional: true - '@wagmi/connectors@8.0.9(@wagmi/core@3.4.8)(accounts@0.8.5)(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))': + '@wagmi/connectors@8.0.9(@wagmi/core@3.4.8)(accounts@0.8.5)(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))': dependencies: - '@wagmi/core': 3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)) - viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + '@wagmi/core': 3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) + viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) optionalDependencies: - accounts: 0.8.5(@types/react@19.2.14)(@wagmi/core@3.4.8)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))(wagmi@3.6.9) - typescript: 6.0.2 + accounts: 0.8.5(@types/react@19.2.14)(@wagmi/core@3.4.8)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))(wagmi@3.6.9) + typescript: 6.0.3 - '@wagmi/core@3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))': + '@wagmi/core@3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))': dependencies: eventemitter3: 5.0.1 - mipd: 0.0.7(typescript@6.0.2) - viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + mipd: 0.0.7(typescript@6.0.3) + viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) zustand: 5.0.0(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.4.0(react@19.2.5)) optionalDependencies: '@tanstack/query-core': 5.96.2 - accounts: 0.8.5(@types/react@19.2.14)(@wagmi/core@3.4.8)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))(wagmi@3.6.9) - typescript: 6.0.2 + accounts: 0.8.5(@types/react@19.2.14)(@wagmi/core@3.4.8)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))(wagmi@3.6.9) + typescript: 6.0.3 transitivePeerDependencies: - '@types/react' - immer - react - use-sync-external-store - '@wagmi/core@3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))': + '@wagmi/core@3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))': dependencies: eventemitter3: 5.0.1 - mipd: 0.0.7(typescript@6.0.2) - viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + mipd: 0.0.7(typescript@6.0.3) + viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) zustand: 5.0.0(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)) optionalDependencies: '@tanstack/query-core': 5.96.2 - accounts: 0.8.5(@types/react@19.2.14)(@wagmi/core@3.4.8)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))(wagmi@3.6.9) - typescript: 6.0.2 + accounts: 0.8.5(@types/react@19.2.14)(@wagmi/core@3.4.8)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))(wagmi@3.6.9) + typescript: 6.0.3 transitivePeerDependencies: - '@types/react' - immer - react - use-sync-external-store - abitype@1.2.3(typescript@6.0.2)(zod@4.3.6): + abitype@1.2.3(typescript@6.0.3)(zod@4.3.6): optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 zod: 4.3.6 - abitype@1.2.4(typescript@6.0.2)(zod@4.3.6): + abitype@1.2.4(typescript@6.0.3)(zod@4.3.6): optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 zod: 4.3.6 abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 - accounts@0.8.5(@types/react@19.2.14)(@wagmi/core@3.4.8)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))(wagmi@3.6.9): + accounts@0.8.5(@types/react@19.2.14)(@wagmi/core@3.4.8)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))(wagmi@3.6.9): dependencies: hono: 4.12.14 idb-keyval: 6.2.2 - mipd: 0.0.7(typescript@6.0.2) - mppx: 0.6.5(hono@4.12.14)(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)) - ox: 0.14.20(typescript@6.0.2)(zod@4.3.6) - webauthx: 0.1.2(typescript@6.0.2)(zod@4.3.6) + mipd: 0.0.7(typescript@6.0.3) + mppx: 0.6.5(hono@4.12.14)(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) + ox: 0.14.20(typescript@6.0.3)(zod@4.3.6) + webauthx: 0.1.2(typescript@6.0.3)(zod@4.3.6) zod: 4.3.6 zustand: 5.0.12(@types/react@19.2.14)(react@19.2.5)(use-sync-external-store@1.6.0(react@19.2.5)) optionalDependencies: - '@wagmi/core': 3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)) + '@wagmi/core': 3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.6.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) react: 19.2.5 - viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) - wagmi: 3.6.9(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)) + viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) + wagmi: 3.6.9(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) transitivePeerDependencies: - '@modelcontextprotocol/sdk' - '@types/react' @@ -8537,7 +8358,7 @@ snapshots: axios@1.13.6: dependencies: - follow-redirects: 1.15.11 + follow-redirects: 1.16.0 form-data: 4.0.5 proxy-from-env: 1.1.0 transitivePeerDependencies: @@ -8657,9 +8478,9 @@ snapshots: buildcheck@0.0.7: optional: true - bun-types@1.3.11: + bun-types@1.3.13: dependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 bundle-name@4.1.0: dependencies: @@ -8804,14 +8625,14 @@ snapshots: core-util-is@1.0.3: {} - cosmiconfig@8.3.6(typescript@6.0.2): + cosmiconfig@8.3.6(typescript@6.0.3): dependencies: import-fresh: 3.3.1 js-yaml: 4.1.1 parse-json: 5.2.0 path-type: 4.0.0 optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 cpu-features@0.0.10: dependencies: @@ -8865,11 +8686,11 @@ snapshots: csstype@3.2.3: {} - cva@1.0.0-beta.4(typescript@6.0.2): + cva@1.0.0-beta.4(typescript@6.0.3): dependencies: clsx: 2.1.1 optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 data-uri-to-buffer@4.0.1: optional: true @@ -8969,7 +8790,7 @@ snapshots: dotenv@16.6.1: {} - dotenv@17.3.1: {} + dotenv@17.4.2: {} drizzle-kit@0.31.10: dependencies: @@ -8978,12 +8799,12 @@ snapshots: esbuild: 0.25.12 tsx: 4.21.0 - drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260408.1)(@libsql/client@0.17.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(@opentelemetry/api@1.9.0)(bun-types@1.3.11)(kysely@0.28.15): + drizzle-orm@0.45.2(@cloudflare/workers-types@4.20260426.1)(@libsql/client@0.17.2(bufferutil@4.1.0)(utf-8-validate@5.0.10))(@opentelemetry/api@1.9.0)(bun-types@1.3.13)(kysely@0.28.15): optionalDependencies: - '@cloudflare/workers-types': 4.20260408.1 + '@cloudflare/workers-types': 4.20260426.1 '@libsql/client': 0.17.2(bufferutil@4.1.0)(utf-8-validate@5.0.10) '@opentelemetry/api': 1.9.0 - bun-types: 1.3.11 + bun-types: 1.3.13 kysely: 0.28.15 dunder-proto@1.0.1: @@ -9293,7 +9114,7 @@ snapshots: flatted@3.4.2: {} - follow-redirects@1.15.11: {} + follow-redirects@1.16.0: {} foreground-child@3.3.1: dependencies: @@ -9386,7 +9207,7 @@ snapshots: minipass: 7.1.3 path-scurry: 2.0.2 - globals@17.4.0: {} + globals@17.5.0: {} goober@2.1.18(csstype@3.2.3): dependencies: @@ -9399,9 +9220,7 @@ snapshots: h3@2.0.1-rc.16: dependencies: rou3: 0.8.1 - srvx: 0.11.12 - - has-flag@4.0.0: {} + srvx: 0.11.15 has-symbols@1.1.0: {} @@ -9437,8 +9256,6 @@ snapshots: hono@4.12.14: {} - html-escaper@2.0.2: {} - html-void-elements@3.0.0: {} htmlparser2@10.1.0: @@ -9458,7 +9275,7 @@ snapshots: http-proxy@1.18.1: dependencies: eventemitter3: 4.0.7 - follow-redirects: 1.15.11 + follow-redirects: 1.16.0 requires-port: 1.0.0 transitivePeerDependencies: - debug @@ -9485,12 +9302,12 @@ snapshots: idb-keyval@6.2.2: {} - idxs@0.0.6(typescript@6.0.2): + idxs@0.0.6(typescript@6.0.3): dependencies: - abitype: 1.2.4(typescript@6.0.2)(zod@4.3.6) + abitype: 1.2.4(typescript@6.0.3)(zod@4.3.6) kysely: 0.28.15 mitt: 3.0.1 - ox: 0.9.17(typescript@6.0.2)(zod@4.3.6) + ox: 0.9.17(typescript@6.0.3)(zod@4.3.6) zod: 4.3.6 transitivePeerDependencies: - typescript @@ -9561,19 +9378,6 @@ snapshots: dependencies: ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) - istanbul-lib-coverage@3.2.2: {} - - istanbul-lib-report@3.0.1: - dependencies: - istanbul-lib-coverage: 3.2.2 - make-dir: 4.0.0 - supports-color: 7.2.0 - - istanbul-reports@3.2.0: - dependencies: - html-escaper: 2.0.2 - istanbul-lib-report: 3.0.1 - jackspeak@3.4.3: dependencies: '@isaacs/cliui': 8.0.2 @@ -9711,16 +9515,6 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 - magicast@0.5.2: - dependencies: - '@babel/parser': 7.29.2 - '@babel/types': 7.29.0 - source-map-js: 1.2.1 - - make-dir@4.0.0: - dependencies: - semver: 7.7.4 - math-intrinsics@1.1.0: {} mdast-util-to-hast@13.2.1: @@ -9766,24 +9560,12 @@ snapshots: dependencies: mime-db: 1.52.0 - miniflare@4.20260317.3(bufferutil@4.1.0)(utf-8-validate@5.0.10): + miniflare@4.20260420.0(bufferutil@4.1.0)(utf-8-validate@5.0.10): dependencies: '@cspotcode/source-map-support': 0.8.1 sharp: 0.34.5 - undici: 7.24.4 - workerd: 1.20260317.1 - ws: 8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) - youch: 4.1.0-beta.10 - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - miniflare@4.20260329.0(bufferutil@4.1.0)(utf-8-validate@5.0.10): - dependencies: - '@cspotcode/source-map-support': 0.8.1 - sharp: 0.34.5 - undici: 7.24.4 - workerd: 1.20260329.1 + undici: 7.24.8 + workerd: 1.20260420.1 ws: 8.18.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) youch: 4.1.0-beta.10 transitivePeerDependencies: @@ -9808,9 +9590,9 @@ snapshots: dependencies: minipass: 7.1.3 - mipd@0.0.7(typescript@6.0.2): + mipd@0.0.7(typescript@6.0.3): optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 mitt@3.0.1: {} @@ -9825,11 +9607,11 @@ snapshots: pkg-types: 1.3.1 ufo: 1.6.3 - mppx@0.6.5(hono@4.12.14)(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)): + mppx@0.6.5(hono@4.12.14)(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)): dependencies: incur: 0.3.25 - ox: 0.14.15(typescript@6.0.2)(zod@4.3.6) - viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + ox: 0.14.15(typescript@6.0.3)(zod@4.3.6) + viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) zod: 4.3.6 optionalDependencies: hono: 4.12.14 @@ -9876,7 +9658,7 @@ snapshots: node-releases@2.0.36: {} - node@runtime:24.14.0: {} + node@runtime:24.15.0: {} normalize-path@3.0.0: {} @@ -9912,7 +9694,7 @@ snapshots: powershell-utils: 0.1.0 wsl-utils: 0.3.1 - ox@0.14.15(typescript@6.0.2)(zod@4.3.6): + ox@0.14.15(typescript@6.0.3)(zod@4.3.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -9920,14 +9702,14 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.4(typescript@6.0.2)(zod@4.3.6) + abitype: 1.2.4(typescript@6.0.3)(zod@4.3.6) eventemitter3: 5.0.1 optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - zod - ox@0.14.20(typescript@6.0.2)(zod@4.3.6): + ox@0.14.20(typescript@6.0.3)(zod@4.3.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -9935,14 +9717,14 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@6.0.2)(zod@4.3.6) + abitype: 1.2.3(typescript@6.0.3)(zod@4.3.6) eventemitter3: 5.0.1 optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - zod - ox@0.9.17(typescript@6.0.2)(zod@4.3.6): + ox@0.9.17(typescript@6.0.3)(zod@4.3.6): dependencies: '@adraffy/ens-normalize': 1.11.1 '@noble/ciphers': 1.3.0 @@ -9950,10 +9732,10 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.4(typescript@6.0.2)(zod@4.3.6) + abitype: 1.2.4(typescript@6.0.3)(zod@4.3.6) eventemitter3: 5.0.1 optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - zod @@ -10055,7 +9837,7 @@ snapshots: exsolve: 1.0.8 pathe: 2.0.3 - postcss@8.5.8: + postcss@8.5.12: dependencies: nanoid: 3.3.11 picocolors: 1.1.1 @@ -10118,7 +9900,7 @@ snapshots: '@protobufjs/path': 1.1.2 '@protobufjs/pool': 1.1.0 '@protobufjs/utf8': 1.1.0 - '@types/node': 25.5.2 + '@types/node': 25.6.0 long: 5.3.2 proxy-agent@6.5.0: @@ -10215,35 +9997,35 @@ snapshots: glob: 13.0.6 package-json-from-dist: 1.0.1 - rolldown@1.0.0-rc.13: + rolldown@1.0.0-rc.17: dependencies: - '@oxc-project/types': 0.123.0 - '@rolldown/pluginutils': 1.0.0-rc.13 + '@oxc-project/types': 0.127.0 + '@rolldown/pluginutils': 1.0.0-rc.17 optionalDependencies: - '@rolldown/binding-android-arm64': 1.0.0-rc.13 - '@rolldown/binding-darwin-arm64': 1.0.0-rc.13 - '@rolldown/binding-darwin-x64': 1.0.0-rc.13 - '@rolldown/binding-freebsd-x64': 1.0.0-rc.13 - '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.13 - '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.13 - '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.13 - '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.13 - '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.13 - '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.13 - '@rolldown/binding-linux-x64-musl': 1.0.0-rc.13 - '@rolldown/binding-openharmony-arm64': 1.0.0-rc.13 - '@rolldown/binding-wasm32-wasi': 1.0.0-rc.13 - '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.13 - '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.13 - - rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.13)(rollup@4.59.0): + '@rolldown/binding-android-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-arm64': 1.0.0-rc.17 + '@rolldown/binding-darwin-x64': 1.0.0-rc.17 + '@rolldown/binding-freebsd-x64': 1.0.0-rc.17 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-arm64-musl': 1.0.0-rc.17 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-s390x-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-gnu': 1.0.0-rc.17 + '@rolldown/binding-linux-x64-musl': 1.0.0-rc.17 + '@rolldown/binding-openharmony-arm64': 1.0.0-rc.17 + '@rolldown/binding-wasm32-wasi': 1.0.0-rc.17 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-rc.17 + '@rolldown/binding-win32-x64-msvc': 1.0.0-rc.17 + + rollup-plugin-visualizer@7.0.1(rolldown@1.0.0-rc.17)(rollup@4.59.0): dependencies: open: 11.0.0 picomatch: 4.0.4 source-map: 0.7.6 yargs: 18.0.0 optionalDependencies: - rolldown: 1.0.0-rc.13 + rolldown: 1.0.0-rc.17 rollup: 4.59.0 rollup@4.59.0: @@ -10291,8 +10073,6 @@ snapshots: scheduler@0.27.0: {} - semver@6.3.1: {} - semver@7.7.4: {} seroval-plugins@1.5.1(seroval@1.5.1): @@ -10411,7 +10191,7 @@ snapshots: split-ca@1.0.1: {} - srvx@0.11.12: {} + srvx@0.11.15: {} ssh-remote-port-forward@1.0.4: dependencies: @@ -10482,10 +10262,6 @@ snapshots: supports-color@10.2.2: {} - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - svg-parser@2.0.4: {} svgo@4.0.1: @@ -10557,12 +10333,12 @@ snapshots: - bare-abort-controller - react-native-b4a - tempo.ts@0.14.2(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6))(zod@4.3.6): + tempo.ts@0.14.2(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6))(zod@4.3.6): dependencies: '@remix-run/fetch-router': 0.17.0 - ox: 0.14.20(typescript@6.0.2)(zod@4.3.6) + ox: 0.14.20(typescript@6.0.3)(zod@4.3.6) optionalDependencies: - viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) transitivePeerDependencies: - typescript - zod @@ -10598,12 +10374,12 @@ snapshots: through@2.3.8: {} - tidx.ts@0.1.1(typescript@6.0.2): + tidx.ts@0.1.1(typescript@6.0.3): dependencies: - abitype: 1.2.4(typescript@6.0.2)(zod@4.3.6) + abitype: 1.2.4(typescript@6.0.3)(zod@4.3.6) kysely: 0.28.15 mitt: 3.0.1 - ox: 0.14.20(typescript@6.0.2)(zod@4.3.6) + ox: 0.14.20(typescript@6.0.3)(zod@4.3.6) zod: 4.3.6 transitivePeerDependencies: - typescript @@ -10619,6 +10395,11 @@ snapshots: fdir: 6.5.0(picomatch@4.0.4) picomatch: 4.0.4 + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + tinyrainbow@3.1.0: {} tmp@0.2.5: {} @@ -10652,7 +10433,7 @@ snapshots: typed-query-selector@2.12.1: {} - typescript@6.0.2: {} + typescript@6.0.3: {} ufo@1.6.3: {} @@ -10663,12 +10444,12 @@ snapshots: undici-types@5.26.5: {} - undici-types@7.18.2: {} - - undici@7.24.4: {} + undici-types@7.19.2: {} undici@7.24.5: {} + undici@7.24.8: {} + unenv@2.0.0-rc.24: dependencies: pathe: 2.0.3 @@ -10698,7 +10479,7 @@ snapshots: unist-util-is: 6.0.1 unist-util-visit-parents: 6.0.2 - unplugin-icons@23.0.1(@svgr/core@8.1.0(typescript@6.0.2))(@vue/compiler-sfc@3.5.30): + unplugin-icons@23.0.1(@svgr/core@8.1.0(typescript@6.0.3))(@vue/compiler-sfc@3.5.30): dependencies: '@antfu/install-pkg': 1.1.0 '@iconify/utils': 3.1.0 @@ -10706,7 +10487,7 @@ snapshots: obug: 2.1.1 unplugin: 2.3.11 optionalDependencies: - '@svgr/core': 8.1.0(typescript@6.0.2) + '@svgr/core': 8.1.0(typescript@6.0.3) '@vue/compiler-sfc': 3.5.30 unplugin@2.3.11: @@ -10751,56 +10532,56 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6): + viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6): dependencies: '@noble/curves': 1.9.1 '@noble/hashes': 1.8.0 '@scure/bip32': 1.7.0 '@scure/bip39': 1.6.0 - abitype: 1.2.3(typescript@6.0.2)(zod@4.3.6) + abitype: 1.2.3(typescript@6.0.3)(zod@4.3.6) isows: 1.0.7(ws@8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10)) - ox: 0.14.20(typescript@6.0.2)(zod@4.3.6) + ox: 0.14.20(typescript@6.0.3)(zod@4.3.6) ws: 8.18.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - bufferutil - utf-8-validate - zod - vite-plugin-devtools-json@1.0.0(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): + vite-plugin-devtools-json@1.0.0(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: uuid: 11.1.0 - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) - vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3): + vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 - postcss: 8.5.8 - rolldown: 1.0.0-rc.13 - tinyglobby: 0.2.15 + postcss: 8.5.12 + rolldown: 1.0.0-rc.17 + tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 25.5.2 + '@types/node': 25.6.0 esbuild: 0.28.0 fsevents: 2.3.3 jiti: 2.6.1 tsx: 4.21.0 yaml: 2.8.3 - vitefu@1.1.2(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): + vitefu@1.1.2(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): optionalDependencies: - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) - vitest@4.1.0(@opentelemetry/api@1.9.0)(@types/node@25.5.2)(@vitest/ui@4.1.0)(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): + vitest@4.1.4(@opentelemetry/api@1.9.0)(@types/node@25.6.0)(@vitest/ui@4.1.4)(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)): dependencies: - '@vitest/expect': 4.1.0 - '@vitest/mocker': 4.1.0(vite@8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) - '@vitest/pretty-format': 4.1.0 - '@vitest/runner': 4.1.0 - '@vitest/snapshot': 4.1.0 - '@vitest/spy': 4.1.0 - '@vitest/utils': 4.1.0 + '@vitest/expect': 4.1.4 + '@vitest/mocker': 4.1.4(vite@8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3)) + '@vitest/pretty-format': 4.1.4 + '@vitest/runner': 4.1.4 + '@vitest/snapshot': 4.1.4 + '@vitest/spy': 4.1.4 + '@vitest/utils': 4.1.4 es-module-lexer: 2.0.0 expect-type: 1.3.0 magic-string: 0.30.21 @@ -10812,25 +10593,25 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 8.0.7(@types/node@25.5.2)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) + vite: 8.0.10(@types/node@25.6.0)(esbuild@0.28.0)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 - '@types/node': 25.5.2 - '@vitest/ui': 4.1.0(vitest@4.1.0) + '@types/node': 25.6.0 + '@vitest/ui': 4.1.4(vitest@4.1.4) transitivePeerDependencies: - msw - wagmi@3.6.9(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)): + wagmi@3.6.9(@tanstack/query-core@5.96.2)(@tanstack/react-query@5.96.2(react@19.2.5))(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)): dependencies: '@tanstack/react-query': 5.96.2(react@19.2.5) - '@wagmi/connectors': 8.0.9(@wagmi/core@3.4.8)(accounts@0.8.5)(typescript@6.0.2)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)) - '@wagmi/core': 3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.2)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6)) + '@wagmi/connectors': 8.0.9(@wagmi/core@3.4.8)(accounts@0.8.5)(typescript@6.0.3)(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) + '@wagmi/core': 3.4.8(@tanstack/query-core@5.96.2)(@types/react@19.2.14)(accounts@0.8.5)(react@19.2.5)(typescript@6.0.3)(use-sync-external-store@1.4.0(react@19.2.5))(viem@2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6)) react: 19.2.5 use-sync-external-store: 1.4.0(react@19.2.5) - viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.2)(utf-8-validate@5.0.10)(zod@4.3.6) + viem: 2.48.4(bufferutil@4.1.0)(typescript@6.0.3)(utf-8-validate@5.0.10)(zod@4.3.6) optionalDependencies: - typescript: 6.0.2 + typescript: 6.0.3 transitivePeerDependencies: - '@base-org/account' - '@coinbase/wallet-sdk' @@ -10852,9 +10633,9 @@ snapshots: web-streams-polyfill@3.3.3: optional: true - webauthx@0.1.2(typescript@6.0.2)(zod@4.3.6): + webauthx@0.1.2(typescript@6.0.3)(zod@4.3.6): dependencies: - ox: 0.14.20(typescript@6.0.2)(zod@4.3.6) + ox: 0.14.20(typescript@6.0.3)(zod@4.3.6) transitivePeerDependencies: - typescript - zod @@ -10883,60 +10664,26 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 - workerd@1.20260317.1: - optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260317.1 - '@cloudflare/workerd-darwin-arm64': 1.20260317.1 - '@cloudflare/workerd-linux-64': 1.20260317.1 - '@cloudflare/workerd-linux-arm64': 1.20260317.1 - '@cloudflare/workerd-windows-64': 1.20260317.1 - - workerd@1.20260329.1: - optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260329.1 - '@cloudflare/workerd-darwin-arm64': 1.20260329.1 - '@cloudflare/workerd-linux-64': 1.20260329.1 - '@cloudflare/workerd-linux-arm64': 1.20260329.1 - '@cloudflare/workerd-windows-64': 1.20260329.1 - - workerd@1.20260405.1: + workerd@1.20260420.1: optionalDependencies: - '@cloudflare/workerd-darwin-64': 1.20260405.1 - '@cloudflare/workerd-darwin-arm64': 1.20260405.1 - '@cloudflare/workerd-linux-64': 1.20260405.1 - '@cloudflare/workerd-linux-arm64': 1.20260405.1 - '@cloudflare/workerd-windows-64': 1.20260405.1 - optional: true - - wrangler@4.78.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10): - dependencies: - '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260317.1) - blake3-wasm: 2.1.5 - esbuild: 0.27.3 - miniflare: 4.20260317.3(bufferutil@4.1.0)(utf-8-validate@5.0.10) - path-to-regexp: 6.3.0 - unenv: 2.0.0-rc.24 - workerd: 1.20260317.1 - optionalDependencies: - '@cloudflare/workers-types': 4.20260408.1 - fsevents: 2.3.3 - transitivePeerDependencies: - - bufferutil - - utf-8-validate + '@cloudflare/workerd-darwin-64': 1.20260420.1 + '@cloudflare/workerd-darwin-arm64': 1.20260420.1 + '@cloudflare/workerd-linux-64': 1.20260420.1 + '@cloudflare/workerd-linux-arm64': 1.20260420.1 + '@cloudflare/workerd-windows-64': 1.20260420.1 - wrangler@4.79.0(@cloudflare/workers-types@4.20260408.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10): + wrangler@4.84.0(@cloudflare/workers-types@4.20260426.1)(bufferutil@4.1.0)(utf-8-validate@5.0.10): dependencies: '@cloudflare/kv-asset-handler': 0.4.2 - '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260329.1) + '@cloudflare/unenv-preset': 2.16.0(unenv@2.0.0-rc.24)(workerd@1.20260420.1) blake3-wasm: 2.1.5 esbuild: 0.27.3 - miniflare: 4.20260329.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) + miniflare: 4.20260420.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) path-to-regexp: 6.3.0 unenv: 2.0.0-rc.24 - workerd: 1.20260329.1 + workerd: 1.20260420.1 optionalDependencies: - '@cloudflare/workers-types': 4.20260408.1 + '@cloudflare/workers-types': 4.20260426.1 fsevents: 2.3.3 transitivePeerDependencies: - bufferutil diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b27f90a11..3129d7d22 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -6,9 +6,9 @@ packages: catalog: '@biomejs/biome': ^2.4.10 '@cloudflare/puppeteer': ^1.0.6 - '@cloudflare/vite-plugin': 1.30.3 - '@cloudflare/vitest-pool-workers': ^0.13.5 - '@cloudflare/workers-types': ^4.20260408.1 + '@cloudflare/vite-plugin': 1.33.0 + '@cloudflare/vitest-pool-workers': 0.14.8 + '@cloudflare/workers-types': ^4.20260421.1 '@hono/zod-validator': ^0.7.6 '@iconify/json': ^2.2.460 '@logtape/config': ^2.0.4 @@ -39,20 +39,20 @@ catalog: '@tanstack/router-plugin': ^1.167.12 '@total-typescript/ts-reset': ^0.6.1 '@types/animejs': ^3.1.13 - '@types/node': ^25.5.2 + '@types/node': ^25.6.0 '@types/react': ^19.2.14 '@types/react-dom': ^19.2.3 '@vitejs/plugin-react': ^6.0.1 - '@vitest/ui': 4.1.0 + '@vitest/ui': 4.1.4 '@wagmi/core': ^3.4.8 accounts: ~0.8.1 abitype: ^1.2.4 animejs: ^4.3.6 cva: ^1.0.0-beta.4 - dotenv: ^17.3.1 + dotenv: ^17.4.2 eruda: ^3.4.3 esbuild: ^0.28.0 - globals: ^17.4.0 + globals: ^17.5.0 hono: ^4.12.14 hono-rate-limiter: ^0.5.3 idxs: ^0.0.6 @@ -72,14 +72,14 @@ catalog: tidx.ts: ^0.1.1 tw-animate-css: ^1.4.0 typed-query-selector: ^2.12.1 - typescript: ^6.0.2 + typescript: ^6.0.3 unplugin-icons: ^23.0.1 viem: ^2.48.4 - vite: ^8.0.7 + vite: ^8.0.9 vite-plugin-devtools-json: ^1.0.0 - vitest: 4.1.0 + vitest: 4.1.4 wagmi: ^3.6.9 - wrangler: 4.79.0 + wrangler: 4.84.0 zod: ^4.3.6 catalogMode: strict @@ -88,7 +88,6 @@ enablePrePostScripts: true ignoredBuiltDependencies: - '@sentry/cli' - - bufferutil - cbor-extract - cpu-features - protobufjs @@ -96,45 +95,56 @@ ignoredBuiltDependencies: - ssh2 - vue-demi +minimumReleaseAge: 1440 + +minimumReleaseAgeExclude: + - 'ox' + - 'viem' + - 'wagmi' + - 'tidx.ts' + - '@wagmi/core' + - 'vite' + - wrangler + - miniflare + - '@wagmi/connectors' + - '@cloudflare/*' + - '@typescript/*' + nodeOptions: '--disable-warning=ExperimentalWarning --disable-warning=DeprecationWarning' onlyBuiltDependencies: - '@ast-grep/cli' - '@j178/prek' + - bufferutil - '@sentry/cli' - esbuild - simple-git-hooks - utf-8-validate - workerd -shellEmulator: true - -workspaceConcurrency: -1 - -minimumReleaseAge: 1440 -minimumReleaseAgeExclude: - - 'ox' - - 'viem' - - 'wagmi' - - 'tidx.ts' - - '@wagmi/core' - - '@wagmi/connectors' - - '@cloudflare/containers' - overrides: basic-ftp: 5.3.0 flatted: ^3.4.2 - tar: ^7.5.13 + follow-redirects@<=1.15.11: '>=1.16.0' + h3@>=2.0.0-beta.0 <=2.0.1-rc.16: '>=2.0.1-rc.17' + h3@>=2.0.0-beta.4 <2.0.1-rc.18: '>=2.0.1-rc.18' + h3@>=2.0.1-alpha.0 <=2.0.1-rc.16: '>=2.0.1-rc.17' + lodash: ^4.18.1 picomatch@<3: ^2.3.2 picomatch@>=4: ^4.0.4 - lodash: ^4.18.1 - brace-expansion@<3: ^2.0.3 - brace-expansion@>=5: ^5.0.5 - yaml: ^2.8.3 protobufjs: 7.5.5 + protobufjs@<7.5.5: '>=7.5.5' + semver@<7: ^7.7.4 + srvx@<0.11.13: '>=0.11.13' + tar: ^7.5.13 + yaml: ^2.8.3 patchedDependencies: - '@cloudflare/vite-plugin@1.30.3': patches/@cloudflare__vite-plugin@1.30.3.patch + '@cloudflare/vite-plugin@1.33.0': patches/@cloudflare__vite-plugin@1.33.0.patch + +shellEmulator: true strictDepBuilds: true trustPolicy: no-downgrade + +workspaceConcurrency: -1