From 2025bf6dab131756516dc6cdb04cbcb1c76b5394 Mon Sep 17 00:00:00 2001 From: Chris Hunter Date: Thu, 14 May 2026 13:51:23 -0400 Subject: [PATCH 1/7] =?UTF-8?q?feat:=20vibenet=20stack=20=E2=80=94=20fauce?= =?UTF-8?q?t,=20explorer,=20landing=20page=20(Next.js=20port)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ports the vibenet Rust/nginx stack to this Next.js app: - Landing page (/) with chain info, features, contracts - Faucet page + API routes (/faucet, /api/vibenet/faucet/*) using viem for signing, in-memory rate limiting per IP/address - Block explorer pages + API routes (/explorer/*, /api/vibenet/explorer/*) backed by better-sqlite3 (same schema as vibescan Rust crate) - Background indexer script (scripts/indexer.mjs) that backfills and subscribes to newHeads via WebSocket - Subdomain routing via proxy.ts: faucet.vibes.base.org → /faucet, explorer.vibes.base.org → /explorer - TIPS pages moved to /tips to free up root for vibenet landing - Dockerfile updated: self-contained build context, starts both Next.js server and background indexer Co-Authored-By: Claude Sonnet 4.6 (1M context) --- Dockerfile | 14 +- docker/migrations/0001_init.sql | 65 + docker/vibenet-env.example | 12 + next.config.ts | 2 + package-lock.json | 3888 +++++++++++++++++ package.json | 2 + scripts/indexer.mjs | 236 + scripts/start.sh | 9 + src/app/api/vibenet/config/route.ts | 29 + src/app/api/vibenet/contracts/route.ts | 23 + .../vibenet/explorer/address/[addr]/route.ts | 44 + .../vibenet/explorer/block/[hash]/route.ts | 62 + src/app/api/vibenet/explorer/blocks/route.ts | 15 + src/app/api/vibenet/explorer/stats/route.ts | 12 + .../api/vibenet/explorer/tx/[hash]/route.ts | 72 + src/app/api/vibenet/faucet/drip-usdv/route.ts | 68 + src/app/api/vibenet/faucet/drip/route.ts | 53 + src/app/api/vibenet/faucet/status/route.ts | 36 + src/app/explorer/address/[addr]/page.tsx | 114 + src/app/explorer/block/[hash]/page.tsx | 133 + src/app/explorer/page.tsx | 207 + src/app/explorer/tx/[hash]/page.tsx | 163 + src/app/faucet/page.tsx | 226 + src/app/layout.tsx | 4 +- src/app/page.tsx | 983 ++--- src/app/{ => tips}/block/[hash]/page.tsx | 10 +- src/app/{ => tips}/bundles/[hash]/page.tsx | 6 +- src/app/tips/page.tsx | 770 ++++ src/app/{ => tips}/txn/[hash]/page.tsx | 2 +- src/lib/vibenet/db.ts | 115 + src/lib/vibenet/faucet.ts | 133 + src/proxy.ts | 26 +- 32 files changed, 6785 insertions(+), 749 deletions(-) create mode 100644 docker/migrations/0001_init.sql create mode 100644 docker/vibenet-env.example create mode 100644 package-lock.json create mode 100644 scripts/indexer.mjs create mode 100644 scripts/start.sh create mode 100644 src/app/api/vibenet/config/route.ts create mode 100644 src/app/api/vibenet/contracts/route.ts create mode 100644 src/app/api/vibenet/explorer/address/[addr]/route.ts create mode 100644 src/app/api/vibenet/explorer/block/[hash]/route.ts create mode 100644 src/app/api/vibenet/explorer/blocks/route.ts create mode 100644 src/app/api/vibenet/explorer/stats/route.ts create mode 100644 src/app/api/vibenet/explorer/tx/[hash]/route.ts create mode 100644 src/app/api/vibenet/faucet/drip-usdv/route.ts create mode 100644 src/app/api/vibenet/faucet/drip/route.ts create mode 100644 src/app/api/vibenet/faucet/status/route.ts create mode 100644 src/app/explorer/address/[addr]/page.tsx create mode 100644 src/app/explorer/block/[hash]/page.tsx create mode 100644 src/app/explorer/page.tsx create mode 100644 src/app/explorer/tx/[hash]/page.tsx create mode 100644 src/app/faucet/page.tsx rename src/app/{ => tips}/block/[hash]/page.tsx (98%) rename src/app/{ => tips}/bundles/[hash]/page.tsx (99%) create mode 100644 src/app/tips/page.tsx rename src/app/{ => tips}/txn/[hash]/page.tsx (97%) create mode 100644 src/lib/vibenet/db.ts create mode 100644 src/lib/vibenet/faucet.ts diff --git a/Dockerfile b/Dockerfile index 9e0457c2..fa806674 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,12 @@ FROM oven/bun:1 AS deps WORKDIR /app -COPY ui/package.json ui/bun.lock ./ +COPY package.json bun.lock ./ RUN bun install --frozen-lockfile FROM oven/bun:1 AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules -COPY ./ui . +COPY . . ENV NEXT_TELEMETRY_DISABLED=1 @@ -21,12 +21,16 @@ ENV NEXT_TELEMETRY_DISABLED=1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs -RUN mkdir .next -RUN chown nextjs:nodejs .next +RUN mkdir -p .next /data +RUN chown nextjs:nodejs .next /data COPY --from=builder /app/public ./public COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder /app/scripts ./scripts +COPY --from=builder /app/docker/migrations ./docker/migrations + +RUN chmod +x scripts/start.sh USER nextjs @@ -35,4 +39,4 @@ EXPOSE 3000 ENV PORT=3000 ENV HOSTNAME="0.0.0.0" -CMD ["node", "server.js"] +CMD ["sh", "scripts/start.sh"] diff --git a/docker/migrations/0001_init.sql b/docker/migrations/0001_init.sql new file mode 100644 index 00000000..9d1a5a56 --- /dev/null +++ b/docker/migrations/0001_init.sql @@ -0,0 +1,65 @@ +-- vibescan indexer schema (ported from crates/vibenet/explorer). +-- Everything derivable from RPC stays in the node; the only persisted state +-- is the cursor + the address-activity join index that plain JSON-RPC cannot +-- serve efficiently. + +CREATE TABLE IF NOT EXISTS cursor ( + id INTEGER PRIMARY KEY CHECK (id = 0), + last_indexed_block INTEGER NOT NULL, + last_indexed_hash TEXT NOT NULL, + updated_at INTEGER NOT NULL +); + +CREATE TABLE IF NOT EXISTS blocks ( + number INTEGER PRIMARY KEY, + hash TEXT NOT NULL UNIQUE, + timestamp INTEGER NOT NULL, + miner TEXT NOT NULL, + tx_count INTEGER NOT NULL, + gas_used INTEGER NOT NULL, + gas_limit INTEGER NOT NULL, + base_fee TEXT +); + +CREATE TABLE IF NOT EXISTS txs ( + hash TEXT PRIMARY KEY, + block_num INTEGER NOT NULL, + tx_index INTEGER NOT NULL, + from_addr TEXT NOT NULL, + to_addr TEXT, + value TEXT NOT NULL, + status INTEGER NOT NULL, + created TEXT +); +CREATE INDEX IF NOT EXISTS idx_txs_block_num ON txs (block_num DESC, tx_index DESC); + +-- address -> activity feed. role values: +-- 0 = sender, 1 = recipient, 2 = creator +-- 3 = erc20/721 log from, 4 = erc20/721 log to +CREATE TABLE IF NOT EXISTS address_activity ( + address TEXT NOT NULL, + block_num INTEGER NOT NULL, + tx_index INTEGER NOT NULL, + log_index INTEGER NOT NULL DEFAULT -1, + tx_hash TEXT NOT NULL, + role INTEGER NOT NULL, + token TEXT, + PRIMARY KEY (address, block_num, tx_index, log_index, role) +); +CREATE INDEX IF NOT EXISTS idx_activity_addr_block + ON address_activity (address, block_num DESC, tx_index DESC, log_index DESC); + +CREATE TABLE IF NOT EXISTS addresses ( + address TEXT PRIMARY KEY +); + +CREATE TABLE IF NOT EXISTS explorer_stats ( + id INTEGER PRIMARY KEY CHECK (id = 0), + blocks INTEGER NOT NULL, + txs INTEGER NOT NULL, + addresses INTEGER NOT NULL +); + +INSERT INTO explorer_stats (id, blocks, txs, addresses) +VALUES (0, 0, 0, 0) +ON CONFLICT(id) DO NOTHING; diff --git a/docker/vibenet-env.example b/docker/vibenet-env.example new file mode 100644 index 00000000..e5ca3149 --- /dev/null +++ b/docker/vibenet-env.example @@ -0,0 +1,12 @@ +# base/ui vibenet env — UI-specific config only. +# Infra secrets (chain IDs, faucet key, etc.) live in base/base etc/vibenet/vibenet-env. +# This file is passed as a second --env-file to docker compose and merges with it. + +# === Explorer === +VIBESCAN_DB_PATH=/data/vibescan.db +VIBESCAN_START_BLOCK=0 +VIBESCAN_BACKFILL_CONCURRENCY=16 + +# === Optional: surfaced in explorer footer === +# VIBESCAN_PUBLIC_RPC_URL=https://rpc.vibes.base.org +# VIBESCAN_PUBLIC_FAUCET_URL=https://faucet.vibes.base.org diff --git a/next.config.ts b/next.config.ts index 68a6c64d..86e90560 100644 --- a/next.config.ts +++ b/next.config.ts @@ -2,6 +2,8 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: "standalone", + // better-sqlite3 is a native module; exclude it from webpack bundling + serverExternalPackages: ["better-sqlite3"], }; export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..4a3c1fe5 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3888 @@ +{ + "name": "ui", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ui", + "version": "0.1.0", + "dependencies": { + "@aws-sdk/client-s3": "3.940.0", + "next": "16.0.7", + "react": "19.2.1", + "react-dom": "19.2.1", + "viem": "2.40.3" + }, + "devDependencies": { + "@biomejs/biome": "2.3.8", + "@tailwindcss/postcss": "4.1.17", + "@types/better-sqlite3": "^7.6.13", + "@types/node": "20.19.25", + "@types/react": "19.2.7", + "@types/react-dom": "19.2.3", + "better-sqlite3": "^12.10.0", + "tailwindcss": "4.1.17", + "typescript": "5.9.3" + } + }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry-npm.cbhq.net/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry-npm.cbhq.net/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@aws-crypto/crc32": { + "version": "5.2.0", + "resolved": "https://registry-npm.cbhq.net/@aws-crypto/crc32/-/crc32-5.2.0.tgz", + "integrity": "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/crc32c": { + "version": "5.2.0", + "resolved": "https://registry-npm.cbhq.net/@aws-crypto/crc32c/-/crc32c-5.2.0.tgz", + "integrity": "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser": { + "version": "5.2.0", + "resolved": "https://registry-npm.cbhq.net/@aws-crypto/sha1-browser/-/sha1-browser-5.2.0.tgz", + "integrity": "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha1-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-browser": { + "version": "5.2.0", + "resolved": "https://registry-npm.cbhq.net/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", + "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-js": "^5.2.0", + "@aws-crypto/supports-web-crypto": "^5.2.0", + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "@aws-sdk/util-locate-window": "^3.0.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-crypto/sha256-js": { + "version": "5.2.0", + "resolved": "https://registry-npm.cbhq.net/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", + "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/util": "^5.2.0", + "@aws-sdk/types": "^3.222.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/@aws-crypto/supports-web-crypto": { + "version": "5.2.0", + "resolved": "https://registry-npm.cbhq.net/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", + "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util": { + "version": "5.2.0", + "resolved": "https://registry-npm.cbhq.net/@aws-crypto/util/-/util-5.2.0.tgz", + "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "^3.222.0", + "@smithy/util-utf8": "^2.0.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { + "version": "2.3.0", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", + "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/util-buffer-from": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@aws-sdk/client-s3": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/client-s3/-/client-s3-3.940.0.tgz", + "integrity": "sha512-Wi4qnBT6shRRMXuuTgjMFTU5mu2KFWisgcigEMPptjPGUtJvBVi4PTGgS64qsLoUk/obqDAyOBOfEtRZ2ddC2w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha1-browser": "5.2.0", + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/credential-provider-node": "3.940.0", + "@aws-sdk/middleware-bucket-endpoint": "3.936.0", + "@aws-sdk/middleware-expect-continue": "3.936.0", + "@aws-sdk/middleware-flexible-checksums": "3.940.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-location-constraint": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-sdk-s3": "3.940.0", + "@aws-sdk/middleware-ssec": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.940.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/signature-v4-multi-region": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.940.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/eventstream-serde-browser": "^4.2.5", + "@smithy/eventstream-serde-config-resolver": "^4.3.5", + "@smithy/eventstream-serde-node": "^4.2.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-blob-browser": "^4.2.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/hash-stream-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/md5-js": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "@smithy/util-waiter": "^4.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/client-sso": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/client-sso/-/client-sso-3.940.0.tgz", + "integrity": "sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.940.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.940.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/core": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/core/-/core-3.940.0.tgz", + "integrity": "sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/xml-builder": "3.930.0", + "@smithy/core": "^3.18.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-env": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/credential-provider-env/-/credential-provider-env-3.940.0.tgz", + "integrity": "sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-http": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/credential-provider-http/-/credential-provider-http-3.940.0.tgz", + "integrity": "sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-stream": "^4.5.6", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-ini": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.940.0.tgz", + "integrity": "sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/credential-provider-env": "3.940.0", + "@aws-sdk/credential-provider-http": "3.940.0", + "@aws-sdk/credential-provider-login": "3.940.0", + "@aws-sdk/credential-provider-process": "3.940.0", + "@aws-sdk/credential-provider-sso": "3.940.0", + "@aws-sdk/credential-provider-web-identity": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-login": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/credential-provider-login/-/credential-provider-login-3.940.0.tgz", + "integrity": "sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-node": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/credential-provider-node/-/credential-provider-node-3.940.0.tgz", + "integrity": "sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/credential-provider-env": "3.940.0", + "@aws-sdk/credential-provider-http": "3.940.0", + "@aws-sdk/credential-provider-ini": "3.940.0", + "@aws-sdk/credential-provider-process": "3.940.0", + "@aws-sdk/credential-provider-sso": "3.940.0", + "@aws-sdk/credential-provider-web-identity": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/credential-provider-imds": "^4.2.5", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-process": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/credential-provider-process/-/credential-provider-process-3.940.0.tgz", + "integrity": "sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-sso": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.940.0.tgz", + "integrity": "sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/client-sso": "3.940.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/token-providers": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/credential-provider-web-identity": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.940.0.tgz", + "integrity": "sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-bucket-endpoint": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.936.0.tgz", + "integrity": "sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-expect-continue": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.936.0.tgz", + "integrity": "sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-flexible-checksums": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.940.0.tgz", + "integrity": "sha512-WdsxDAVj5qaa5ApAP+JbpCOMHFGSmzjs2Y2OBSbWPeR9Ew7t/Okj+kUub94QJPsgzhvU1/cqNejhsw5VxeFKSQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@aws-crypto/crc32c": "5.2.0", + "@aws-crypto/util": "5.2.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/is-array-buffer": "^4.2.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-host-header": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/middleware-host-header/-/middleware-host-header-3.936.0.tgz", + "integrity": "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-location-constraint": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.936.0.tgz", + "integrity": "sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-logger": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/middleware-logger/-/middleware-logger-3.936.0.tgz", + "integrity": "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-recursion-detection": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.936.0.tgz", + "integrity": "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@aws/lambda-invoke-store": "^0.2.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-sdk-s3": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.940.0.tgz", + "integrity": "sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-arn-parser": "3.893.0", + "@smithy/core": "^3.18.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/util-config-provider": "^4.2.0", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-stream": "^4.5.6", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-ssec": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/middleware-ssec/-/middleware-ssec-3.936.0.tgz", + "integrity": "sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/middleware-user-agent": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.940.0.tgz", + "integrity": "sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@smithy/core": "^3.18.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/nested-clients": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/nested-clients/-/nested-clients-3.940.0.tgz", + "integrity": "sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/sha256-browser": "5.2.0", + "@aws-crypto/sha256-js": "5.2.0", + "@aws-sdk/core": "3.940.0", + "@aws-sdk/middleware-host-header": "3.936.0", + "@aws-sdk/middleware-logger": "3.936.0", + "@aws-sdk/middleware-recursion-detection": "3.936.0", + "@aws-sdk/middleware-user-agent": "3.940.0", + "@aws-sdk/region-config-resolver": "3.936.0", + "@aws-sdk/types": "3.936.0", + "@aws-sdk/util-endpoints": "3.936.0", + "@aws-sdk/util-user-agent-browser": "3.936.0", + "@aws-sdk/util-user-agent-node": "3.940.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/core": "^3.18.5", + "@smithy/fetch-http-handler": "^5.3.6", + "@smithy/hash-node": "^4.2.5", + "@smithy/invalid-dependency": "^4.2.5", + "@smithy/middleware-content-length": "^4.2.5", + "@smithy/middleware-endpoint": "^4.3.12", + "@smithy/middleware-retry": "^4.4.12", + "@smithy/middleware-serde": "^4.2.6", + "@smithy/middleware-stack": "^4.2.5", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/node-http-handler": "^4.4.5", + "@smithy/protocol-http": "^5.3.5", + "@smithy/smithy-client": "^4.9.8", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-base64": "^4.3.0", + "@smithy/util-body-length-browser": "^4.2.0", + "@smithy/util-body-length-node": "^4.2.1", + "@smithy/util-defaults-mode-browser": "^4.3.11", + "@smithy/util-defaults-mode-node": "^4.2.14", + "@smithy/util-endpoints": "^3.2.5", + "@smithy/util-middleware": "^4.2.5", + "@smithy/util-retry": "^4.2.5", + "@smithy/util-utf8": "^4.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/region-config-resolver": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/region-config-resolver/-/region-config-resolver-3.936.0.tgz", + "integrity": "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/config-resolver": "^4.4.3", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/signature-v4-multi-region": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.940.0.tgz", + "integrity": "sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-sdk-s3": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/protocol-http": "^5.3.5", + "@smithy/signature-v4": "^5.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/token-providers": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/token-providers/-/token-providers-3.940.0.tgz", + "integrity": "sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/core": "3.940.0", + "@aws-sdk/nested-clients": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/property-provider": "^4.2.5", + "@smithy/shared-ini-file-loader": "^4.4.0", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/types": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/types/-/types-3.936.0.tgz", + "integrity": "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-arn-parser": { + "version": "3.893.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/util-arn-parser/-/util-arn-parser-3.893.0.tgz", + "integrity": "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-endpoints": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/util-endpoints/-/util-endpoints-3.936.0.tgz", + "integrity": "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "@smithy/url-parser": "^4.2.5", + "@smithy/util-endpoints": "^3.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws-sdk/util-locate-window": { + "version": "3.965.5", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/util-locate-window/-/util-locate-window-3.965.5.tgz", + "integrity": "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@aws-sdk/util-user-agent-browser": { + "version": "3.936.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.936.0.tgz", + "integrity": "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/types": "3.936.0", + "@smithy/types": "^4.9.0", + "bowser": "^2.11.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@aws-sdk/util-user-agent-node": { + "version": "3.940.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.940.0.tgz", + "integrity": "sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A==", + "license": "Apache-2.0", + "dependencies": { + "@aws-sdk/middleware-user-agent": "3.940.0", + "@aws-sdk/types": "3.936.0", + "@smithy/node-config-provider": "^4.3.5", + "@smithy/types": "^4.9.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "aws-crt": ">=1.0.0" + }, + "peerDependenciesMeta": { + "aws-crt": { + "optional": true + } + } + }, + "node_modules/@aws-sdk/xml-builder": { + "version": "3.930.0", + "resolved": "https://registry-npm.cbhq.net/@aws-sdk/xml-builder/-/xml-builder-3.930.0.tgz", + "integrity": "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/types": "^4.9.0", + "fast-xml-parser": "5.2.5", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@aws/lambda-invoke-store": { + "version": "0.2.4", + "resolved": "https://registry-npm.cbhq.net/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.4.tgz", + "integrity": "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@biomejs/biome": { + "version": "2.3.8", + "resolved": "https://registry-npm.cbhq.net/@biomejs/biome/-/biome-2.3.8.tgz", + "integrity": "sha512-Qjsgoe6FEBxWAUzwFGFrB+1+M8y/y5kwmg5CHac+GSVOdmOIqsAiXM5QMVGZJ1eCUCLlPZtq4aFAQ0eawEUuUA==", + "dev": true, + "license": "MIT OR Apache-2.0", + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "2.3.8", + "@biomejs/cli-darwin-x64": "2.3.8", + "@biomejs/cli-linux-arm64": "2.3.8", + "@biomejs/cli-linux-arm64-musl": "2.3.8", + "@biomejs/cli-linux-x64": "2.3.8", + "@biomejs/cli-linux-x64-musl": "2.3.8", + "@biomejs/cli-win32-arm64": "2.3.8", + "@biomejs/cli-win32-x64": "2.3.8" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "2.3.8", + "resolved": "https://registry-npm.cbhq.net/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-2.3.8.tgz", + "integrity": "sha512-HM4Zg9CGQ3txTPflxD19n8MFPrmUAjaC7PQdLkugeeC0cQ+PiVrd7i09gaBS/11QKsTDBJhVg85CEIK9f50Qww==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "2.3.8", + "resolved": "https://registry-npm.cbhq.net/@biomejs/cli-darwin-x64/-/cli-darwin-x64-2.3.8.tgz", + "integrity": "sha512-lUDQ03D7y/qEao7RgdjWVGCu+BLYadhKTm40HkpJIi6kn8LSv5PAwRlew/DmwP4YZ9ke9XXoTIQDO1vAnbRZlA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "2.3.8", + "resolved": "https://registry-npm.cbhq.net/@biomejs/cli-linux-arm64/-/cli-linux-arm64-2.3.8.tgz", + "integrity": "sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "2.3.8", + "resolved": "https://registry-npm.cbhq.net/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.3.8.tgz", + "integrity": "sha512-PShR4mM0sjksUMyxbyPNMxoKFPVF48fU8Qe8Sfx6w6F42verbwRLbz+QiKNiDPRJwUoMG1nPM50OBL3aOnTevA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "2.3.8", + "resolved": "https://registry-npm.cbhq.net/@biomejs/cli-linux-x64/-/cli-linux-x64-2.3.8.tgz", + "integrity": "sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "2.3.8", + "resolved": "https://registry-npm.cbhq.net/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-2.3.8.tgz", + "integrity": "sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "2.3.8", + "resolved": "https://registry-npm.cbhq.net/@biomejs/cli-win32-arm64/-/cli-win32-arm64-2.3.8.tgz", + "integrity": "sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "2.3.8", + "resolved": "https://registry-npm.cbhq.net/@biomejs/cli-win32-x64/-/cli-win32-x64-2.3.8.tgz", + "integrity": "sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT OR Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.10.0", + "resolved": "https://registry-npm.cbhq.net/@emnapi/runtime/-/runtime-1.10.0.tgz", + "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry-npm.cbhq.net/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry-npm.cbhq.net/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry-npm.cbhq.net/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry-npm.cbhq.net/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry-npm.cbhq.net/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry-npm.cbhq.net/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "16.0.7", + "resolved": "https://registry-npm.cbhq.net/@next/env/-/env-16.0.7.tgz", + "integrity": "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.0.7", + "resolved": "https://registry-npm.cbhq.net/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.0.7.tgz", + "integrity": "sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.0.7", + "resolved": "https://registry-npm.cbhq.net/@next/swc-darwin-x64/-/swc-darwin-x64-16.0.7.tgz", + "integrity": "sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.0.7", + "resolved": "https://registry-npm.cbhq.net/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.0.7.tgz", + "integrity": "sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.0.7", + "resolved": "https://registry-npm.cbhq.net/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.0.7.tgz", + "integrity": "sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.0.7", + "resolved": "https://registry-npm.cbhq.net/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.0.7.tgz", + "integrity": "sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.0.7", + "resolved": "https://registry-npm.cbhq.net/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.0.7.tgz", + "integrity": "sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.0.7", + "resolved": "https://registry-npm.cbhq.net/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.0.7.tgz", + "integrity": "sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.0.7", + "resolved": "https://registry-npm.cbhq.net/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.0.7.tgz", + "integrity": "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry-npm.cbhq.net/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry-npm.cbhq.net/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry-npm.cbhq.net/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry-npm.cbhq.net/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry-npm.cbhq.net/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry-npm.cbhq.net/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@smithy/config-resolver": { + "version": "4.5.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/config-resolver/-/config-resolver-4.5.2.tgz", + "integrity": "sha512-Gxr1czgeGUKgUJBf3fUSvpb1d+EjEl17f5s8qAzn36QIWmVzNPZQb3C9Rdtfya1yu2qUSAhDGoHUvHI/GJjbBQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/core": { + "version": "3.24.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/core/-/core-3.24.2.tgz", + "integrity": "sha512-IKS7qX59fAGCYBmt5JChcDswQDupZqT2Yn2ZBA3UgTlsjRNNkQzZobbn95xoAAdtTyJmBiJB3Y02qR3rgy3Zog==", + "license": "Apache-2.0", + "dependencies": { + "@aws-crypto/crc32": "5.2.0", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/credential-provider-imds": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/credential-provider-imds/-/credential-provider-imds-4.3.2.tgz", + "integrity": "sha512-iYr9ekBjmZ+FwkiHEopqGscBbl78X62cq3p5Dd0eC+gNd7fybNZFQQdDuOQjTVmFymleuA8YRWZnuXWZ8B3kKA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-browser": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-4.3.2.tgz", + "integrity": "sha512-wq3p8g8pyciXSTmysvOvpGeMQaPssgJqyJdfKquwAgpgRW5cKcXb4c0V/K48Lsn24oYK/cox/vHtj/eaGm0PEg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-config-resolver": { + "version": "4.4.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-4.4.2.tgz", + "integrity": "sha512-/Z4Rn+d/HzU96Ciy2bPDZq2KdlRM18ZhGSiy1lSUZPCpGQxXzGQCIItsk3vMcioBHn8E5t48LEXpTL0rOogrSA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/eventstream-serde-node": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/eventstream-serde-node/-/eventstream-serde-node-4.3.2.tgz", + "integrity": "sha512-D4b+U+tOm9DVB8sk0/iDTVYLtBbid9T4+kX25k3rLie1SNqsaKNPTkx6dTWuB4TGNczKixeTCB4RGiV7KGO4Jw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/fetch-http-handler": { + "version": "5.4.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/fetch-http-handler/-/fetch-http-handler-5.4.2.tgz", + "integrity": "sha512-3wF40g8OOCA5BnwQUvwtzZqYBbWWftDjpAlWIUo6Yld3ZzJaMAKqg7MWQBPjE8oLaqvZQUE7tVGlZPsae6A4bQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-blob-browser": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/hash-blob-browser/-/hash-blob-browser-4.3.2.tgz", + "integrity": "sha512-p/X2kAc11zKkSZYW4bQb2CuWB0rZyXz52UAOtDpMpj8gXlVE7K9NU3sqh3gpTsPFS/2qvMoDS/w1a839k+815w==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-node": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/hash-node/-/hash-node-4.3.2.tgz", + "integrity": "sha512-27ImyEVgDZ2ZQz1rnQMSlw9rm7x3244oZVIFI1WKi3vNtbxQ75XU2jA9BQuH8eb91zBLlyGjWQ/L/QGvmJfEHA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/hash-stream-node": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/hash-stream-node/-/hash-stream-node-4.3.2.tgz", + "integrity": "sha512-eUIHSr4RWqMQVtsnZ4p4tNDFuyCuOFCO3cfkvMHLbTRUIbCAq/7XEeGor7h0o0KYMGLeBztOKLQ0WQyc0j/8gA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/invalid-dependency": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/invalid-dependency/-/invalid-dependency-4.3.2.tgz", + "integrity": "sha512-bP0RYUtgINyMsUEzTnjnwJ/NX9Aa8y7bj0NbqwrMMqT6vjpp7H9SqGuKsn0+wD+Fu4GXcxUex1mH3k0t6WsatQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/is-array-buffer": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/is-array-buffer/-/is-array-buffer-4.3.2.tgz", + "integrity": "sha512-JYNfdKj1kr3ke+Pip+qxiuXW/OuSe8KlCyJz93bU25uKVq6wQGdm6H0/1g2XoFjehJFUmNdS+2/nM6Oabe6/+A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/md5-js": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/md5-js/-/md5-js-4.3.2.tgz", + "integrity": "sha512-i7ES+52XcEuvIYaZrSePh8YHoLjI43hC3HO/KLO2osWEaUdX8kh4YpE6/iyfVud1M1vB3zEGHvwmsmqxDhw/Xw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-content-length": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/middleware-content-length/-/middleware-content-length-4.3.2.tgz", + "integrity": "sha512-Zq4MFXu6Cl/00FKXcc6mkio0xzd6D9Q8+AmtBOC6vHpN6Fz1MButZ0zfL46WxhJH/JgKjv5xl4Qo8HZsr6sDBg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-endpoint": { + "version": "4.5.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/middleware-endpoint/-/middleware-endpoint-4.5.2.tgz", + "integrity": "sha512-1aiE05F2Er+ZYvGfrfI0+JRNeFY4M+hSjUp0SIKyq98k9iC0Lo71XwLbKWcFgFBZuhg5n6i+MQzRVmeCDlWOjw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-retry": { + "version": "4.6.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/middleware-retry/-/middleware-retry-4.6.2.tgz", + "integrity": "sha512-ovt2p0LV3sSRpt8EBajI6imGMz/kq8F73P0eSOq6bhtvcOZM3xODFOjk6Lz2KvKfe7dVlxpNIiOYYyVe8ofv0A==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-serde": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/middleware-serde/-/middleware-serde-4.3.2.tgz", + "integrity": "sha512-hM3b9/JgMjohYHcgh5780ymND7RWGvYuyjNDtQL6P/DawtdbUu5m4oon4FHas+rmjdQ2DHee3cmAetTgU6keJw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/middleware-stack": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/middleware-stack/-/middleware-stack-4.3.2.tgz", + "integrity": "sha512-WthvBnmmksOBbW2I8CRc35QUJn/whgXucpW/hNmE70znXG16/yuC7LGtbDYTr7ak+LNZKzBGmaQR1uDNpeBd1g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-config-provider": { + "version": "4.4.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/node-config-provider/-/node-config-provider-4.4.2.tgz", + "integrity": "sha512-LKup5BaU51fQM1YI/T64SJnn561tUPCfbpStnEygz5u1AqSAOALYY4e8imzz2EuMjcUtaYhOBtMazaN3TRXTgw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/node-http-handler": { + "version": "4.7.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/node-http-handler/-/node-http-handler-4.7.2.tgz", + "integrity": "sha512-EdksTZ8UXYxGUgQ4mpIKrHoaj9WVGsp66TpZuixLAz1Jex8YDLnS4RH9ktGED5aOpN0OJlEtrsC9IGt76go1eA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/property-provider": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/property-provider/-/property-provider-4.3.2.tgz", + "integrity": "sha512-utUCslUrmJxKqSEidPhE1yfBgox78yhZQcHfdU4qvh79Rhi0nNKq6xNG4J/IwPD+Pm9y7a5FdXOgJxmHU5CGoA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/protocol-http": { + "version": "5.4.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/protocol-http/-/protocol-http-5.4.2.tgz", + "integrity": "sha512-rUvFCkbaHglm1zgOJ3QKFcD8jx/e68OUme3n+kAEiefAcGHK46UtmIT0/cuVLuiuSZzTqo8ts0Ju5hy9wqMGZA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/shared-ini-file-loader": { + "version": "4.5.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.5.2.tgz", + "integrity": "sha512-4Cyy2IrFCgZBdw8G5eqB0G5FQ3HwH9FRYMJXGAMdo7hVIguVwQtLvEMmveIBlHf6hKQBd116M9IQRCA/7sxrpg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/signature-v4": { + "version": "5.4.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/signature-v4/-/signature-v4-5.4.2.tgz", + "integrity": "sha512-1km1OjdLRFuITWpCPofjFqzZ+tbeWuB72ZhcYjbjkCxZ21tTPfIs4GUxRrelMyKMLxLghGD58RENnXorU/O8cw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/smithy-client": { + "version": "4.13.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/smithy-client/-/smithy-client-4.13.2.tgz", + "integrity": "sha512-lK+Ssl8FzZHvdiPwB6qWLlPV6ih8FCr2BbRV+6/7QWabtMoiTbMTiGrrKsfKu6fcYzt+5akGAY7Xqna+EySq5g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "@smithy/types": "^4.14.1", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/types": { + "version": "4.14.1", + "resolved": "https://registry-npm.cbhq.net/@smithy/types/-/types-4.14.1.tgz", + "integrity": "sha512-59b5HtSVrVR/eYNei3BUj3DCPKD/G7EtDDe7OEJE7i7FtQFugYo6MxbotS8mVJkLNVf8gYaAlEBwwtJ9HzhWSg==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/url-parser": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/url-parser/-/url-parser-4.3.2.tgz", + "integrity": "sha512-ADEFjhX7Iv+cWrwkK+31tqoUzICzYAjB8GvpGDMoCQ71CA519Qd1ZRg1gDveYe20WQJxwW/ls4F0fSMZZTZnQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-base64": { + "version": "4.4.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-base64/-/util-base64-4.4.2.tgz", + "integrity": "sha512-+dxoGuQMmtP/m3ApPgliWQOTasVP9AHaKWCvczdiJlYk0SmTWrXMKq3lyiooeqMXWLuptcptQPiUYPitGzJjQQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-browser": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-body-length-browser/-/util-body-length-browser-4.3.2.tgz", + "integrity": "sha512-69ETVcLni5dcIneXl7/Kc9Km/MqxOB4rGtr0zhuiVAz6mEr4QLo0P+c9bHZZzqrL5Tizd3fbPOVYpUOW1w4+MQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-body-length-node": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-body-length-node/-/util-body-length-node-4.3.2.tgz", + "integrity": "sha512-P6E2/3h8FZgMadSoUx/xZALw8pwDBQux4W/AYu1TwM/icy/P2G0LXjhLkBclgaR6AJr6xXjaT5p+vivgIQ+wYQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-buffer-from": { + "version": "2.2.0", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", + "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/is-array-buffer": "^2.2.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-buffer-from/node_modules/@smithy/is-array-buffer": { + "version": "2.2.0", + "resolved": "https://registry-npm.cbhq.net/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", + "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@smithy/util-config-provider": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-config-provider/-/util-config-provider-4.3.2.tgz", + "integrity": "sha512-gVuLsMyqePBzTD4BY+VeOxT/o09t+QEwGClHh7yNDL7Wl+XktX6qXmAkAAjAAzOPI8u+ZpoQFq9+ooH2ftKnFw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-browser": { + "version": "4.4.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.4.2.tgz", + "integrity": "sha512-e9TZwwffgUFLgafwzyx4wNnxtgbipHG515xHmurkmjtuyJyCRkAn+Tbd4+iF/fnoCyeJXsZAzPX/OSo2nW9XOQ==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-defaults-mode-node": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.3.2.tgz", + "integrity": "sha512-RsmNY73PM8iO8iRreeXxE3MwY/kRRvBlbJpfm3VKMJc6MQ7vN+LulWj+VrDh+Wf5jCLdQyP43OrTSEH8d/lpCA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-endpoints": { + "version": "3.5.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-endpoints/-/util-endpoints-3.5.2.tgz", + "integrity": "sha512-3qGB71aqXTJnNqNlOh3R9u+9nVbR3qgd7BhtBj1Na/fgD/KAJ/+nkUHt/CGmWyEa+P1Jl361hRO2zwlMvuKJGA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-middleware": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-middleware/-/util-middleware-4.3.2.tgz", + "integrity": "sha512-0GFlqDcWjnJT+TsAj91L4iTPvpR7VySjsALxtGROZaIfR03LLM69nmJDr3V/GjhZfyv/DWlFEnAWyaoKkmby1g==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-retry": { + "version": "4.4.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-retry/-/util-retry-4.4.2.tgz", + "integrity": "sha512-CwOWVLoMWyeHxaFM9a6jpq47yFKx2awaa8oFzQ6e+c5qlIATVc8MX0aN2PGuTgCaTZw//BDgHSjdAFaSbdbJRg==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-stream": { + "version": "4.6.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-stream/-/util-stream-4.6.2.tgz", + "integrity": "sha512-b5kyVsNM3pU36cJGyxwAQajGxs4JkNuaKKNufDu7oASWmzKG5gtXEdVK1rvz99O2JV1B85Pp9bAcN7fXWbN6Aw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-utf8": { + "version": "4.3.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-utf8/-/util-utf8-4.3.2.tgz", + "integrity": "sha512-Bswgu9zDwZRp5HBQKIaW6QrKrbwQ9RuOyAg2adsIjJTHA2SmE0XXBk+mYP82Ay6I3XzkY5lM7K9R0XZDFzJ3tw==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@smithy/util-waiter": { + "version": "4.4.2", + "resolved": "https://registry-npm.cbhq.net/@smithy/util-waiter/-/util-waiter-4.4.2.tgz", + "integrity": "sha512-2lAaVh9CAMVi0yI5nGX8nFeZed2v3CstXqX9sqYcIo2OjABSziqk/o/xekYsNHI4nwLzXeTazcm+eN7DpdSbLA==", + "license": "Apache-2.0", + "dependencies": { + "@smithy/core": "^3.24.2", + "tslib": "^2.6.2" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.15", + "resolved": "https://registry-npm.cbhq.net/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/node/-/node-4.1.17.tgz", + "integrity": "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.17" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide/-/oxide-4.1.17.tgz", + "integrity": "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-arm64": "4.1.17", + "@tailwindcss/oxide-darwin-x64": "4.1.17", + "@tailwindcss/oxide-freebsd-x64": "4.1.17", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", + "@tailwindcss/oxide-linux-x64-musl": "4.1.17", + "@tailwindcss/oxide-wasm32-wasi": "4.1.17", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.17.tgz", + "integrity": "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.17.tgz", + "integrity": "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.17.tgz", + "integrity": "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.17.tgz", + "integrity": "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.17.tgz", + "integrity": "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.17.tgz", + "integrity": "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.17.tgz", + "integrity": "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.17.tgz", + "integrity": "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.17.tgz", + "integrity": "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.17.tgz", + "integrity": "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.6.0", + "@emnapi/runtime": "^1.6.0", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.0.7", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.17.tgz", + "integrity": "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.17.tgz", + "integrity": "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/@tailwindcss/postcss/-/postcss-4.1.17.tgz", + "integrity": "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.17", + "@tailwindcss/oxide": "4.1.17", + "postcss": "^8.4.41", + "tailwindcss": "4.1.17" + } + }, + "node_modules/@types/better-sqlite3": { + "version": "7.6.13", + "resolved": "https://registry-npm.cbhq.net/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", + "integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "20.19.25", + "resolved": "https://registry-npm.cbhq.net/@types/node/-/node-20.19.25.tgz", + "integrity": "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.7", + "resolved": "https://registry-npm.cbhq.net/@types/react/-/react-19.2.7.tgz", + "integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry-npm.cbhq.net/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/abitype": { + "version": "1.1.0", + "resolved": "https://registry-npm.cbhq.net/abitype/-/abitype-1.1.0.tgz", + "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry-npm.cbhq.net/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/better-sqlite3": { + "version": "12.10.0", + "resolved": "https://registry-npm.cbhq.net/better-sqlite3/-/better-sqlite3-12.10.0.tgz", + "integrity": "sha512-CyzaZRQKyHkB2ZInfTTl2nvT33EbDpjkLEbE8/Zck3Ll6O0qqvuGdrJ45HgtH+HykRg88ITY3AdreBGN70aBSQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + }, + "engines": { + "node": "20.x || 22.x || 23.x || 24.x || 25.x || 26.x" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry-npm.cbhq.net/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry-npm.cbhq.net/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bowser": { + "version": "2.14.1", + "resolved": "https://registry-npm.cbhq.net/bowser/-/bowser-2.14.1.tgz", + "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", + "license": "MIT" + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry-npm.cbhq.net/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001792", + "resolved": "https://registry-npm.cbhq.net/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry-npm.cbhq.net/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry-npm.cbhq.net/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry-npm.cbhq.net/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry-npm.cbhq.net/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry-npm.cbhq.net/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry-npm.cbhq.net/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "devOptional": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry-npm.cbhq.net/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.21.3", + "resolved": "https://registry-npm.cbhq.net/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", + "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry-npm.cbhq.net/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry-npm.cbhq.net/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-xml-parser": { + "version": "5.2.5", + "resolved": "https://registry-npm.cbhq.net/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", + "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT", + "dependencies": { + "strnum": "^2.1.0" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry-npm.cbhq.net/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry-npm.cbhq.net/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry-npm.cbhq.net/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT" + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry-npm.cbhq.net/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry-npm.cbhq.net/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry-npm.cbhq.net/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry-npm.cbhq.net/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry-npm.cbhq.net/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry-npm.cbhq.net/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry-npm.cbhq.net/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry-npm.cbhq.net/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry-npm.cbhq.net/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry-npm.cbhq.net/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry-npm.cbhq.net/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.12", + "resolved": "https://registry-npm.cbhq.net/nanoid/-/nanoid-3.3.12.tgz", + "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry-npm.cbhq.net/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.0.7", + "resolved": "https://registry-npm.cbhq.net/next/-/next-16.0.7.tgz", + "integrity": "sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A==", + "deprecated": "This version has a security vulnerability. Please upgrade to a patched version. See https://nextjs.org/blog/security-update-2025-12-11 for more details.", + "license": "MIT", + "dependencies": { + "@next/env": "16.0.7", + "@swc/helpers": "0.5.15", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.0.7", + "@next/swc-darwin-x64": "16.0.7", + "@next/swc-linux-arm64-gnu": "16.0.7", + "@next/swc-linux-arm64-musl": "16.0.7", + "@next/swc-linux-x64-gnu": "16.0.7", + "@next/swc-linux-x64-musl": "16.0.7", + "@next/swc-win32-arm64-msvc": "16.0.7", + "@next/swc-win32-x64-msvc": "16.0.7", + "sharp": "^0.34.4" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.4.31", + "resolved": "https://registry-npm.cbhq.net/postcss/-/postcss-8.4.31.tgz", + "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-abi": { + "version": "3.92.0", + "resolved": "https://registry-npm.cbhq.net/node-abi/-/node-abi-3.92.0.tgz", + "integrity": "sha512-KdHvFWZjEKDf0cakgFjebl371GPsISX2oZHcuyKqM7DtogIsHrqKeLTo8wBHxaXRAQlY2PsPlZmfo+9ZCxEREQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry-npm.cbhq.net/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/ox": { + "version": "0.9.6", + "resolved": "https://registry-npm.cbhq.net/ox/-/ox-0.9.6.tgz", + "integrity": "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.9", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry-npm.cbhq.net/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.14", + "resolved": "https://registry-npm.cbhq.net/postcss/-/postcss-8.5.14.tgz", + "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry-npm.cbhq.net/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry-npm.cbhq.net/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry-npm.cbhq.net/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/react": { + "version": "19.2.1", + "resolved": "https://registry-npm.cbhq.net/react/-/react-19.2.1.tgz", + "integrity": "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.1", + "resolved": "https://registry-npm.cbhq.net/react-dom/-/react-dom-19.2.1.tgz", + "integrity": "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg==", + "license": "MIT", + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.1" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry-npm.cbhq.net/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry-npm.cbhq.net/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry-npm.cbhq.net/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry-npm.cbhq.net/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry-npm.cbhq.net/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry-npm.cbhq.net/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry-npm.cbhq.net/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry-npm.cbhq.net/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry-npm.cbhq.net/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry-npm.cbhq.net/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strnum": { + "version": "2.3.0", + "resolved": "https://registry-npm.cbhq.net/strnum/-/strnum-2.3.0.tgz", + "integrity": "sha512-ums3KNd42PGyx5xaoVTO1mjU1bH3NpY4vsrVlnv9PNGqQj8wd7rJ6nEypLrJ7z5vxK5RP0yMLo6J/Gsm62DI5Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + } + ], + "license": "MIT" + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry-npm.cbhq.net/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tailwindcss": { + "version": "4.1.17", + "resolved": "https://registry-npm.cbhq.net/tailwindcss/-/tailwindcss-4.1.17.tgz", + "integrity": "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry-npm.cbhq.net/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry-npm.cbhq.net/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry-npm.cbhq.net/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry-npm.cbhq.net/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry-npm.cbhq.net/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry-npm.cbhq.net/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry-npm.cbhq.net/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry-npm.cbhq.net/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/viem": { + "version": "2.40.3", + "resolved": "https://registry-npm.cbhq.net/viem/-/viem-2.40.3.tgz", + "integrity": "sha512-feYfEpbgjRkZYQpwcgxqkWzjxHI5LSDAjcGetHHwDRuX9BRQHUdV8ohrCosCYpdEhus/RknD3/bOd4qLYVPPuA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.1.0", + "isows": "1.0.7", + "ox": "0.9.6", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry-npm.cbhq.net/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry-npm.cbhq.net/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json index 54d105ce..e10cc4c0 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "3.940.0", + "better-sqlite3": "^12.10.0", "next": "16.0.7", "react": "19.2.1", "react-dom": "19.2.1", @@ -19,6 +20,7 @@ "devDependencies": { "@biomejs/biome": "2.3.8", "@tailwindcss/postcss": "4.1.17", + "@types/better-sqlite3": "^7.6.13", "@types/node": "20.19.25", "@types/react": "19.2.7", "@types/react-dom": "19.2.3", diff --git a/scripts/indexer.mjs b/scripts/indexer.mjs new file mode 100644 index 00000000..c8a060c6 --- /dev/null +++ b/scripts/indexer.mjs @@ -0,0 +1,236 @@ +#!/usr/bin/env node +// Background block indexer for vibescan. +// Reads env: VIBESCAN_RPC_HTTP_URL, VIBESCAN_RPC_WS_URL, VIBESCAN_CHAIN_ID, +// VIBESCAN_DB_PATH, VIBESCAN_START_BLOCK, VIBESCAN_BACKFILL_CONCURRENCY + +import Database from "better-sqlite3"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { createPublicClient, http, webSocket, defineChain, parseAbiItem } from "viem"; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const DB_PATH = process.env.VIBESCAN_DB_PATH ?? "/data/vibescan.db"; +const RPC_HTTP = process.env.VIBESCAN_RPC_HTTP_URL ?? "http://localhost:8545"; +const RPC_WS = process.env.VIBESCAN_RPC_WS_URL ?? "ws://localhost:8546"; +const CHAIN_ID = Number(process.env.VIBESCAN_CHAIN_ID ?? "84538453"); +const START_BLOCK = BigInt(process.env.VIBESCAN_START_BLOCK ?? "0"); +const CONCURRENCY = Number(process.env.VIBESCAN_BACKFILL_CONCURRENCY ?? "16"); + +const ERC20_TRANSFER = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f00b2d2b3179ef118821c0b55d5"; + +const chain = defineChain({ + id: CHAIN_ID, + name: "vibenet", + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: { default: { http: [RPC_HTTP], webSocket: [RPC_WS] } }, +}); + +const httpClient = createPublicClient({ chain, transport: http(RPC_HTTP) }); + +// --- DB setup --- + +function openDb() { + const db = new Database(DB_PATH); + db.pragma("journal_mode = WAL"); + db.pragma("synchronous = NORMAL"); + const migrationPath = path.join(__dirname, "../docker/migrations/0001_init.sql"); + db.exec(readFileSync(migrationPath, "utf8")); + return db; +} + +const db = openDb(); + +const stmts = { + getCursor: db.prepare("SELECT last_indexed_block, last_indexed_hash FROM cursor WHERE id = 0"), + upsertCursor: db.prepare(` + INSERT INTO cursor (id, last_indexed_block, last_indexed_hash, updated_at) + VALUES (0, ?, ?, ?) + ON CONFLICT(id) DO UPDATE SET + last_indexed_block = excluded.last_indexed_block, + last_indexed_hash = excluded.last_indexed_hash, + updated_at = excluded.updated_at + `), + insertBlock: db.prepare(` + INSERT OR IGNORE INTO blocks (number, hash, timestamp, miner, tx_count, gas_used, gas_limit, base_fee) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `), + insertTx: db.prepare(` + INSERT OR IGNORE INTO txs (hash, block_num, tx_index, from_addr, to_addr, value, status, created) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) + `), + insertActivity: db.prepare(` + INSERT OR IGNORE INTO address_activity (address, block_num, tx_index, log_index, tx_hash, role, token) + VALUES (?, ?, ?, ?, ?, ?, ?) + `), + insertAddress: db.prepare("INSERT OR IGNORE INTO addresses (address) VALUES (?)"), + updateStats: db.prepare(` + UPDATE explorer_stats SET + blocks = (SELECT COUNT(*) FROM blocks), + txs = (SELECT COUNT(*) FROM txs), + addresses = (SELECT COUNT(*) FROM addresses) + WHERE id = 0 + `), + getBlockHash: db.prepare("SELECT hash FROM blocks WHERE number = ?"), + wipeAll: db.prepare("DELETE FROM blocks; DELETE FROM txs; DELETE FROM address_activity; DELETE FROM addresses; DELETE FROM cursor; DELETE FROM explorer_stats; INSERT INTO explorer_stats (id, blocks, txs, addresses) VALUES (0, 0, 0, 0)"), +}; + +// --- Indexing logic --- + +async function processBlock(blockNum) { + const [block, receipts] = await Promise.all([ + httpClient.getBlock({ blockNumber: blockNum, includeTransactions: true }), + httpClient.request({ + method: "eth_getBlockReceipts", + params: [`0x${blockNum.toString(16)}`], + }).catch(() => null), + ]); + + if (!block) return null; + + db.transaction(() => { + stmts.insertBlock.run( + Number(block.number), + block.hash, + Number(block.timestamp), + block.miner.toLowerCase(), + block.transactions.length, + Number(block.gasUsed), + Number(block.gasLimit), + block.baseFeePerGas != null ? `0x${block.baseFeePerGas.toString(16)}` : null, + ); + + for (let i = 0; i < block.transactions.length; i++) { + const tx = block.transactions[i]; + const receipt = receipts?.[i]; + const status = receipt ? (receipt.status === "0x1" ? 1 : 0) : 1; + const to = tx.to ? tx.to.toLowerCase() : null; + const from = tx.from.toLowerCase(); + + stmts.insertTx.run( + tx.hash, Number(block.number), i, from, to, + `0x${tx.value.toString(16)}`, status, + to === null ? (receipt?.contractAddress?.toLowerCase() ?? null) : null, + ); + + // sender + stmts.insertActivity.run(from, Number(block.number), i, -1, tx.hash, 0, null); + stmts.insertAddress.run(from); + + // recipient or creator + if (to) { + stmts.insertActivity.run(to, Number(block.number), i, -1, tx.hash, 1, null); + stmts.insertAddress.run(to); + } else if (receipt?.contractAddress) { + const created = receipt.contractAddress.toLowerCase(); + stmts.insertActivity.run(created, Number(block.number), i, -1, tx.hash, 2, null); + stmts.insertAddress.run(created); + } + + // ERC-20/721 Transfer logs + if (receipt?.logs) { + for (const log of receipt.logs) { + if (log.topics[0] === ERC20_TRANSFER && log.topics.length >= 3) { + const token = log.address.toLowerCase(); + const logIdx = Number(log.logIndex); + const fromLog = `0x${log.topics[1].slice(26)}`; + const toLog = `0x${log.topics[2].slice(26)}`; + stmts.insertActivity.run(fromLog, Number(block.number), i, logIdx, tx.hash, 3, token); + stmts.insertActivity.run(toLog, Number(block.number), i, logIdx, tx.hash, 4, token); + stmts.insertAddress.run(fromLog); + stmts.insertAddress.run(toLog); + } + } + } + } + + stmts.upsertCursor.run(Number(block.number), block.hash, Math.floor(Date.now() / 1000)); + stmts.updateStats.run(); + })(); + + return block; +} + +async function ensureGenesisConsistent() { + const stored = stmts.getBlockHash.get(0); + if (!stored) return; + const upstream = await httpClient.getBlock({ blockNumber: 0n }); + if (!upstream) return; + if (upstream.hash !== stored.hash) { + console.log(`[indexer] genesis hash mismatch — chain reset detected, wiping DB`); + db.exec("DELETE FROM blocks; DELETE FROM txs; DELETE FROM address_activity; DELETE FROM addresses; DELETE FROM cursor; UPDATE explorer_stats SET blocks=0, txs=0, addresses=0 WHERE id=0;"); + } +} + +async function backfill(fromBlock, toBlock) { + console.log(`[indexer] backfilling blocks ${fromBlock}–${toBlock}`); + let i = fromBlock; + while (i <= toBlock) { + const batch = []; + for (let j = 0; j < CONCURRENCY && i + BigInt(j) <= toBlock; j++) { + batch.push(processBlock(i + BigInt(j))); + } + await Promise.all(batch); + i += BigInt(CONCURRENCY); + process.stdout.write(`\r[indexer] backfilled up to ${i - 1n}`); + } + console.log(`\n[indexer] backfill complete`); +} + +async function streamLive() { + console.log(`[indexer] subscribing to newHeads via ${RPC_WS}`); + + const wsClient = createPublicClient({ chain, transport: webSocket(RPC_WS) }); + + const unwatch = wsClient.watchBlocks({ + onBlock: async (block) => { + try { + await processBlock(block.number); + console.log(`[indexer] indexed block #${block.number}`); + } catch (e) { + console.error(`[indexer] error processing block #${block.number}:`, e); + } + }, + onError: (err) => { + console.error(`[indexer] subscription error:`, err); + }, + }); + + // Keep running; signal handler below handles cleanup + process.on("SIGTERM", () => { unwatch(); process.exit(0); }); + process.on("SIGINT", () => { unwatch(); process.exit(0); }); + await new Promise(() => {}); // block forever +} + +async function main() { + console.log(`[indexer] starting (chain=${CHAIN_ID}, rpc=${RPC_HTTP})`); + + // Wait for node to be reachable + for (let attempt = 0; attempt < 30; attempt++) { + try { + await httpClient.getChainId(); + break; + } catch { + console.log(`[indexer] waiting for node… (attempt ${attempt + 1}/30)`); + await new Promise((r) => setTimeout(r, 2000)); + } + } + + await ensureGenesisConsistent(); + + const cursor = stmts.getCursor.get(); + const resumeFrom = cursor + ? BigInt(cursor.last_indexed_block) + 1n + : START_BLOCK; + + const latest = await httpClient.getBlockNumber(); + + if (resumeFrom <= latest) { + await backfill(resumeFrom, latest); + } + + await streamLive(); +} + +main().catch((e) => { console.error("[indexer] fatal:", e); process.exit(1); }); diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 00000000..49430d81 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,9 @@ +#!/bin/sh +set -e + +# Start the background indexer (writes to SQLite, reads from node via RPC) +node scripts/indexer.mjs & +INDEXER_PID=$! + +# Start Next.js server in foreground so Docker tracks its exit code +exec node server.js diff --git a/src/app/api/vibenet/config/route.ts b/src/app/api/vibenet/config/route.ts new file mode 100644 index 00000000..c0e96e65 --- /dev/null +++ b/src/app/api/vibenet/config/route.ts @@ -0,0 +1,29 @@ +import { readFile } from "node:fs/promises"; +import { NextResponse } from "next/server"; + +export const dynamic = "force-dynamic"; + +const CONFIG_PATH = process.env.VIBENET_CONFIG_PATH ?? "/config/config.json"; + +export async function GET() { + try { + const raw = await readFile(CONFIG_PATH, "utf8"); + return new NextResponse(raw, { + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-store", + }, + }); + } catch { + return NextResponse.json( + { + title: "base vibenet", + subtitle: "An ephemeral Base devnet.", + features: [], + branch: "unknown", + commit: "unknown", + }, + { headers: { "Cache-Control": "no-store" } }, + ); + } +} diff --git a/src/app/api/vibenet/contracts/route.ts b/src/app/api/vibenet/contracts/route.ts new file mode 100644 index 00000000..079b5a9a --- /dev/null +++ b/src/app/api/vibenet/contracts/route.ts @@ -0,0 +1,23 @@ +import { readFile } from "node:fs/promises"; +import { NextResponse } from "next/server"; + +export const dynamic = "force-dynamic"; + +const CONTRACTS_PATH = + process.env.VIBENET_CONTRACTS_PATH ?? "/shared/contracts.json"; + +export async function GET() { + try { + const raw = await readFile(CONTRACTS_PATH, "utf8"); + return new NextResponse(raw, { + headers: { + "Content-Type": "application/json", + "Cache-Control": "no-store", + }, + }); + } catch { + // contracts.json is written by vibenet-setup after chain is ready; + // return empty object during the boot window + return NextResponse.json({}, { headers: { "Cache-Control": "no-store" } }); + } +} diff --git a/src/app/api/vibenet/explorer/address/[addr]/route.ts b/src/app/api/vibenet/explorer/address/[addr]/route.ts new file mode 100644 index 00000000..9ebdaf33 --- /dev/null +++ b/src/app/api/vibenet/explorer/address/[addr]/route.ts @@ -0,0 +1,44 @@ +import { NextResponse } from "next/server"; +import { createPublicClient, defineChain, http, isAddress } from "viem"; +import { getAddressActivity } from "@/lib/vibenet/db"; + +const rpcUrl = process.env.VIBESCAN_RPC_HTTP_URL ?? "http://localhost:8545"; +const chainId = Number(process.env.VIBESCAN_CHAIN_ID ?? "84538453"); + +function getClient() { + return createPublicClient({ + chain: defineChain({ + id: chainId, + name: "vibenet", + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: { default: { http: [rpcUrl] } }, + }), + transport: http(rpcUrl), + }); +} + +export async function GET( + _req: Request, + { params }: { params: Promise<{ addr: string }> }, +) { + const { addr } = await params; + if (!isAddress(addr)) { + return NextResponse.json({ error: "Invalid address" }, { status: 400 }); + } + + try { + const client = getClient(); + const [balance, activity] = await Promise.all([ + client.getBalance({ address: addr }), + Promise.resolve(getAddressActivity(addr)), + ]); + + return NextResponse.json({ + address: addr, + balance_wei: `0x${balance.toString(16)}`, + activity, + }); + } catch (e) { + return NextResponse.json({ error: String(e) }, { status: 500 }); + } +} diff --git a/src/app/api/vibenet/explorer/block/[hash]/route.ts b/src/app/api/vibenet/explorer/block/[hash]/route.ts new file mode 100644 index 00000000..d1835726 --- /dev/null +++ b/src/app/api/vibenet/explorer/block/[hash]/route.ts @@ -0,0 +1,62 @@ +import { NextResponse } from "next/server"; +import { createPublicClient, defineChain, http } from "viem"; + +const rpcUrl = process.env.VIBESCAN_RPC_HTTP_URL ?? "http://localhost:8545"; +const chainId = Number(process.env.VIBESCAN_CHAIN_ID ?? "84538453"); + +function getClient() { + return createPublicClient({ + chain: defineChain({ + id: chainId, + name: "vibenet", + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: { default: { http: [rpcUrl] } }, + }), + transport: http(rpcUrl), + }); +} + +export async function GET( + _req: Request, + { params }: { params: Promise<{ hash: string }> }, +) { + const { hash } = await params; + try { + const client = getClient(); + // Accepts block hash or block number + const block = + hash.startsWith("0x") && hash.length === 66 + ? await client.getBlock({ + blockHash: hash as `0x${string}`, + includeTransactions: false, + }) + : await client.getBlock({ + blockNumber: BigInt(hash), + includeTransactions: false, + }); + + if (!block) { + return NextResponse.json({ error: "Block not found" }, { status: 404 }); + } + // Serialize bigints as hex strings (matching go-ethereum RPC format) + return NextResponse.json({ + number: `0x${block.number?.toString(16)}`, + hash: block.hash, + parentHash: block.parentHash, + timestamp: `0x${block.timestamp.toString(16)}`, + miner: block.miner, + gasUsed: `0x${block.gasUsed.toString(16)}`, + gasLimit: `0x${block.gasLimit.toString(16)}`, + baseFeePerGas: block.baseFeePerGas + ? `0x${block.baseFeePerGas.toString(16)}` + : null, + transactions: block.transactions, + }); + } catch (e) { + const msg = String(e); + if (msg.includes("not found") || msg.includes("null")) { + return NextResponse.json({ error: "Block not found" }, { status: 404 }); + } + return NextResponse.json({ error: msg }, { status: 500 }); + } +} diff --git a/src/app/api/vibenet/explorer/blocks/route.ts b/src/app/api/vibenet/explorer/blocks/route.ts new file mode 100644 index 00000000..5cc53c81 --- /dev/null +++ b/src/app/api/vibenet/explorer/blocks/route.ts @@ -0,0 +1,15 @@ +import { NextResponse } from "next/server"; +import { getRecentBlocks, getRecentTxs } from "@/lib/vibenet/db"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + try { + return NextResponse.json({ + blocks: getRecentBlocks(20), + txs: getRecentTxs(20), + }); + } catch { + return NextResponse.json({ blocks: [], txs: [] }); + } +} diff --git a/src/app/api/vibenet/explorer/stats/route.ts b/src/app/api/vibenet/explorer/stats/route.ts new file mode 100644 index 00000000..c00f3f33 --- /dev/null +++ b/src/app/api/vibenet/explorer/stats/route.ts @@ -0,0 +1,12 @@ +import { NextResponse } from "next/server"; +import { getStats } from "@/lib/vibenet/db"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + try { + return NextResponse.json(getStats()); + } catch { + return NextResponse.json({ blocks: 0, txs: 0, addresses: 0 }); + } +} diff --git a/src/app/api/vibenet/explorer/tx/[hash]/route.ts b/src/app/api/vibenet/explorer/tx/[hash]/route.ts new file mode 100644 index 00000000..e21155b7 --- /dev/null +++ b/src/app/api/vibenet/explorer/tx/[hash]/route.ts @@ -0,0 +1,72 @@ +import { NextResponse } from "next/server"; +import { createPublicClient, defineChain, http } from "viem"; + +const rpcUrl = process.env.VIBESCAN_RPC_HTTP_URL ?? "http://localhost:8545"; +const chainId = Number(process.env.VIBESCAN_CHAIN_ID ?? "84538453"); + +function getClient() { + return createPublicClient({ + chain: defineChain({ + id: chainId, + name: "vibenet", + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: { default: { http: [rpcUrl] } }, + }), + transport: http(rpcUrl), + }); +} + +export async function GET( + _req: Request, + { params }: { params: Promise<{ hash: string }> }, +) { + const { hash } = await params; + try { + const client = getClient(); + const [tx, receipt] = await Promise.all([ + client.getTransaction({ hash: hash as `0x${string}` }), + client + .getTransactionReceipt({ hash: hash as `0x${string}` }) + .catch(() => null), + ]); + + if (!tx) { + return NextResponse.json( + { error: "Transaction not found" }, + { status: 404 }, + ); + } + + return NextResponse.json({ + hash: tx.hash, + blockHash: tx.blockHash, + blockNumber: tx.blockNumber ? `0x${tx.blockNumber.toString(16)}` : null, + from: tx.from, + to: tx.to, + value: `0x${tx.value.toString(16)}`, + gas: `0x${tx.gas.toString(16)}`, + gasPrice: tx.gasPrice ? `0x${tx.gasPrice.toString(16)}` : "0x0", + nonce: `0x${tx.nonce.toString(16)}`, + input: tx.input, + transactionIndex: + tx.transactionIndex !== null + ? `0x${tx.transactionIndex.toString(16)}` + : null, + status: + receipt?.status === "success" + ? 1 + : receipt?.status === "reverted" + ? 0 + : undefined, + }); + } catch (e) { + const msg = String(e); + if (msg.includes("not found") || msg.includes("null")) { + return NextResponse.json( + { error: "Transaction not found" }, + { status: 404 }, + ); + } + return NextResponse.json({ error: msg }, { status: 500 }); + } +} diff --git a/src/app/api/vibenet/faucet/drip-usdv/route.ts b/src/app/api/vibenet/faucet/drip-usdv/route.ts new file mode 100644 index 00000000..007298ff --- /dev/null +++ b/src/app/api/vibenet/faucet/drip-usdv/route.ts @@ -0,0 +1,68 @@ +import { NextResponse } from "next/server"; +import { encodeFunctionData, isAddress, parseAbi } from "viem"; +import { + checkCooldown, + clientIp, + getAddrCooldownSecs, + getContractAddress, + getIpCooldownSecs, + getPublicClient, + getUsdvDripUnits, + getWalletClient, + recordDrip, +} from "@/lib/vibenet/faucet"; + +export const dynamic = "force-dynamic"; + +const MINT_ABI = parseAbi([ + "function mint(address to, uint256 amount) external", +]); + +export async function POST(req: Request) { + const body = await req.json().catch(() => ({})); + const { address } = body as { address?: string }; + + if (!address || !isAddress(address)) { + return NextResponse.json({ error: "Invalid address" }, { status: 400 }); + } + + const usdvAddress = await getContractAddress("usdv"); + if (!usdvAddress) { + return NextResponse.json( + { error: "USDV contract not yet deployed. Try again shortly." }, + { status: 503 }, + ); + } + + const ip = clientIp(req); + const cooldownErr = checkCooldown( + "usdv", + ip, + address, + getIpCooldownSecs(), + getAddrCooldownSecs(), + ); + if (cooldownErr) { + return NextResponse.json({ error: cooldownErr }, { status: 429 }); + } + + try { + const wallet = getWalletClient(); + const publicClient = getPublicClient(); + const data = encodeFunctionData({ + abi: MINT_ABI, + functionName: "mint", + args: [address, getUsdvDripUnits()], + }); + const hash = await wallet.sendTransaction({ to: usdvAddress, data }); + await publicClient.waitForTransactionReceipt({ hash }); + recordDrip("usdv", ip, address); + return NextResponse.json({ + tx_hash: hash, + to: address, + usdv_address: usdvAddress, + }); + } catch (e) { + return NextResponse.json({ error: String(e) }, { status: 500 }); + } +} diff --git a/src/app/api/vibenet/faucet/drip/route.ts b/src/app/api/vibenet/faucet/drip/route.ts new file mode 100644 index 00000000..a8973196 --- /dev/null +++ b/src/app/api/vibenet/faucet/drip/route.ts @@ -0,0 +1,53 @@ +import { NextResponse } from "next/server"; +import { isAddress } from "viem"; +import { + checkCooldown, + clientIp, + getAddrCooldownSecs, + getDripWei, + getIpCooldownSecs, + getPublicClient, + getWalletClient, + recordDrip, +} from "@/lib/vibenet/faucet"; + +export const dynamic = "force-dynamic"; + +export async function POST(req: Request) { + const body = await req.json().catch(() => ({})); + const { address } = body as { address?: string }; + + if (!address || !isAddress(address)) { + return NextResponse.json({ error: "Invalid address" }, { status: 400 }); + } + + const ip = clientIp(req); + const cooldownErr = checkCooldown( + "eth", + ip, + address, + getIpCooldownSecs(), + getAddrCooldownSecs(), + ); + if (cooldownErr) { + return NextResponse.json({ error: cooldownErr }, { status: 429 }); + } + + try { + const wallet = getWalletClient(); + const publicClient = getPublicClient(); + const hash = await wallet.sendTransaction({ + to: address, + value: getDripWei(), + }); + await publicClient.waitForTransactionReceipt({ hash }); + recordDrip("eth", ip, address); + return NextResponse.json({ + tx_hash: hash, + amount_wei: getDripWei().toString(), + to: address, + }); + } catch (e) { + return NextResponse.json({ error: String(e) }, { status: 500 }); + } +} diff --git a/src/app/api/vibenet/faucet/status/route.ts b/src/app/api/vibenet/faucet/status/route.ts new file mode 100644 index 00000000..ed6c3156 --- /dev/null +++ b/src/app/api/vibenet/faucet/status/route.ts @@ -0,0 +1,36 @@ +import { NextResponse } from "next/server"; +import { + getAddrCooldownSecs, + getContractAddress, + getDripWei, + getFaucetAddress, + getIpCooldownSecs, + getPublicClient, + getUsdvDripUnits, +} from "@/lib/vibenet/faucet"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + try { + const address = getFaucetAddress(); + const client = getPublicClient(); + const balance = await client.getBalance({ address }); + const usdvAddress = await getContractAddress("usdv"); + + return NextResponse.json({ + address, + chain_id: client.chain.id, + drip_wei: getDripWei().toString(), + balance_wei: balance.toString(), + ip_cooldown_secs: getIpCooldownSecs(), + addr_cooldown_secs: getAddrCooldownSecs(), + ...(usdvAddress && { + usdv_address: usdvAddress, + usdv_drip_units: getUsdvDripUnits().toString(), + }), + }); + } catch (e) { + return NextResponse.json({ error: String(e) }, { status: 500 }); + } +} diff --git a/src/app/explorer/address/[addr]/page.tsx b/src/app/explorer/address/[addr]/page.tsx new file mode 100644 index 00000000..89db1c0b --- /dev/null +++ b/src/app/explorer/address/[addr]/page.tsx @@ -0,0 +1,114 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; + +interface PageProps { + params: Promise<{ addr: string }>; +} + +interface ActivityRow { + tx_hash: string; + block_num: number; + role: number; +} + +const ROLE_LABELS: Record = { + 0: "Sender", + 1: "Recipient", + 2: "Creator", + 3: "Token from", + 4: "Token to", +}; + +export default function ExplorerAddressPage({ params }: PageProps) { + const [addr, setAddr] = useState(""); + const [balance, setBalance] = useState(null); + const [activity, setActivity] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + params.then((p) => setAddr(p.addr)); + }, [params]); + + useEffect(() => { + if (!addr) return; + setLoading(true); + fetch(`/api/vibenet/explorer/address/${addr}`) + .then((r) => r.json()) + .then((data) => { + setBalance(data.balance_wei ?? null); + setActivity(data.activity ?? []); + }) + .catch(() => null) + .finally(() => setLoading(false)); + }, [addr]); + + const ethBalance = balance + ? (Number(BigInt(balance)) / 1e18).toFixed(6) + : null; + + return ( +
+
+ + ← Explorer + + / + + {addr.slice(0, 14)}… + +
+ +
+
+

Address

+

{addr}

+
+ + {ethBalance !== null && ( +
+
Balance
+
+ {ethBalance} ETH +
+
+ )} + +
+

+ Activity ({activity.length}) +

+ {loading ? ( +

Loading…

+ ) : activity.length === 0 ? ( +

No activity found

+ ) : ( +
+ {activity.map((row, i) => ( + + + {ROLE_LABELS[row.role] ?? "—"} + + + {row.tx_hash} + + + #{row.block_num.toLocaleString()} + + + ))} +
+ )} +
+
+
+ ); +} diff --git a/src/app/explorer/block/[hash]/page.tsx b/src/app/explorer/block/[hash]/page.tsx new file mode 100644 index 00000000..446d8946 --- /dev/null +++ b/src/app/explorer/block/[hash]/page.tsx @@ -0,0 +1,133 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; + +interface PageProps { + params: Promise<{ hash: string }>; +} + +interface BlockDetail { + number: string; + hash: string; + parentHash: string; + timestamp: string; + miner: string; + gasUsed: string; + gasLimit: string; + baseFeePerGas: string | null; + transactions: string[]; +} + +export default function ExplorerBlockPage({ params }: PageProps) { + const [hash, setHash] = useState(""); + const [block, setBlock] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + params.then((p) => setHash(p.hash)); + }, [params]); + + useEffect(() => { + if (!hash) return; + setLoading(true); + fetch(`/api/vibenet/explorer/block/${hash}`) + .then(async (r) => { + if (!r.ok) + throw new Error( + r.status === 404 ? "Block not found" : "Failed to fetch block", + ); + return r.json(); + }) + .then(setBlock) + .catch((e) => setError(e.message)) + .finally(() => setLoading(false)); + }, [hash]); + + const num = block ? parseInt(block.number, 16) : null; + const ts = block + ? new Date(parseInt(block.timestamp, 16) * 1000).toLocaleString() + : null; + + return ( +
+
+ + ← Explorer + + / + + Block{" "} + {num !== null ? `#${num.toLocaleString()}` : hash.slice(0, 12) + "…"} + +
+ +
+ {loading &&

Loading…

} + {error &&

{error}

} + {block && ( +
+

+ Block #{num?.toLocaleString()} +

+
+ {[ + ["Hash", block.hash], + ["Number", num?.toLocaleString() ?? "—"], + ["Timestamp", ts ?? "—"], + ["Miner", block.miner], + ["Parent Hash", block.parentHash], + ["Gas Used", parseInt(block.gasUsed, 16).toLocaleString()], + ["Gas Limit", parseInt(block.gasLimit, 16).toLocaleString()], + [ + "Base Fee", + block.baseFeePerGas + ? `${parseInt(block.baseFeePerGas, 16)} wei` + : "—", + ], + ["Transactions", block.transactions.length.toString()], + ].map(([label, value], i) => ( +
+ + {label} + + + {value} + +
+ ))} +
+ + {block.transactions.length > 0 && ( +
+

+ Transactions ({block.transactions.length}) +

+
+ {block.transactions.map((txHash, i) => ( + + + {txHash} + + + ))} +
+
+ )} +
+ )} +
+
+ ); +} diff --git a/src/app/explorer/page.tsx b/src/app/explorer/page.tsx new file mode 100644 index 00000000..6fec7e44 --- /dev/null +++ b/src/app/explorer/page.tsx @@ -0,0 +1,207 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; + +interface BlockRow { + number: number; + hash: string; + timestamp: number; + tx_count: number; + gas_used: number; +} + +interface TxRow { + hash: string; + block_num: number; + from_addr: string; + to_addr: string | null; + value: string; + status: number; +} + +interface Stats { + blocks: number; + txs: number; + addresses: number; +} + +function timeAgo(ts: number): string { + const s = Math.floor(Date.now() / 1000 - ts); + if (s < 60) return `${s}s ago`; + if (s < 3600) return `${Math.floor(s / 60)}m ago`; + return `${Math.floor(s / 3600)}h ago`; +} + +const HOME_URL = + typeof window !== "undefined" && + window.location.hostname === "explorer.vibes.base.org" + ? "https://vibes.base.org" + : "/"; + +const FAUCET_URL = + typeof window !== "undefined" && + window.location.hostname === "explorer.vibes.base.org" + ? "https://faucet.vibes.base.org" + : "/faucet"; + +export default function ExplorerPage() { + const [blocks, setBlocks] = useState([]); + const [txs, setTxs] = useState([]); + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const load = async () => { + const [blocksRes, statsRes] = await Promise.all([ + fetch("/api/vibenet/explorer/blocks").then((r) => r.json()), + fetch("/api/vibenet/explorer/stats").then((r) => r.json()), + ]); + setBlocks(blocksRes.blocks ?? []); + setTxs(blocksRes.txs ?? []); + setStats(statsRes); + setLoading(false); + }; + load().catch(() => setLoading(false)); + const t = setInterval(load, 5_000); + return () => clearInterval(t); + }, []); + + return ( +
+
+ + base vibenet + + +
+ +
+ {stats && ( +
+ {[ + { label: "Blocks", value: stats.blocks.toLocaleString() }, + { label: "Transactions", value: stats.txs.toLocaleString() }, + { label: "Addresses", value: stats.addresses.toLocaleString() }, + ].map((s) => ( +
+
{s.label}
+
+ {s.value} +
+
+ ))} +
+ )} + +
+
+

+ Latest Blocks +

+
+ {loading ? ( +
+ Indexing… +
+ ) : blocks.length === 0 ? ( +
+ No blocks yet +
+ ) : ( + blocks.slice(0, 10).map((b, i) => ( + +
+ B +
+
+
+ #{b.number.toLocaleString()} +
+
+ {b.hash.slice(0, 18)}… +
+
+
+
+ {b.tx_count} txns +
+
+ {timeAgo(b.timestamp)} +
+
+ + )) + )} +
+
+ +
+

+ Latest Transactions +

+
+ {loading ? ( +
+ Indexing… +
+ ) : txs.length === 0 ? ( +
+ No transactions yet +
+ ) : ( + txs.slice(0, 10).map((tx, i) => ( + +
+ T +
+
+
+ {tx.hash.slice(0, 18)}… +
+
+ {tx.from_addr.slice(0, 10)}… + {tx.to_addr + ? ` → ${tx.to_addr.slice(0, 10)}…` + : " (create)"} +
+
+
+ #{tx.block_num.toLocaleString()} +
+ + )) + )} +
+
+
+
+
+ ); +} diff --git a/src/app/explorer/tx/[hash]/page.tsx b/src/app/explorer/tx/[hash]/page.tsx new file mode 100644 index 00000000..4fa56be0 --- /dev/null +++ b/src/app/explorer/tx/[hash]/page.tsx @@ -0,0 +1,163 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; + +interface PageProps { + params: Promise<{ hash: string }>; +} + +interface TxDetail { + hash: string; + blockHash: string; + blockNumber: string; + from: string; + to: string | null; + value: string; + gas: string; + gasPrice: string; + nonce: string; + input: string; + transactionIndex: string; + status?: number; +} + +function weiToEth(hex: string): string { + const n = BigInt(hex); + if (n === 0n) return "0 ETH"; + const eth = Number(n) / 1e18; + return `${eth.toFixed(6)} ETH`; +} + +export default function ExplorerTxPage({ params }: PageProps) { + const [hash, setHash] = useState(""); + const [tx, setTx] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + params.then((p) => setHash(p.hash)); + }, [params]); + + useEffect(() => { + if (!hash) return; + setLoading(true); + fetch(`/api/vibenet/explorer/tx/${hash}`) + .then(async (r) => { + if (!r.ok) + throw new Error( + r.status === 404 + ? "Transaction not found" + : "Failed to fetch transaction", + ); + return r.json(); + }) + .then(setTx) + .catch((e) => setError(e.message)) + .finally(() => setLoading(false)); + }, [hash]); + + const blockNum = tx ? parseInt(tx.blockNumber, 16) : null; + + return ( +
+
+ + ← Explorer + + / + + {hash.slice(0, 14)}… + +
+ +
+ {loading &&

Loading…

} + {error &&

{error}

} + {tx && ( +
+
+

Transaction

+ {tx.status !== undefined && ( + + {tx.status === 1 ? "Success" : "Failed"} + + )} +
+
+ {[ + ["Hash", tx.hash], + [ + "Block", + blockNum !== null + ? `#${blockNum.toLocaleString()}` + : tx.blockHash, + ], + ["From", tx.from], + ["To", tx.to ?? "(contract creation)"], + ["Value", weiToEth(tx.value)], + ["Gas Limit", parseInt(tx.gas, 16).toLocaleString()], + ["Gas Price", `${parseInt(tx.gasPrice, 16)} wei`], + ["Nonce", parseInt(tx.nonce, 16).toString()], + ["Index", parseInt(tx.transactionIndex, 16).toString()], + ].map(([label, value], i) => ( +
+ + {label} + + + {label === "Block" && blockNum !== null ? ( + + {value} + + ) : label === "From" ? ( + + {value} + + ) : label === "To" && tx.to ? ( + + {value} + + ) : ( + value + )} + +
+ ))} +
+ + {tx.input && tx.input !== "0x" && ( +
+

+ Input Data +

+
+ + {tx.input} + +
+
+ )} +
+ )} +
+
+ ); +} diff --git a/src/app/faucet/page.tsx b/src/app/faucet/page.tsx new file mode 100644 index 00000000..162078e3 --- /dev/null +++ b/src/app/faucet/page.tsx @@ -0,0 +1,226 @@ +"use client"; + +import { useEffect, useState } from "react"; + +interface FaucetStatus { + address: string; + chain_id: number; + drip_wei: string; + balance_wei: string; + ip_cooldown_secs: number; + addr_cooldown_secs: number; + usdv_address?: string; + usdv_drip_units?: string; +} + +interface DripResult { + ok: boolean; + message: string; + tx_hash?: string; +} + +const HOME_URL = + typeof window !== "undefined" && + window.location.hostname === "faucet.vibes.base.org" + ? "https://vibes.base.org" + : "/"; + +const EXPLORER_URL = + typeof window !== "undefined" && + window.location.hostname === "faucet.vibes.base.org" + ? "https://explorer.vibes.base.org" + : "/explorer"; + +function weiToEth(wei: string): string { + const n = BigInt(wei); + const eth = Number(n) / 1e18; + return eth.toFixed(4); +} + +export default function FaucetPage() { + const [status, setStatus] = useState(null); + const [address, setAddress] = useState(""); + const [loading, setLoading] = useState<"eth" | "usdv" | null>(null); + const [result, setResult] = useState(null); + + useEffect(() => { + const load = () => + fetch("/api/vibenet/faucet/status") + .then((r) => r.json()) + .then(setStatus) + .catch(() => null); + load(); + const t = setInterval(load, 15_000); + return () => clearInterval(t); + }, []); + + const drip = async (token: "eth" | "usdv") => { + if (!address.match(/^0x[a-fA-F0-9]{40}$/)) { + setResult({ ok: false, message: "Invalid address" }); + return; + } + setLoading(token); + setResult(null); + const endpoint = + token === "eth" + ? "/api/vibenet/faucet/drip" + : "/api/vibenet/faucet/drip-usdv"; + try { + const res = await fetch(endpoint, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ address }), + }); + const data = await res.json(); + if (res.ok) { + setResult({ ok: true, message: "Success!", tx_hash: data.tx_hash }); + } else { + setResult({ ok: false, message: data.error ?? "Request failed" }); + } + } catch { + setResult({ ok: false, message: "Network error" }); + } finally { + setLoading(null); + } + }; + + return ( +
+
+ + base vibenet + + +
+ +
+

Faucet

+

+ Drip testnet ETH or mint USDV (Vibe USD) to any address. +

+ +
+ {status && ( +
+
+ Balance:{" "} + + {weiToEth(status.balance_wei)} ETH + +
+
+ Drip amount:{" "} + + {weiToEth(status.drip_wei)} ETH + +
+
+ Cooldown:{" "} + + {status.ip_cooldown_secs / 3600}h per IP / address + +
+ {status.usdv_address && ( + + )} +
+ )} + +
+ + setAddress(e.target.value)} + placeholder="0x..." + className="w-full px-4 py-2.5 rounded-lg bg-white/5 border border-white/10 text-white font-mono text-sm placeholder-gray-600 focus:outline-none focus:border-blue-500" + /> +
+ +
+ + {status?.usdv_address && ( + + )} +
+ + {result && ( +
+

{result.message}

+ {result.tx_hash && ( + + {result.tx_hash} + + )} +
+ )} +
+
+ + +
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index bc9a9f21..70210617 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -13,8 +13,8 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "TIPS", - description: "A beautiful UI for interacting with TIPS", + title: "base vibenet", + description: "An ephemeral Base devnet for trying out in-flight features.", }; export default function RootLayout({ diff --git a/src/app/page.tsx b/src/app/page.tsx index 1b031193..4d7f6ec4 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,770 +1,293 @@ "use client"; -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { useCallback, useEffect, useState } from "react"; -import { - formatRejectionReason, - type MeterBundleResponse, - type RejectedTransaction, -} from "@/lib/s3"; -import type { BlockSummary, BlocksResponse } from "./api/blocks/route"; -import type { RejectedTransactionsResponse } from "./api/rejected/route"; +import { useEffect, useState } from "react"; -type Tab = "blocks" | "rejected"; - -const WEI_PER_GWEI = 10n ** 9n; -const WEI_PER_ETH = 10n ** 18n; - -function formatBigInt(value: bigint, decimals: number, scale: bigint): string { - const whole = value / scale; - const frac = ((value % scale) * 10n ** BigInt(decimals)) / scale; - return `${whole}.${frac.toString().padStart(decimals, "0")}`; -} - -function formatHexValue(hex: string | undefined): string { - if (!hex) return "—"; - const value = BigInt(hex); - if (value >= WEI_PER_ETH / 10000n) { - return `${formatBigInt(value, 6, WEI_PER_ETH)} ETH`; - } - if (value >= WEI_PER_GWEI / 100n) { - return `${formatBigInt(value, 4, WEI_PER_GWEI)} Gwei`; +declare global { + interface Window { + // biome-ignore lint/suspicious/noExplicitAny: EIP-1193 provider + ethereum?: any; } - return `${value.toString()} Wei`; } -function formatGasPrice(hex: string | undefined): string { - if (!hex) return "—"; - const value = BigInt(hex); - return `${formatBigInt(value, 2, WEI_PER_GWEI)} Gwei`; +interface Feature { + title: string; + description: string; + link?: string; } -function SearchBar({ onError }: { onError: (error: string | null) => void }) { - const router = useRouter(); - const [searchHash, setSearchHash] = useState(""); - const [loading, setLoading] = useState(false); - - const handleSearch = async (e: React.FormEvent) => { - e.preventDefault(); - const hash = searchHash.trim(); - if (!hash) return; - - setLoading(true); - onError(null); - - try { - const response = await fetch(`/api/txn/${hash}`); - if (!response.ok) { - if (response.status === 404) { - onError("Transaction not found"); - } else { - onError("Failed to fetch transaction data"); - } - return; - } - const result = await response.json(); - - if (result.bundle_ids && result.bundle_ids.length > 0) { - router.push(`/bundles/${result.bundle_ids[0]}`); - } else { - onError("No bundle found for this transaction"); - } - } catch { - onError("Failed to fetch transaction data"); - } finally { - setLoading(false); - } - }; - - return ( -
- setSearchHash(e.target.value)} - className="w-64 lg:w-80 px-3 py-1.5 text-sm border rounded-lg bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-gray-400" - disabled={loading} - /> - -
- ); +interface VibeConfig { + title: string; + subtitle: string; + features: Feature[]; + branch: string; + commit: string; } -function BlockRow({ block, index }: { block: BlockSummary; index: number }) { - const opacity = Math.max(0.3, 1 - index * 0.08); - const timeSince = Math.floor(Date.now() / 1000 - block.timestamp); - const timeAgo = - timeSince <= 0 - ? "now" - : timeSince < 60 - ? `${timeSince}s ago` - : timeSince < 3600 - ? `${Math.floor(timeSince / 60)}m ago` - : `${Math.floor(timeSince / 3600)}h ago`; - - return ( - -
- - Block - - -
-
-
- - #{block.number.toLocaleString()} - - {timeAgo} -
-
- {block.hash} -
-
-
-
- {block.transactionCount} -
-
txns
-
- - View - - - - ); +interface ContractEntry { + name: string; + address: string; } -function Card({ - children, - className = "", -}: { - children: React.ReactNode; - className?: string; -}) { - return ( -
- {children} -
- ); -} +const FAUCET_URL = + typeof window !== "undefined" && window.location.hostname === "vibes.base.org" + ? "https://faucet.vibes.base.org" + : `${typeof window !== "undefined" ? window.location.protocol : "http:"}//${typeof window !== "undefined" ? window.location.hostname : "localhost"}:${typeof window !== "undefined" ? (Number(window.location.port || 80) + 3).toString() : "18083"}`; -function MeteringCard({ meter }: { meter: MeterBundleResponse }) { - const executionTimeUs = meter.results.reduce( - (sum, r) => sum + r.executionTimeUs, - 0, - ); - const stateRootTimeUs = meter.stateRootTimeUs ?? 0; - const totalTimeUs = executionTimeUs + stateRootTimeUs; +const EXPLORER_URL = + typeof window !== "undefined" && window.location.hostname === "vibes.base.org" + ? "https://explorer.vibes.base.org" + : `${typeof window !== "undefined" ? window.location.protocol : "http:"}//${typeof window !== "undefined" ? window.location.hostname : "localhost"}/explorer`; - return ( - -
-
-
-
Execution
-
- {executionTimeUs.toLocaleString()}μs -
-
-
-
State Root
-
- {stateRootTimeUs.toLocaleString()}μs -
-
-
-
Total Time
-
- {totalTimeUs.toLocaleString()}μs -
-
-
- {((meter.stateRootAccountLeafCount ?? 0) > 0 || - (meter.stateRootStorageLeafCount ?? 0) > 0) && ( -
-
-
- Account Trie Nodes -
-
- {( - (meter.stateRootAccountLeafCount ?? 0) + - (meter.stateRootAccountBranchCount ?? 0) - ).toLocaleString()} -
-
-
-
- Storage Trie Nodes -
-
- {( - (meter.stateRootStorageLeafCount ?? 0) + - (meter.stateRootStorageBranchCount ?? 0) - ).toLocaleString()} -
-
-
- )} -
-
-
- Total Gas - - {(meter.totalGasUsed ?? 0).toLocaleString()} - -
-
- Gas Price - - {formatGasPrice(meter.bundleGasPrice)} - -
-
- Gas Fees - - {formatHexValue(meter.gasFees)} - -
-
- Coinbase Diff - - {formatHexValue(meter.coinbaseDiff)} - -
-
- State Block - - #{meter.stateBlockNumber} - -
-
-
- ); -} - -function RejectedTxRow({ - tx, - expanded, - onToggle, -}: { - tx: RejectedTransaction; - expanded: boolean; - onToggle: () => void; -}) { - const timeAgo = (() => { - const timeSince = Math.floor(Date.now() / 1000 - tx.timestamp); - if (timeSince <= 0) return "now"; - if (timeSince < 60) return `${timeSince}s ago`; - if (timeSince < 3600) return `${Math.floor(timeSince / 60)}m ago`; - if (timeSince < 86400) return `${Math.floor(timeSince / 3600)}h ago`; - return `${Math.floor(timeSince / 86400)}d ago`; - })(); - - return ( -
- - - {expanded && ( -
-
- - - - - - - - - - - - - - - - - - - -
Transaction - {tx.txHash} -
Block - #{tx.blockNumber.toLocaleString()} -
Reason - {formatRejectionReason(tx.reason)} -
Rejected At - {new Date(tx.timestamp * 1000).toLocaleString()} -
-
- -
-

- Metering Results -

- -
- - {tx.metering.results.length > 0 && ( -
-

- Per-Transaction Breakdown -

- - - - - - - - - - - - - - {tx.metering.results.map((result) => ( - - - - - - - - - ))} - -
- Tx Hash - - From - - Gas Used - - Exec Time - - Gas Price - - Gas Fees -
- {result.txHash.slice(0, 10)}... - {result.txHash.slice(-6)} - - {result.fromAddress.slice(0, 8)}... - {result.fromAddress.slice(-4)} - - {result.gasUsed.toLocaleString()} - - {result.executionTimeUs.toLocaleString()}μs - - {formatGasPrice(result.gasPrice)} - - {formatHexValue(result.gasFees)} -
-
-
- )} -
- )} -
- ); -} - -function RejectedTransactionsTab() { - const [transactions, setTransactions] = useState([]); - const [loading, setLoading] = useState(true); - const [expandedIdx, setExpandedIdx] = useState(null); - const [txHashFilter, setTxHashFilter] = useState(""); - const [blockNumberFilter, setBlockNumberFilter] = useState(""); +export default function VibeHomePage() { + const [config, setConfig] = useState(null); + const [contracts, setContracts] = useState(null); + const [rpcUrl, setRpcUrl] = useState(""); + const [chainId, setChainId] = useState(""); + const [copied, setCopied] = useState(null); + const [walletStatus, setWalletStatus] = useState(""); useEffect(() => { - const fetchRejected = async () => { - try { - const response = await fetch("/api/rejected"); - if (response.ok) { - const data: RejectedTransactionsResponse = await response.json(); - setTransactions(data.transactions); - } - } catch { - console.error("Failed to fetch rejected transactions"); - } finally { - setLoading(false); - } - }; - - fetchRejected(); - const interval = setInterval(fetchRejected, 10000); - return () => clearInterval(interval); + fetch("/api/vibenet/config") + .then((r) => r.json()) + .then(setConfig) + .catch(() => null); + + fetch("/api/vibenet/contracts") + .then((r) => r.json()) + .then((data: Record) => + setContracts( + Object.entries(data).map(([name, address]) => ({ name, address })), + ), + ) + .catch(() => null); }, []); - const filteredTransactions = transactions.filter((tx) => { - if (txHashFilter) { - const query = txHashFilter.toLowerCase(); - if (!tx.txHash.toLowerCase().includes(query)) return false; - } - if (blockNumberFilter) { - const blockNum = parseInt(blockNumberFilter, 10); - if (!Number.isNaN(blockNum) && tx.blockNumber !== blockNum) return false; - } - return true; - }); - - return ( -
- -
- - Info - - -
- About rejected transactions:{" "} - These are transactions that violated per-transaction resource - metering budgets (execution time or state root gas limits). They - would have never been - considered for block inclusion and were rejected during the block - building process. -
-
-
- -
-

- Rejected Transactions -

-
- { - setTxHashFilter(e.target.value); - setExpandedIdx(null); - }} - className="w-52 lg:w-64 px-3 py-1.5 text-sm border rounded-lg bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent placeholder-gray-400" - /> - { - setBlockNumberFilter(e.target.value); - setExpandedIdx(null); - }} - className="w-32 px-3 py-1.5 text-sm border rounded-lg bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent placeholder-gray-400" - /> - {(txHashFilter || blockNumberFilter) && ( - - )} -
-
- - - {loading ? ( -
-
-
- - Loading rejected transactions... - -
-
- ) : filteredTransactions.length > 0 ? ( -
- {filteredTransactions.map((tx, index) => ( - - setExpandedIdx(expandedIdx === index ? null : index) - } - /> - ))} -
- ) : ( -
- - No rejected transactions - - -

- {transactions.length > 0 - ? "No transactions match the current filters" - : "No rejected transactions found"} -

-

- {transactions.length > 0 - ? "Try adjusting your search criteria" - : "Transactions that violate metering budgets will appear here"} -

-
- )} - -
- ); -} - -export default function Home() { - const [activeTab, setActiveTab] = useState("blocks"); - - // Read hash on client only after hydration useEffect(() => { - const hash = window.location.hash.replace("#", ""); - if (hash === "rejected") { - setActiveTab("rejected"); - } - }, []); - const [error, setError] = useState(null); - const [blocks, setBlocks] = useState([]); - const [loading, setLoading] = useState(true); - - const switchTab = useCallback((tab: Tab) => { - setActiveTab(tab); - window.location.hash = tab === "blocks" ? "" : tab; - }, []); + if (!config) return; + const rpc = + window.location.hostname === "vibes.base.org" + ? "https://rpc.vibes.base.org" + : `${window.location.protocol}//${window.location.hostname}:${Number(window.location.port || 80) + 1}`; + setRpcUrl(rpc); + + fetch("/api/vibenet/config") + .then((r) => r.json()) + .then((cfg: VibeConfig & { chain_id?: number }) => { + if (cfg.chain_id) setChainId(cfg.chain_id.toString()); + }) + .catch(() => null); + }, [config]); + + const copyToClipboard = (text: string, key: string) => { + navigator.clipboard.writeText(text).then(() => { + setCopied(key); + setTimeout(() => setCopied(null), 2000); + }); + }; - const fetchBlocks = useCallback(async () => { + const addToWallet = async () => { + if (!window.ethereum) { + setWalletStatus("No wallet detected"); + return; + } try { - const response = await fetch("/api/blocks"); - if (response.ok) { - const data: BlocksResponse = await response.json(); - setBlocks(data.blocks); - } + await window.ethereum.request({ + method: "wallet_addEthereumChain", + params: [ + { + chainId: `0x${Number(chainId).toString(16)}`, + chainName: config?.title ?? "base vibenet", + rpcUrls: [rpcUrl], + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + blockExplorerUrls: [EXPLORER_URL], + }, + ], + }); + setWalletStatus("Network added!"); } catch { - console.error("Failed to fetch blocks"); - } finally { - setLoading(false); + setWalletStatus("Failed to add network"); } - }, []); - - useEffect(() => { - fetchBlocks(); - const interval = setInterval(fetchBlocks, 2000); - return () => clearInterval(interval); - }, [fetchBlocks]); + }; return ( -
-
-
-
- - -
- {activeTab === "blocks" && } -
+
+
+ + base vibenet + +
-
- {error && activeTab === "blocks" && ( - -
- - Error - - - {error} - +

{f.title}

+

{f.description}

+ {f.link && ( + + Learn more ↗ + + )} +
+ ))}
- + )} - {activeTab === "blocks" && ( +
+

Connect

+
+ {chainId && ( +
+ Chain ID + +
+ )} + {rpcUrl && ( +
+ RPC URL + +
+ )} +
+ Explorer + + {EXPLORER_URL} + +
+

+ Public RPC access is IP rate limited. +

+ {chainId && ( +
+ + {walletStatus && ( +

{walletStatus}

+ )} +
+ )} +
+
+ + {contracts && contracts.length > 0 && (
-

- Latest Blocks +

+ Deployed Contracts

- - - {loading ? ( -
-
-
- Loading blocks... -
-
- ) : blocks.length > 0 ? ( -
- {blocks.map((block, index) => ( - - ))} -
- ) : ( -
- No blocks available +
+ {contracts.map((c, i) => ( +
+ + {c.name} + + + {c.address} +
- )} - + ))} +
)} - - {activeTab === "rejected" && } + +
+
+ + branch {config?.branch ?? "—"} + + + commit {config?.commit ?? "—"} + +
+ + base.org + +
); } diff --git a/src/app/block/[hash]/page.tsx b/src/app/tips/block/[hash]/page.tsx similarity index 98% rename from src/app/block/[hash]/page.tsx rename to src/app/tips/block/[hash]/page.tsx index c5d0a4a0..95a5ee7c 100644 --- a/src/app/block/[hash]/page.tsx +++ b/src/app/tips/block/[hash]/page.tsx @@ -181,7 +181,7 @@ function TransactionRow({ ); if (hasBundle) { - return {content}; + return {content}; } return content; @@ -331,7 +331,7 @@ export default function BlockPage({ params }: PageProps) {
@@ -352,7 +352,7 @@ export default function BlockPage({ params }: PageProps) {
TIPS @@ -363,7 +363,7 @@ export default function BlockPage({ params }: PageProps) {
{Number(data.number) > 0 ? ( @@ -401,7 +401,7 @@ export default function BlockPage({ params }: PageProps) { )} diff --git a/src/app/bundles/[hash]/page.tsx b/src/app/tips/bundles/[hash]/page.tsx similarity index 99% rename from src/app/bundles/[hash]/page.tsx rename to src/app/tips/bundles/[hash]/page.tsx index 9ea8602a..57522888 100644 --- a/src/app/bundles/[hash]/page.tsx +++ b/src/app/tips/bundles/[hash]/page.tsx @@ -415,7 +415,7 @@ function TimelineEventDetails({
{event.event} Block #{event.data.block_number} @@ -542,7 +542,7 @@ export default function BundlePage({ params }: PageProps) {
@@ -563,7 +563,7 @@ export default function BundlePage({ params }: PageProps) {
TIPS diff --git a/src/app/tips/page.tsx b/src/app/tips/page.tsx new file mode 100644 index 00000000..80693324 --- /dev/null +++ b/src/app/tips/page.tsx @@ -0,0 +1,770 @@ +"use client"; + +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useCallback, useEffect, useState } from "react"; +import type { BlockSummary, BlocksResponse } from "@/app/api/blocks/route"; +import type { RejectedTransactionsResponse } from "@/app/api/rejected/route"; +import { + formatRejectionReason, + type MeterBundleResponse, + type RejectedTransaction, +} from "@/lib/s3"; + +type Tab = "blocks" | "rejected"; + +const WEI_PER_GWEI = 10n ** 9n; +const WEI_PER_ETH = 10n ** 18n; + +function formatBigInt(value: bigint, decimals: number, scale: bigint): string { + const whole = value / scale; + const frac = ((value % scale) * 10n ** BigInt(decimals)) / scale; + return `${whole}.${frac.toString().padStart(decimals, "0")}`; +} + +function formatHexValue(hex: string | undefined): string { + if (!hex) return "—"; + const value = BigInt(hex); + if (value >= WEI_PER_ETH / 10000n) { + return `${formatBigInt(value, 6, WEI_PER_ETH)} ETH`; + } + if (value >= WEI_PER_GWEI / 100n) { + return `${formatBigInt(value, 4, WEI_PER_GWEI)} Gwei`; + } + return `${value.toString()} Wei`; +} + +function formatGasPrice(hex: string | undefined): string { + if (!hex) return "—"; + const value = BigInt(hex); + return `${formatBigInt(value, 2, WEI_PER_GWEI)} Gwei`; +} + +function SearchBar({ onError }: { onError: (error: string | null) => void }) { + const router = useRouter(); + const [searchHash, setSearchHash] = useState(""); + const [loading, setLoading] = useState(false); + + const handleSearch = async (e: React.FormEvent) => { + e.preventDefault(); + const hash = searchHash.trim(); + if (!hash) return; + + setLoading(true); + onError(null); + + try { + const response = await fetch(`/api/txn/${hash}`); + if (!response.ok) { + if (response.status === 404) { + onError("Transaction not found"); + } else { + onError("Failed to fetch transaction data"); + } + return; + } + const result = await response.json(); + + if (result.bundle_ids && result.bundle_ids.length > 0) { + router.push(`/tips/bundles/${result.bundle_ids[0]}`); + } else { + onError("No bundle found for this transaction"); + } + } catch { + onError("Failed to fetch transaction data"); + } finally { + setLoading(false); + } + }; + + return ( +
+ setSearchHash(e.target.value)} + className="w-64 lg:w-80 px-3 py-1.5 text-sm border rounded-lg bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent placeholder-gray-400" + disabled={loading} + /> + +
+ ); +} + +function BlockRow({ block, index }: { block: BlockSummary; index: number }) { + const opacity = Math.max(0.3, 1 - index * 0.08); + const timeSince = Math.floor(Date.now() / 1000 - block.timestamp); + const timeAgo = + timeSince <= 0 + ? "now" + : timeSince < 60 + ? `${timeSince}s ago` + : timeSince < 3600 + ? `${Math.floor(timeSince / 60)}m ago` + : `${Math.floor(timeSince / 3600)}h ago`; + + return ( + +
+ + Block + + +
+
+
+ + #{block.number.toLocaleString()} + + {timeAgo} +
+
+ {block.hash} +
+
+
+
+ {block.transactionCount} +
+
txns
+
+ + View + + + + ); +} + +function Card({ + children, + className = "", +}: { + children: React.ReactNode; + className?: string; +}) { + return ( +
+ {children} +
+ ); +} + +function MeteringCard({ meter }: { meter: MeterBundleResponse }) { + const executionTimeUs = meter.results.reduce( + (sum, r) => sum + r.executionTimeUs, + 0, + ); + const stateRootTimeUs = meter.stateRootTimeUs ?? 0; + const totalTimeUs = executionTimeUs + stateRootTimeUs; + + return ( + +
+
+
+
Execution
+
+ {executionTimeUs.toLocaleString()}μs +
+
+
+
State Root
+
+ {stateRootTimeUs.toLocaleString()}μs +
+
+
+
Total Time
+
+ {totalTimeUs.toLocaleString()}μs +
+
+
+ {((meter.stateRootAccountLeafCount ?? 0) > 0 || + (meter.stateRootStorageLeafCount ?? 0) > 0) && ( +
+
+
+ Account Trie Nodes +
+
+ {( + (meter.stateRootAccountLeafCount ?? 0) + + (meter.stateRootAccountBranchCount ?? 0) + ).toLocaleString()} +
+
+
+
+ Storage Trie Nodes +
+
+ {( + (meter.stateRootStorageLeafCount ?? 0) + + (meter.stateRootStorageBranchCount ?? 0) + ).toLocaleString()} +
+
+
+ )} +
+
+
+ Total Gas + + {(meter.totalGasUsed ?? 0).toLocaleString()} + +
+
+ Gas Price + + {formatGasPrice(meter.bundleGasPrice)} + +
+
+ Gas Fees + + {formatHexValue(meter.gasFees)} + +
+
+ Coinbase Diff + + {formatHexValue(meter.coinbaseDiff)} + +
+
+ State Block + + #{meter.stateBlockNumber} + +
+
+
+ ); +} + +function RejectedTxRow({ + tx, + expanded, + onToggle, +}: { + tx: RejectedTransaction; + expanded: boolean; + onToggle: () => void; +}) { + const timeAgo = (() => { + const timeSince = Math.floor(Date.now() / 1000 - tx.timestamp); + if (timeSince <= 0) return "now"; + if (timeSince < 60) return `${timeSince}s ago`; + if (timeSince < 3600) return `${Math.floor(timeSince / 60)}m ago`; + if (timeSince < 86400) return `${Math.floor(timeSince / 3600)}h ago`; + return `${Math.floor(timeSince / 86400)}d ago`; + })(); + + return ( +
+ + + {expanded && ( +
+
+ + + + + + + + + + + + + + + + + + + +
Transaction + {tx.txHash} +
Block + #{tx.blockNumber.toLocaleString()} +
Reason + {formatRejectionReason(tx.reason)} +
Rejected At + {new Date(tx.timestamp * 1000).toLocaleString()} +
+
+ +
+

+ Metering Results +

+ +
+ + {tx.metering.results.length > 0 && ( +
+

+ Per-Transaction Breakdown +

+ + + + + + + + + + + + + + {tx.metering.results.map((result) => ( + + + + + + + + + ))} + +
+ Tx Hash + + From + + Gas Used + + Exec Time + + Gas Price + + Gas Fees +
+ {result.txHash.slice(0, 10)}... + {result.txHash.slice(-6)} + + {result.fromAddress.slice(0, 8)}... + {result.fromAddress.slice(-4)} + + {result.gasUsed.toLocaleString()} + + {result.executionTimeUs.toLocaleString()}μs + + {formatGasPrice(result.gasPrice)} + + {formatHexValue(result.gasFees)} +
+
+
+ )} +
+ )} +
+ ); +} + +function RejectedTransactionsTab() { + const [transactions, setTransactions] = useState([]); + const [loading, setLoading] = useState(true); + const [expandedIdx, setExpandedIdx] = useState(null); + const [txHashFilter, setTxHashFilter] = useState(""); + const [blockNumberFilter, setBlockNumberFilter] = useState(""); + + useEffect(() => { + const fetchRejected = async () => { + try { + const response = await fetch("/api/rejected"); + if (response.ok) { + const data: RejectedTransactionsResponse = await response.json(); + setTransactions(data.transactions); + } + } catch { + console.error("Failed to fetch rejected transactions"); + } finally { + setLoading(false); + } + }; + + fetchRejected(); + const interval = setInterval(fetchRejected, 10000); + return () => clearInterval(interval); + }, []); + + const filteredTransactions = transactions.filter((tx) => { + if (txHashFilter) { + const query = txHashFilter.toLowerCase(); + if (!tx.txHash.toLowerCase().includes(query)) return false; + } + if (blockNumberFilter) { + const blockNum = parseInt(blockNumberFilter, 10); + if (!Number.isNaN(blockNum) && tx.blockNumber !== blockNum) return false; + } + return true; + }); + + return ( +
+ +
+ + Info + + +
+ About rejected transactions:{" "} + These are transactions that violated per-transaction resource + metering budgets (execution time or state root gas limits). They + would have never been + considered for block inclusion and were rejected during the block + building process. +
+
+
+ +
+

+ Rejected Transactions +

+
+ { + setTxHashFilter(e.target.value); + setExpandedIdx(null); + }} + className="w-52 lg:w-64 px-3 py-1.5 text-sm border rounded-lg bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent placeholder-gray-400" + /> + { + setBlockNumberFilter(e.target.value); + setExpandedIdx(null); + }} + className="w-32 px-3 py-1.5 text-sm border rounded-lg bg-white border-gray-300 focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-transparent placeholder-gray-400" + /> + {(txHashFilter || blockNumberFilter) && ( + + )} +
+
+ + + {loading ? ( +
+
+
+ + Loading rejected transactions... + +
+
+ ) : filteredTransactions.length > 0 ? ( +
+ {filteredTransactions.map((tx, index) => ( + + setExpandedIdx(expandedIdx === index ? null : index) + } + /> + ))} +
+ ) : ( +
+ + No rejected transactions + + +

+ {transactions.length > 0 + ? "No transactions match the current filters" + : "No rejected transactions found"} +

+

+ {transactions.length > 0 + ? "Try adjusting your search criteria" + : "Transactions that violate metering budgets will appear here"} +

+
+ )} + +
+ ); +} + +export default function Home() { + const [activeTab, setActiveTab] = useState("blocks"); + + // Read hash on client only after hydration + useEffect(() => { + const hash = window.location.hash.replace("#", ""); + if (hash === "rejected") { + setActiveTab("rejected"); + } + }, []); + const [error, setError] = useState(null); + const [blocks, setBlocks] = useState([]); + const [loading, setLoading] = useState(true); + + const switchTab = useCallback((tab: Tab) => { + setActiveTab(tab); + window.location.hash = tab === "blocks" ? "" : tab; + }, []); + + const fetchBlocks = useCallback(async () => { + try { + const response = await fetch("/api/blocks"); + if (response.ok) { + const data: BlocksResponse = await response.json(); + setBlocks(data.blocks); + } + } catch { + console.error("Failed to fetch blocks"); + } finally { + setLoading(false); + } + }, []); + + useEffect(() => { + fetchBlocks(); + const interval = setInterval(fetchBlocks, 2000); + return () => clearInterval(interval); + }, [fetchBlocks]); + + return ( +
+
+
+
+ + +
+ {activeTab === "blocks" && } +
+
+ +
+ {error && activeTab === "blocks" && ( + +
+ + Error + + + {error} + +
+
+ )} + + {activeTab === "blocks" && ( +
+

+ Latest Blocks +

+ + + {loading ? ( +
+
+
+ Loading blocks... +
+
+ ) : blocks.length > 0 ? ( +
+ {blocks.map((block, index) => ( + + ))} +
+ ) : ( +
+ No blocks available +
+ )} + +
+ )} + + {activeTab === "rejected" && } +
+
+ ); +} diff --git a/src/app/txn/[hash]/page.tsx b/src/app/tips/txn/[hash]/page.tsx similarity index 97% rename from src/app/txn/[hash]/page.tsx rename to src/app/tips/txn/[hash]/page.tsx index 76f1dda3..e98211b0 100644 --- a/src/app/txn/[hash]/page.tsx +++ b/src/app/tips/txn/[hash]/page.tsx @@ -39,7 +39,7 @@ export default function TransactionRedirectPage({ params }: PageProps) { const result: TransactionHistoryResponse = await response.json(); if (result.bundle_ids && result.bundle_ids.length > 0) { - router.push(`/bundles/${result.bundle_ids[0]}`); + router.push(`/tips/bundles/${result.bundle_ids[0]}`); } else { setError("No bundle found for this transaction"); } diff --git a/src/lib/vibenet/db.ts b/src/lib/vibenet/db.ts new file mode 100644 index 00000000..d78bc5f7 --- /dev/null +++ b/src/lib/vibenet/db.ts @@ -0,0 +1,115 @@ +import { readFileSync } from "node:fs"; +import path from "node:path"; +import Database from "better-sqlite3"; + +const DB_PATH = process.env.VIBESCAN_DB_PATH ?? "/data/vibescan.db"; +const MIGRATION_PATH = path.join( + process.cwd(), + "docker/migrations/0001_init.sql", +); + +let _db: Database.Database | null = null; + +export function getDb(): Database.Database { + if (_db) return _db; + _db = new Database(DB_PATH); + _db.pragma("journal_mode = WAL"); + _db.pragma("synchronous = NORMAL"); + const migration = readFileSync(MIGRATION_PATH, "utf8"); + _db.exec(migration); + return _db; +} + +export interface BlockRow { + number: number; + hash: string; + timestamp: number; + miner: string; + tx_count: number; + gas_used: number; + gas_limit: number; + base_fee: string | null; +} + +export interface TxRow { + hash: string; + block_num: number; + tx_index: number; + from_addr: string; + to_addr: string | null; + value: string; + status: number; + created: string | null; +} + +export interface ActivityRow { + address: string; + block_num: number; + tx_index: number; + log_index: number; + tx_hash: string; + role: number; + token: string | null; +} + +export interface StatsRow { + blocks: number; + txs: number; + addresses: number; +} + +export function getStats(): StatsRow { + const db = getDb(); + return db + .prepare("SELECT blocks, txs, addresses FROM explorer_stats WHERE id = 0") + .get() as StatsRow; +} + +export function getRecentBlocks(limit = 20): BlockRow[] { + const db = getDb(); + return db + .prepare("SELECT * FROM blocks ORDER BY number DESC LIMIT ?") + .all(limit) as BlockRow[]; +} + +export function getRecentTxs(limit = 20): TxRow[] { + const db = getDb(); + return db + .prepare("SELECT * FROM txs ORDER BY block_num DESC, tx_index DESC LIMIT ?") + .all(limit) as TxRow[]; +} + +export function getBlockHash(number: number): string | null { + const db = getDb(); + const row = db + .prepare("SELECT hash FROM blocks WHERE number = ?") + .get(number) as { hash: string } | undefined; + return row?.hash ?? null; +} + +export function getAddressActivity(address: string, limit = 50): ActivityRow[] { + const db = getDb(); + return db + .prepare( + `SELECT * FROM address_activity + WHERE address = ? + ORDER BY block_num DESC, tx_index DESC, log_index DESC + LIMIT ?`, + ) + .all(address.toLowerCase(), limit) as ActivityRow[]; +} + +export function getCursor(): { + last_indexed_block: number; + last_indexed_hash: string; +} | null { + const db = getDb(); + return (db + .prepare( + "SELECT last_indexed_block, last_indexed_hash FROM cursor WHERE id = 0", + ) + .get() ?? null) as { + last_indexed_block: number; + last_indexed_hash: string; + } | null; +} diff --git a/src/lib/vibenet/faucet.ts b/src/lib/vibenet/faucet.ts new file mode 100644 index 00000000..4f753880 --- /dev/null +++ b/src/lib/vibenet/faucet.ts @@ -0,0 +1,133 @@ +import { readFile } from "node:fs/promises"; +import { + type Address, + createPublicClient, + createWalletClient, + defineChain, + type Hex, + http, + parseEther, +} from "viem"; +import { privateKeyToAccount } from "viem/accounts"; + +// Rate limiting: last drip timestamp per IP / per address, per asset. +// Module-level state — resets on container restart (intentional for devnet). +const ipCooldowns = new Map>(); // asset → ip → ts +const addrCooldowns = new Map>(); // asset → addr → ts + +function cooldownMap(store: Map>, asset: string) { + let m = store.get(asset); + if (!m) { + m = new Map(); + store.set(asset, m); + } + return m; +} + +export function checkCooldown( + asset: string, + ip: string, + address: string, + ipSecs: number, + addrSecs: number, +): string | null { + const now = Date.now() / 1000; + const ipMap = cooldownMap(ipCooldowns, asset); + const addrMap = cooldownMap(addrCooldowns, asset); + + const lastIp = ipMap.get(ip) ?? 0; + const lastAddr = addrMap.get(address.toLowerCase()) ?? 0; + + if (now - lastIp < ipSecs) { + const wait = Math.ceil(ipSecs - (now - lastIp)); + return `IP rate limited. Try again in ${wait}s.`; + } + if (now - lastAddr < addrSecs) { + const wait = Math.ceil(addrSecs - (now - lastAddr)); + return `Address rate limited. Try again in ${wait}s.`; + } + return null; +} + +export function recordDrip(asset: string, ip: string, address: string) { + const now = Date.now() / 1000; + cooldownMap(ipCooldowns, asset).set(ip, now); + cooldownMap(addrCooldowns, asset).set(address.toLowerCase(), now); +} + +function getChain() { + const chainId = Number(process.env.VIBENET_FAUCET_CHAIN_ID ?? "84538453"); + const rpcUrl = process.env.VIBENET_FAUCET_RPC_URL ?? "http://localhost:8545"; + return defineChain({ + id: chainId, + name: "vibenet", + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: { default: { http: [rpcUrl] } }, + }); +} + +function getAccount() { + const pk = process.env.VIBENET_FAUCET_PRIVATE_KEY; + if (!pk) throw new Error("VIBENET_FAUCET_PRIVATE_KEY is not set"); + return privateKeyToAccount(pk as Hex); +} + +export function getPublicClient() { + const chain = getChain(); + return createPublicClient({ chain, transport: http() }); +} + +export function getWalletClient() { + const chain = getChain(); + return createWalletClient({ + account: getAccount(), + chain, + transport: http(), + }); +} + +export function getFaucetAddress(): Address { + return getAccount().address; +} + +export function getDripWei(): bigint { + return BigInt( + process.env.VIBENET_FAUCET_DRIP_WEI ?? String(parseEther("0.1")), + ); +} + +export function getUsdvDripUnits(): bigint { + return BigInt(process.env.VIBENET_FAUCET_USDV_DRIP_UNITS ?? "1000000000"); +} + +export function getIpCooldownSecs(): number { + return Number(process.env.VIBENET_FAUCET_IP_COOLDOWN_SECS ?? "3600"); +} + +export function getAddrCooldownSecs(): number { + return Number(process.env.VIBENET_FAUCET_ADDR_COOLDOWN_SECS ?? "3600"); +} + +export async function getContractAddress( + name: string, +): Promise
{ + const path = + process.env.VIBENET_FAUCET_CONTRACTS_PATH ?? "/shared/contracts.json"; + try { + const raw = await readFile(path, "utf8"); + const contracts = JSON.parse(raw) as Record; + const addr = contracts[name]; + return addr ? (addr as Address) : null; + } catch { + return null; + } +} + +export function clientIp(req: Request): string { + return ( + req.headers.get("x-real-ip") ?? + req.headers.get("cf-connecting-ip") ?? + req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? + "unknown" + ); +} diff --git a/src/proxy.ts b/src/proxy.ts index 6759d1de..17897f06 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -1,25 +1,45 @@ import type { NextRequest } from "next/server"; import { NextResponse } from "next/server"; +const DOMAIN = "vibes.base.org"; + export function proxy(request: NextRequest) { + const host = request.headers.get("host") ?? ""; + const path = request.nextUrl.pathname; + + // OPTIONS preflight — reply immediately if (request.method === "OPTIONS") { return new NextResponse(null, { status: 204, headers: { "Access-Control-Allow-Origin": "*", - "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type", }, }); } + // Subdomain routing: faucet.vibes.base.org/* → /faucet/* + if (host === `faucet.${DOMAIN}` && !path.startsWith("/faucet")) { + const url = request.nextUrl.clone(); + url.pathname = `/faucet${path === "/" ? "" : path}`; + return NextResponse.rewrite(url); + } + + // Subdomain routing: explorer.vibes.base.org/* → /explorer/* + if (host === `explorer.${DOMAIN}` && !path.startsWith("/explorer")) { + const url = request.nextUrl.clone(); + url.pathname = `/explorer${path === "/" ? "" : path}`; + return NextResponse.rewrite(url); + } + const response = NextResponse.next(); response.headers.set("Access-Control-Allow-Origin", "*"); - response.headers.set("Access-Control-Allow-Methods", "GET, OPTIONS"); + response.headers.set("Access-Control-Allow-Methods", "GET, POST, OPTIONS"); response.headers.set("Access-Control-Allow-Headers", "Content-Type"); return response; } export const config = { - matcher: "/api/:path*", + matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"], }; From fdf15294b5f24286d13536184b8847645d5468ee Mon Sep 17 00:00:00 2001 From: Chris Hunter Date: Thu, 14 May 2026 14:32:25 -0400 Subject: [PATCH 2/7] =?UTF-8?q?fix:=20indexer=20wipeAll=20=E2=80=94=20use?= =?UTF-8?q?=20db.exec()=20for=20multi-statement=20reset?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit better-sqlite3 prepare() rejects SQL with multiple statements. Replace wipeAll prepared statement with a wipeDb() function that uses db.exec() which handles multi-statement SQL correctly. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- scripts/indexer.mjs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/indexer.mjs b/scripts/indexer.mjs index c8a060c6..22e1a5ce 100644 --- a/scripts/indexer.mjs +++ b/scripts/indexer.mjs @@ -73,9 +73,19 @@ const stmts = { WHERE id = 0 `), getBlockHash: db.prepare("SELECT hash FROM blocks WHERE number = ?"), - wipeAll: db.prepare("DELETE FROM blocks; DELETE FROM txs; DELETE FROM address_activity; DELETE FROM addresses; DELETE FROM cursor; DELETE FROM explorer_stats; INSERT INTO explorer_stats (id, blocks, txs, addresses) VALUES (0, 0, 0, 0)"), }; +function wipeDb() { + db.exec(` + DELETE FROM blocks; + DELETE FROM txs; + DELETE FROM address_activity; + DELETE FROM addresses; + DELETE FROM cursor; + UPDATE explorer_stats SET blocks=0, txs=0, addresses=0 WHERE id=0; + `); +} + // --- Indexing logic --- async function processBlock(blockNum) { From ea179171accc66474dfdb72419480085d4de359b Mon Sep 17 00:00:00 2001 From: Chris Hunter Date: Thu, 14 May 2026 15:17:07 -0400 Subject: [PATCH 3/7] fix: Dockerfile native deps, .dockerignore, proxy subdomain routing - Switch Dockerfile from bun to npm (bun.lock lacked better-sqlite3) - Add apk build tools (python3/make/g++) for better-sqlite3 native compile - Add .dockerignore to exclude node_modules/.next/tmp from build context - Fix proxy.ts: skip /api/* paths in subdomain rewrites so API routes work correctly when called from faucet/explorer subdomains Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .dockerignore | 7 +++++++ Dockerfile | 12 +++++++----- src/proxy.ts | 26 +++++++++++++++----------- 3 files changed, 29 insertions(+), 16 deletions(-) create mode 100644 .dockerignore diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..a6d73960 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +.git +.next +node_modules +npm-debug.log* +.env*.local +/tmp +*.db diff --git a/Dockerfile b/Dockerfile index fa806674..5d6d3831 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,18 @@ -FROM oven/bun:1 AS deps +FROM node:20-alpine AS deps WORKDIR /app -COPY package.json bun.lock ./ -RUN bun install --frozen-lockfile +# better-sqlite3 requires native compilation +RUN apk add --no-cache python3 make g++ +COPY package.json package-lock.json ./ +RUN npm ci -FROM oven/bun:1 AS builder +FROM node:20-alpine AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . ENV NEXT_TELEMETRY_DISABLED=1 -RUN bun run build +RUN npm run build FROM node:20-alpine AS runner WORKDIR /app diff --git a/src/proxy.ts b/src/proxy.ts index 17897f06..bb777d04 100644 --- a/src/proxy.ts +++ b/src/proxy.ts @@ -19,18 +19,22 @@ export function proxy(request: NextRequest) { }); } - // Subdomain routing: faucet.vibes.base.org/* → /faucet/* - if (host === `faucet.${DOMAIN}` && !path.startsWith("/faucet")) { - const url = request.nextUrl.clone(); - url.pathname = `/faucet${path === "/" ? "" : path}`; - return NextResponse.rewrite(url); - } + // Subdomain routing — only rewrite page paths, not /api/* which routes + // correctly regardless of which subdomain the request originates from. + if (!path.startsWith("/api")) { + // faucet.vibes.base.org/* → /faucet/* + if (host === `faucet.${DOMAIN}` && !path.startsWith("/faucet")) { + const url = request.nextUrl.clone(); + url.pathname = `/faucet${path === "/" ? "" : path}`; + return NextResponse.rewrite(url); + } - // Subdomain routing: explorer.vibes.base.org/* → /explorer/* - if (host === `explorer.${DOMAIN}` && !path.startsWith("/explorer")) { - const url = request.nextUrl.clone(); - url.pathname = `/explorer${path === "/" ? "" : path}`; - return NextResponse.rewrite(url); + // explorer.vibes.base.org/* → /explorer/* + if (host === `explorer.${DOMAIN}` && !path.startsWith("/explorer")) { + const url = request.nextUrl.clone(); + url.pathname = `/explorer${path === "/" ? "" : path}`; + return NextResponse.rewrite(url); + } } const response = NextResponse.next(); From fc39eb099672d23ef37efbb76c9ae89dfc67e3f9 Mon Sep 17 00:00:00 2001 From: Chris Hunter Date: Thu, 14 May 2026 15:22:24 -0400 Subject: [PATCH 4/7] fix: drop standalone output, copy full node_modules for indexer next start (non-standalone) + full node_modules lets indexer.mjs resolve viem and better-sqlite3 without special packaging. For a devnet image the extra layer size is not a concern. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- Dockerfile | 8 +++++--- next.config.ts | 1 - scripts/start.sh | 3 +-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5d6d3831..bb461823 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,13 +26,15 @@ RUN adduser --system --uid 1001 nextjs RUN mkdir -p .next /data RUN chown nextjs:nodejs .next /data +# Full node_modules (not standalone) so indexer.mjs can resolve viem + better-sqlite3 +COPY --from=deps /app/node_modules ./node_modules +COPY --from=builder --chown=nextjs:nodejs /app/.next ./.next COPY --from=builder /app/public ./public -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder /app/package.json ./package.json COPY --from=builder /app/scripts ./scripts COPY --from=builder /app/docker/migrations ./docker/migrations -RUN chmod +x scripts/start.sh +RUN chown -R nextjs:nodejs node_modules && chmod +x scripts/start.sh USER nextjs diff --git a/next.config.ts b/next.config.ts index 86e90560..89cdbd2a 100644 --- a/next.config.ts +++ b/next.config.ts @@ -1,7 +1,6 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { - output: "standalone", // better-sqlite3 is a native module; exclude it from webpack bundling serverExternalPackages: ["better-sqlite3"], }; diff --git a/scripts/start.sh b/scripts/start.sh index 49430d81..e47f1774 100644 --- a/scripts/start.sh +++ b/scripts/start.sh @@ -3,7 +3,6 @@ set -e # Start the background indexer (writes to SQLite, reads from node via RPC) node scripts/indexer.mjs & -INDEXER_PID=$! # Start Next.js server in foreground so Docker tracks its exit code -exec node server.js +exec npm start From f392def651ef27cfb5de58ddd34666c151a8c47e Mon Sep 17 00:00:00 2001 From: Chris Hunter Date: Fri, 15 May 2026 10:15:03 -0400 Subject: [PATCH 5/7] feat: NFV faucet button, explorer search, landing page bug fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugs: - Landing page FAUCET_URL was using port-hopping math from old multi-port nginx layout; switch to /faucet path - Landing page chain_id was looking for it in vibenet.yaml; pull from /api/vibenet/faucet/status which already returns it - Filter out _branch/_commit/faucetAddress metadata from contracts list - isProd() referenced window in second clause without SSR guard Features: - New /api/vibenet/faucet/drip-nfv endpoint (calls NFV.mint(addr)) - Faucet UI gets a third button when nfv_address is present - Explorer home page gets a search bar that auto-classifies queries: block number → /explorer/block, address → /explorer/address, 64-char hash → tries /explorer/tx, falls back to /explorer/block Cleanup: - Delete bun.lock (we use npm now; bun.lock was stale, missing better-sqlite3) Co-Authored-By: Claude Sonnet 4.6 (1M context) --- bun.lock | 482 ------------------- src/app/api/vibenet/faucet/drip-nfv/route.ts | 67 +++ src/app/api/vibenet/faucet/status/route.ts | 6 +- src/app/explorer/page.tsx | 70 +++ src/app/faucet/page.tsx | 50 +- src/app/page.tsx | 56 ++- 6 files changed, 215 insertions(+), 516 deletions(-) delete mode 100644 bun.lock create mode 100644 src/app/api/vibenet/faucet/drip-nfv/route.ts diff --git a/bun.lock b/bun.lock deleted file mode 100644 index 929dbf55..00000000 --- a/bun.lock +++ /dev/null @@ -1,482 +0,0 @@ -{ - "lockfileVersion": 1, - "configVersion": 1, - "workspaces": { - "": { - "name": "ui", - "dependencies": { - "@aws-sdk/client-s3": "3.940.0", - "next": "16.0.7", - "react": "19.2.1", - "react-dom": "19.2.1", - "viem": "2.40.3", - }, - "devDependencies": { - "@biomejs/biome": "2.3.8", - "@tailwindcss/postcss": "4.1.17", - "@types/node": "20.19.25", - "@types/react": "19.2.7", - "@types/react-dom": "19.2.3", - "tailwindcss": "4.1.17", - "typescript": "5.9.3", - }, - }, - }, - "packages": { - "@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.1", "", {}, "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ=="], - - "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="], - - "@aws-crypto/crc32": ["@aws-crypto/crc32@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg=="], - - "@aws-crypto/crc32c": ["@aws-crypto/crc32c@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-+iWb8qaHLYKrNvGRbiYRHSdKRWhto5XlZUEBwDjYNf+ly5SVYG6zEoYIdxvf5R3zyeP16w4PLBn3rH1xc74Rag=="], - - "@aws-crypto/sha1-browser": ["@aws-crypto/sha1-browser@5.2.0", "", { "dependencies": { "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-OH6lveCFfcDjX4dbAvCFSYUjJZjDr/3XJ3xHtjn3Oj5b9RjojQo8npoLeA/bNwkOkrSQ0wgrHzXk4tDRxGKJeg=="], - - "@aws-crypto/sha256-browser": ["@aws-crypto/sha256-browser@5.2.0", "", { "dependencies": { "@aws-crypto/sha256-js": "^5.2.0", "@aws-crypto/supports-web-crypto": "^5.2.0", "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "@aws-sdk/util-locate-window": "^3.0.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw=="], - - "@aws-crypto/sha256-js": ["@aws-crypto/sha256-js@5.2.0", "", { "dependencies": { "@aws-crypto/util": "^5.2.0", "@aws-sdk/types": "^3.222.0", "tslib": "^2.6.2" } }, "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA=="], - - "@aws-crypto/supports-web-crypto": ["@aws-crypto/supports-web-crypto@5.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg=="], - - "@aws-crypto/util": ["@aws-crypto/util@5.2.0", "", { "dependencies": { "@aws-sdk/types": "^3.222.0", "@smithy/util-utf8": "^2.0.0", "tslib": "^2.6.2" } }, "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ=="], - - "@aws-sdk/client-s3": ["@aws-sdk/client-s3@3.940.0", "", { "dependencies": { "@aws-crypto/sha1-browser": "5.2.0", "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/credential-provider-node": "3.940.0", "@aws-sdk/middleware-bucket-endpoint": "3.936.0", "@aws-sdk/middleware-expect-continue": "3.936.0", "@aws-sdk/middleware-flexible-checksums": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-location-constraint": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-sdk-s3": "3.940.0", "@aws-sdk/middleware-ssec": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/signature-v4-multi-region": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/eventstream-serde-browser": "^4.2.5", "@smithy/eventstream-serde-config-resolver": "^4.3.5", "@smithy/eventstream-serde-node": "^4.2.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-blob-browser": "^4.2.6", "@smithy/hash-node": "^4.2.5", "@smithy/hash-stream-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/md5-js": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "@smithy/util-waiter": "^4.2.5", "tslib": "^2.6.2" } }, "sha512-Wi4qnBT6shRRMXuuTgjMFTU5mu2KFWisgcigEMPptjPGUtJvBVi4PTGgS64qsLoUk/obqDAyOBOfEtRZ2ddC2w=="], - - "@aws-sdk/client-sso": ["@aws-sdk/client-sso@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-SdqJGWVhmIURvCSgkDditHRO+ozubwZk9aCX9MK8qxyOndhobCndW1ozl3hX9psvMAo9Q4bppjuqy/GHWpjB+A=="], - - "@aws-sdk/core": ["@aws-sdk/core@3.940.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/xml-builder": "3.930.0", "@smithy/core": "^3.18.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-base64": "^4.3.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-KsGD2FLaX5ngJao1mHxodIVU9VYd1E8810fcYiGwO1PFHDzf5BEkp6D9IdMeQwT8Q6JLYtiiT1Y/o3UCScnGoA=="], - - "@aws-sdk/credential-provider-env": ["@aws-sdk/credential-provider-env@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/G3l5/wbZYP2XEQiOoIkRJmlv15f1P3MSd1a0gz27lHEMrOJOGq66rF1Ca4OJLzapWt3Fy9BPrZAepoAX11kMw=="], - - "@aws-sdk/credential-provider-http": ["@aws-sdk/credential-provider-http@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/node-http-handler": "^4.4.5", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-stream": "^4.5.6", "tslib": "^2.6.2" } }, "sha512-dOrc03DHElNBD6N9Okt4U0zhrG4Wix5QUBSZPr5VN8SvmjD9dkrrxOkkJaMCl/bzrW7kbQEp7LuBdbxArMmOZQ=="], - - "@aws-sdk/credential-provider-ini": ["@aws-sdk/credential-provider-ini@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/credential-provider-env": "3.940.0", "@aws-sdk/credential-provider-http": "3.940.0", "@aws-sdk/credential-provider-login": "3.940.0", "@aws-sdk/credential-provider-process": "3.940.0", "@aws-sdk/credential-provider-sso": "3.940.0", "@aws-sdk/credential-provider-web-identity": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-gn7PJQEzb/cnInNFTOaDoCN/hOKqMejNmLof1W5VW95Qk0TPO52lH8R4RmJPnRrwFMswOWswTOpR1roKNLIrcw=="], - - "@aws-sdk/credential-provider-login": ["@aws-sdk/credential-provider-login@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/protocol-http": "^5.3.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-fOKC3VZkwa9T2l2VFKWRtfHQPQuISqqNl35ZhcXjWKVwRwl/o7THPMkqI4XwgT2noGa7LLYVbWMwnsgSsBqglg=="], - - "@aws-sdk/credential-provider-node": ["@aws-sdk/credential-provider-node@3.940.0", "", { "dependencies": { "@aws-sdk/credential-provider-env": "3.940.0", "@aws-sdk/credential-provider-http": "3.940.0", "@aws-sdk/credential-provider-ini": "3.940.0", "@aws-sdk/credential-provider-process": "3.940.0", "@aws-sdk/credential-provider-sso": "3.940.0", "@aws-sdk/credential-provider-web-identity": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/credential-provider-imds": "^4.2.5", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-M8NFAvgvO6xZjiti5kztFiAYmSmSlG3eUfr4ZHSfXYZUA/KUdZU/D6xJyaLnU8cYRWBludb6K9XPKKVwKfqm4g=="], - - "@aws-sdk/credential-provider-process": ["@aws-sdk/credential-provider-process@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-pILBzt5/TYCqRsJb7vZlxmRIe0/T+FZPeml417EK75060ajDGnVJjHcuVdLVIeKoTKm9gmJc9l45gon6PbHyUQ=="], - - "@aws-sdk/credential-provider-sso": ["@aws-sdk/credential-provider-sso@3.940.0", "", { "dependencies": { "@aws-sdk/client-sso": "3.940.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/token-providers": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-q6JMHIkBlDCOMnA3RAzf8cGfup+8ukhhb50fNpghMs1SNBGhanmaMbZSgLigBRsPQW7fOk2l8jnzdVLS+BB9Uw=="], - - "@aws-sdk/credential-provider-web-identity": ["@aws-sdk/credential-provider-web-identity@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-9QLTIkDJHHaYL0nyymO41H8g3ui1yz6Y3GmAN1gYQa6plXisuFBnGAbmKVj7zNvjWaOKdF0dV3dd3AFKEDoJ/w=="], - - "@aws-sdk/middleware-bucket-endpoint": ["@aws-sdk/middleware-bucket-endpoint@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-XLSVVfAorUxZh6dzF+HTOp4R1B5EQcdpGcPliWr0KUj2jukgjZEcqbBmjyMF/p9bmyQsONX80iURF1HLAlW0qg=="], - - "@aws-sdk/middleware-expect-continue": ["@aws-sdk/middleware-expect-continue@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-Eb4ELAC23bEQLJmUMYnPWcjD3FZIsmz2svDiXEcxRkQU9r7NRID7pM7C5NPH94wOfiCk0b2Y8rVyFXW0lGQwbA=="], - - "@aws-sdk/middleware-flexible-checksums": ["@aws-sdk/middleware-flexible-checksums@3.940.0", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@aws-crypto/crc32c": "5.2.0", "@aws-crypto/util": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/is-array-buffer": "^4.2.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-WdsxDAVj5qaa5ApAP+JbpCOMHFGSmzjs2Y2OBSbWPeR9Ew7t/Okj+kUub94QJPsgzhvU1/cqNejhsw5VxeFKSQ=="], - - "@aws-sdk/middleware-host-header": ["@aws-sdk/middleware-host-header@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-tAaObaAnsP1XnLGndfkGWFuzrJYuk9W0b/nLvol66t8FZExIAf/WdkT2NNAWOYxljVs++oHnyHBCxIlaHrzSiw=="], - - "@aws-sdk/middleware-location-constraint": ["@aws-sdk/middleware-location-constraint@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-SCMPenDtQMd9o5da9JzkHz838w3327iqXk3cbNnXWqnNRx6unyW8FL0DZ84gIY12kAyVHz5WEqlWuekc15ehfw=="], - - "@aws-sdk/middleware-logger": ["@aws-sdk/middleware-logger@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-aPSJ12d3a3Ea5nyEnLbijCaaYJT2QjQ9iW+zGh5QcZYXmOGWbKVyPSxmVOboZQG+c1M8t6d2O7tqrwzIq8L8qw=="], - - "@aws-sdk/middleware-recursion-detection": ["@aws-sdk/middleware-recursion-detection@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@aws/lambda-invoke-store": "^0.2.0", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-l4aGbHpXM45YNgXggIux1HgsCVAvvBoqHPkqLnqMl9QVapfuSTjJHfDYDsx1Xxct6/m7qSMUzanBALhiaGO2fA=="], - - "@aws-sdk/middleware-sdk-s3": ["@aws-sdk/middleware-sdk-s3@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-arn-parser": "3.893.0", "@smithy/core": "^3.18.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/util-config-provider": "^4.2.0", "@smithy/util-middleware": "^4.2.5", "@smithy/util-stream": "^4.5.6", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-JYkLjgS1wLoKHJ40G63+afM1ehmsPsjcmrHirKh8+kSCx4ip7+nL1e/twV4Zicxr8RJi9Y0Ahq5mDvneilDDKQ=="], - - "@aws-sdk/middleware-ssec": ["@aws-sdk/middleware-ssec@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-/GLC9lZdVp05ozRik5KsuODR/N7j+W+2TbfdFL3iS+7un+gnP6hC8RDOZd6WhpZp7drXQ9guKiTAxkZQwzS8DA=="], - - "@aws-sdk/middleware-user-agent": ["@aws-sdk/middleware-user-agent@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@smithy/core": "^3.18.5", "@smithy/protocol-http": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-nJbLrUj6fY+l2W2rIB9P4Qvpiy0tnTdg/dmixRxrU1z3e8wBdspJlyE+AZN4fuVbeL6rrRrO/zxQC1bB3cw5IA=="], - - "@aws-sdk/nested-clients": ["@aws-sdk/nested-clients@3.940.0", "", { "dependencies": { "@aws-crypto/sha256-browser": "5.2.0", "@aws-crypto/sha256-js": "5.2.0", "@aws-sdk/core": "3.940.0", "@aws-sdk/middleware-host-header": "3.936.0", "@aws-sdk/middleware-logger": "3.936.0", "@aws-sdk/middleware-recursion-detection": "3.936.0", "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/region-config-resolver": "3.936.0", "@aws-sdk/types": "3.936.0", "@aws-sdk/util-endpoints": "3.936.0", "@aws-sdk/util-user-agent-browser": "3.936.0", "@aws-sdk/util-user-agent-node": "3.940.0", "@smithy/config-resolver": "^4.4.3", "@smithy/core": "^3.18.5", "@smithy/fetch-http-handler": "^5.3.6", "@smithy/hash-node": "^4.2.5", "@smithy/invalid-dependency": "^4.2.5", "@smithy/middleware-content-length": "^4.2.5", "@smithy/middleware-endpoint": "^4.3.12", "@smithy/middleware-retry": "^4.4.12", "@smithy/middleware-serde": "^4.2.6", "@smithy/middleware-stack": "^4.2.5", "@smithy/node-config-provider": "^4.3.5", "@smithy/node-http-handler": "^4.4.5", "@smithy/protocol-http": "^5.3.5", "@smithy/smithy-client": "^4.9.8", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-base64": "^4.3.0", "@smithy/util-body-length-browser": "^4.2.0", "@smithy/util-body-length-node": "^4.2.1", "@smithy/util-defaults-mode-browser": "^4.3.11", "@smithy/util-defaults-mode-node": "^4.2.14", "@smithy/util-endpoints": "^3.2.5", "@smithy/util-middleware": "^4.2.5", "@smithy/util-retry": "^4.2.5", "@smithy/util-utf8": "^4.2.0", "tslib": "^2.6.2" } }, "sha512-x0mdv6DkjXqXEcQj3URbCltEzW6hoy/1uIL+i8gExP6YKrnhiZ7SzuB4gPls2UOpK5UqLiqXjhRLfBb1C9i4Dw=="], - - "@aws-sdk/region-config-resolver": ["@aws-sdk/region-config-resolver@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/config-resolver": "^4.4.3", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-wOKhzzWsshXGduxO4pqSiNyL9oUtk4BEvjWm9aaq6Hmfdoydq6v6t0rAGHWPjFwy9z2haovGRi3C8IxdMB4muw=="], - - "@aws-sdk/signature-v4-multi-region": ["@aws-sdk/signature-v4-multi-region@3.940.0", "", { "dependencies": { "@aws-sdk/middleware-sdk-s3": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/protocol-http": "^5.3.5", "@smithy/signature-v4": "^5.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-ugHZEoktD/bG6mdgmhzLDjMP2VrYRAUPRPF1DpCyiZexkH7DCU7XrSJyXMvkcf0DHV+URk0q2sLf/oqn1D2uYw=="], - - "@aws-sdk/token-providers": ["@aws-sdk/token-providers@3.940.0", "", { "dependencies": { "@aws-sdk/core": "3.940.0", "@aws-sdk/nested-clients": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/property-provider": "^4.2.5", "@smithy/shared-ini-file-loader": "^4.4.0", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-k5qbRe/ZFjW9oWEdzLIa2twRVIEx7p/9rutofyrRysrtEnYh3HAWCngAnwbgKMoiwa806UzcTRx0TjyEpnKcCg=="], - - "@aws-sdk/types": ["@aws-sdk/types@3.936.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "tslib": "^2.6.2" } }, "sha512-uz0/VlMd2pP5MepdrHizd+T+OKfyK4r3OA9JI+L/lPKg0YFQosdJNCKisr6o70E3dh8iMpFYxF1UN/4uZsyARg=="], - - "@aws-sdk/util-arn-parser": ["@aws-sdk/util-arn-parser@3.893.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA=="], - - "@aws-sdk/util-endpoints": ["@aws-sdk/util-endpoints@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "@smithy/url-parser": "^4.2.5", "@smithy/util-endpoints": "^3.2.5", "tslib": "^2.6.2" } }, "sha512-0Zx3Ntdpu+z9Wlm7JKUBOzS9EunwKAb4KdGUQQxDqh5Lc3ta5uBoub+FgmVuzwnmBu9U1Os8UuwVTH0Lgu+P5w=="], - - "@aws-sdk/util-locate-window": ["@aws-sdk/util-locate-window@3.965.5", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-WhlJNNINQB+9qtLtZJcpQdgZw3SCDCpXdUJP7cToGwHbCWCnRckGlc6Bx/OhWwIYFNAn+FIydY8SZ0QmVu3xTQ=="], - - "@aws-sdk/util-user-agent-browser": ["@aws-sdk/util-user-agent-browser@3.936.0", "", { "dependencies": { "@aws-sdk/types": "3.936.0", "@smithy/types": "^4.9.0", "bowser": "^2.11.0", "tslib": "^2.6.2" } }, "sha512-eZ/XF6NxMtu+iCma58GRNRxSq4lHo6zHQLOZRIeL/ghqYJirqHdenMOwrzPettj60KWlv827RVebP9oNVrwZbw=="], - - "@aws-sdk/util-user-agent-node": ["@aws-sdk/util-user-agent-node@3.940.0", "", { "dependencies": { "@aws-sdk/middleware-user-agent": "3.940.0", "@aws-sdk/types": "3.936.0", "@smithy/node-config-provider": "^4.3.5", "@smithy/types": "^4.9.0", "tslib": "^2.6.2" }, "peerDependencies": { "aws-crt": ">=1.0.0" }, "optionalPeers": ["aws-crt"] }, "sha512-dlD/F+L/jN26I8Zg5x0oDGJiA+/WEQmnSE27fi5ydvYnpfQLwThtQo9SsNS47XSR/SOULaaoC9qx929rZuo74A=="], - - "@aws-sdk/xml-builder": ["@aws-sdk/xml-builder@3.930.0", "", { "dependencies": { "@smithy/types": "^4.9.0", "fast-xml-parser": "5.2.5", "tslib": "^2.6.2" } }, "sha512-YIfkD17GocxdmlUVc3ia52QhcWuRIUJonbF8A2CYfcWNV3HzvAqpcPeC0bYUhkK+8e8YO1ARnLKZQE0TlwzorA=="], - - "@aws/lambda-invoke-store": ["@aws/lambda-invoke-store@0.2.4", "", {}, "sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ=="], - - "@biomejs/biome": ["@biomejs/biome@2.3.8", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.8", "@biomejs/cli-darwin-x64": "2.3.8", "@biomejs/cli-linux-arm64": "2.3.8", "@biomejs/cli-linux-arm64-musl": "2.3.8", "@biomejs/cli-linux-x64": "2.3.8", "@biomejs/cli-linux-x64-musl": "2.3.8", "@biomejs/cli-win32-arm64": "2.3.8", "@biomejs/cli-win32-x64": "2.3.8" }, "bin": { "biome": "bin/biome" } }, "sha512-Qjsgoe6FEBxWAUzwFGFrB+1+M8y/y5kwmg5CHac+GSVOdmOIqsAiXM5QMVGZJ1eCUCLlPZtq4aFAQ0eawEUuUA=="], - - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HM4Zg9CGQ3txTPflxD19n8MFPrmUAjaC7PQdLkugeeC0cQ+PiVrd7i09gaBS/11QKsTDBJhVg85CEIK9f50Qww=="], - - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-lUDQ03D7y/qEao7RgdjWVGCu+BLYadhKTm40HkpJIi6kn8LSv5PAwRlew/DmwP4YZ9ke9XXoTIQDO1vAnbRZlA=="], - - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-Uo1OJnIkJgSgF+USx970fsM/drtPcQ39I+JO+Fjsaa9ZdCN1oysQmy6oAGbyESlouz+rzEckLTF6DS7cWse95g=="], - - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-PShR4mM0sjksUMyxbyPNMxoKFPVF48fU8Qe8Sfx6w6F42verbwRLbz+QiKNiDPRJwUoMG1nPM50OBL3aOnTevA=="], - - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.8", "", { "os": "linux", "cpu": "x64" }, "sha512-QDPMD5bQz6qOVb3kiBui0zKZXASLo0NIQ9JVJio5RveBEFgDgsvJFUvZIbMbUZT3T00M/1wdzwWXk4GIh0KaAw=="], - - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.8", "", { "os": "linux", "cpu": "x64" }, "sha512-YGLkqU91r1276uwSjiUD/xaVikdxgV1QpsicT0bIA1TaieM6E5ibMZeSyjQ/izBn4tKQthUSsVZacmoJfa3pDA=="], - - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-H4IoCHvL1fXKDrTALeTKMiE7GGWFAraDwBYFquE/L/5r1927Te0mYIGseXi4F+lrrwhSWbSGt5qPFswNoBaCxg=="], - - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.8", "", { "os": "win32", "cpu": "x64" }, "sha512-RguzimPoZWtBapfKhKjcWXBVI91tiSprqdBYu7tWhgN8pKRZhw24rFeNZTNf6UiBfjCYCi9eFQs/JzJZIhuK4w=="], - - "@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], - - "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], - - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], - - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], - - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], - - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], - - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], - - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], - - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], - - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], - - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], - - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], - - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], - - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], - - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], - - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], - - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], - - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], - - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], - - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], - - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], - - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], - - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], - - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], - - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], - - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], - - "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], - - "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="], - - "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], - - "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], - - "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - - "@next/env": ["@next/env@16.0.7", "", {}, "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw=="], - - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg=="], - - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA=="], - - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww=="], - - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g=="], - - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.7", "", { "os": "linux", "cpu": "x64" }, "sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA=="], - - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.7", "", { "os": "linux", "cpu": "x64" }, "sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w=="], - - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q=="], - - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.7", "", { "os": "win32", "cpu": "x64" }, "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug=="], - - "@noble/ciphers": ["@noble/ciphers@1.3.0", "", {}, "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw=="], - - "@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], - - "@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - - "@scure/base": ["@scure/base@1.2.6", "", {}, "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg=="], - - "@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], - - "@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - - "@smithy/abort-controller": ["@smithy/abort-controller@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-xolrFw6b+2iYGl6EcOL7IJY71vvyZ0DJ3mcKtpykqPe2uscwtzDZJa1uVQXyP7w9Dd+kGwYnPbMsJrGISKiY/Q=="], - - "@smithy/chunked-blob-reader": ["@smithy/chunked-blob-reader@5.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-St+kVicSyayWQca+I1rGitaOEH6uKgE8IUWoYnnEX26SWdWQcL6LvMSD19Lg+vYHKdT9B2Zuu7rd3i6Wnyb/iw=="], - - "@smithy/chunked-blob-reader-native": ["@smithy/chunked-blob-reader-native@4.2.3", "", { "dependencies": { "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-jA5k5Udn7Y5717L86h4EIv06wIr3xn8GM1qHRi/Nf31annXcXHJjBKvgztnbn2TxH3xWrPBfgwHsOwZf0UmQWw=="], - - "@smithy/config-resolver": ["@smithy/config-resolver@4.4.13", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "@smithy/util-config-provider": "^4.2.2", "@smithy/util-endpoints": "^3.3.3", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-iIzMC5NmOUP6WL6o8iPBjFhUhBZ9pPjpUpQYWMUFQqKyXXzOftbfK8zcQCz/jFV1Psmf05BK5ypx4K2r4Tnwdg=="], - - "@smithy/core": ["@smithy/core@3.23.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-base64": "^4.3.2", "@smithy/util-body-length-browser": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-stream": "^4.5.20", "@smithy/util-utf8": "^4.2.2", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-o9VycsYNtgC+Dy3I0yrwCqv9CWicDnke0L7EVOrZtJpjb2t0EjaEofmMrYc0T1Kn3yk32zm6cspxF9u9Bj7e5w=="], - - "@smithy/credential-provider-imds": ["@smithy/credential-provider-imds@4.2.12", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-cr2lR792vNZcYMriSIj+Um3x9KWrjcu98kn234xA6reOAFMmbRpQMOv8KPgEmLLtx3eldU6c5wALKFqNOhugmg=="], - - "@smithy/eventstream-codec": ["@smithy/eventstream-codec@4.2.12", "", { "dependencies": { "@aws-crypto/crc32": "5.2.0", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FE3bZdEl62ojmy8x4FHqxq2+BuOHlcxiH5vaZ6aqHJr3AIZzwF5jfx8dEiU/X0a8RboyNDjmXjlbr8AdEyLgiA=="], - - "@smithy/eventstream-serde-browser": ["@smithy/eventstream-serde-browser@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-XUSuMxlTxV5pp4VpqZf6Sa3vT/Q75FVkLSpSSE3KkWBvAQWeuWt1msTv8fJfgA4/jcJhrbrbMzN1AC/hvPmm5A=="], - - "@smithy/eventstream-serde-config-resolver": ["@smithy/eventstream-serde-config-resolver@4.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-7epsAZ3QvfHkngz6RXQYseyZYHlmWXSTPOfPmXkiS+zA6TBNo1awUaMFL9vxyXlGdoELmCZyZe1nQE+imbmV+Q=="], - - "@smithy/eventstream-serde-node": ["@smithy/eventstream-serde-node@4.2.12", "", { "dependencies": { "@smithy/eventstream-serde-universal": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-D1pFuExo31854eAvg89KMn9Oab/wEeJR6Buy32B49A9Ogdtx5fwZPqBHUlDzaCDpycTFk2+fSQgX689Qsk7UGA=="], - - "@smithy/eventstream-serde-universal": ["@smithy/eventstream-serde-universal@4.2.12", "", { "dependencies": { "@smithy/eventstream-codec": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-+yNuTiyBACxOJUTvbsNsSOfH9G9oKbaJE1lNL3YHpGcuucl6rPZMi3nrpehpVOVR2E07YqFFmtwpImtpzlouHQ=="], - - "@smithy/fetch-http-handler": ["@smithy/fetch-http-handler@5.3.15", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "tslib": "^2.6.2" } }, "sha512-T4jFU5N/yiIfrtrsb9uOQn7RdELdM/7HbyLNr6uO/mpkj1ctiVs7CihVr51w4LyQlXWDpXFn4BElf1WmQvZu/A=="], - - "@smithy/hash-blob-browser": ["@smithy/hash-blob-browser@4.2.13", "", { "dependencies": { "@smithy/chunked-blob-reader": "^5.2.2", "@smithy/chunked-blob-reader-native": "^4.2.3", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YrF4zWKh+ghLuquldj6e/RzE3xZYL8wIPfkt0MqCRphVICjyyjH8OwKD7LLlKpVEbk4FLizFfC1+gwK6XQdR3g=="], - - "@smithy/hash-node": ["@smithy/hash-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-QhBYbGrbxTkZ43QoTPrK72DoYviDeg6YKDrHTMJbbC+A0sml3kSjzFtXP7BtbyJnXojLfTQldGdUR0RGD8dA3w=="], - - "@smithy/hash-stream-node": ["@smithy/hash-stream-node@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-O3YbmGExeafuM/kP7Y8r6+1y0hIh3/zn6GROx0uNlB54K9oihAL75Qtc+jFfLNliTi6pxOAYZrRKD9A7iA6UFw=="], - - "@smithy/invalid-dependency": ["@smithy/invalid-dependency@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-/4F1zb7Z8LOu1PalTdESFHR0RbPwHd3FcaG1sI3UEIriQTWakysgJr65lc1jj6QY5ye7aFsisajotH6UhWfm/g=="], - - "@smithy/is-array-buffer": ["@smithy/is-array-buffer@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow=="], - - "@smithy/md5-js": ["@smithy/md5-js@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-W/oIpHCpWU2+iAkfZYyGWE+qkpuf3vEXHLxQQDx9FPNZTTdnul0dZ2d/gUFrtQ5je1G2kp4cjG0/24YueG2LbQ=="], - - "@smithy/middleware-content-length": ["@smithy/middleware-content-length@4.2.12", "", { "dependencies": { "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-YE58Yz+cvFInWI/wOTrB+DbvUVz/pLn5mC5MvOV4fdRUc6qGwygyngcucRQjAhiCEbmfLOXX0gntSIcgMvAjmA=="], - - "@smithy/middleware-endpoint": ["@smithy/middleware-endpoint@4.4.27", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-serde": "^4.2.15", "@smithy/node-config-provider": "^4.3.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "@smithy/url-parser": "^4.2.12", "@smithy/util-middleware": "^4.2.12", "tslib": "^2.6.2" } }, "sha512-T3TFfUgXQlpcg+UdzcAISdZpj4Z+XECZ/cefgA6wLBd6V4lRi0svN2hBouN/be9dXQ31X4sLWz3fAQDf+nt6BA=="], - - "@smithy/middleware-retry": ["@smithy/middleware-retry@4.4.44", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/protocol-http": "^5.3.12", "@smithy/service-error-classification": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "@smithy/util-middleware": "^4.2.12", "@smithy/util-retry": "^4.2.12", "@smithy/uuid": "^1.1.2", "tslib": "^2.6.2" } }, "sha512-Y1Rav7m5CFRPQyM4CI0koD/bXjyjJu3EQxZZhtLGD88WIrBrQ7kqXM96ncd6rYnojwOo/u9MXu57JrEvu/nLrA=="], - - "@smithy/middleware-serde": ["@smithy/middleware-serde@4.2.15", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-ExYhcltZSli0pgAKOpQQe1DLFBLryeZ22605y/YS+mQpdNWekum9Ujb/jMKfJKgjtz1AZldtwA/wCYuKJgjjlg=="], - - "@smithy/middleware-stack": ["@smithy/middleware-stack@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-kruC5gRHwsCOuyCd4ouQxYjgRAym2uDlCvQ5acuMtRrcdfg7mFBg6blaxcJ09STpt3ziEkis6bhg1uwrWU7txw=="], - - "@smithy/node-config-provider": ["@smithy/node-config-provider@4.3.12", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/shared-ini-file-loader": "^4.4.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-tr2oKX2xMcO+rBOjobSwVAkV05SIfUKz8iI53rzxEmgW3GOOPOv0UioSDk+J8OpRQnpnhsO3Af6IEBabQBVmiw=="], - - "@smithy/node-http-handler": ["@smithy/node-http-handler@4.5.0", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/querystring-builder": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Rnq9vQWiR1+/I6NZZMNzJHV6pZYyEHt2ZnuV3MG8z2NNenC4i/8Kzttz7CjZiHSmsN5frhXhg17z3Zqjjhmz1A=="], - - "@smithy/property-provider": ["@smithy/property-provider@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-jqve46eYU1v7pZ5BM+fmkbq3DerkSluPr5EhvOcHxygxzD05ByDRppRwRPPpFrsFo5yDtCYLKu+kreHKVrvc7A=="], - - "@smithy/protocol-http": ["@smithy/protocol-http@5.3.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-fit0GZK9I1xoRlR4jXmbLhoN0OdEpa96ul8M65XdmXnxXkuMxM0Y8HDT0Fh0Xb4I85MBvBClOzgSrV1X2s1Hxw=="], - - "@smithy/querystring-builder": ["@smithy/querystring-builder@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "@smithy/util-uri-escape": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-6wTZjGABQufekycfDGMEB84BgtdOE/rCVTov+EDXQ8NHKTUNIp/j27IliwP7tjIU9LR+sSzyGBOXjeEtVgzCHg=="], - - "@smithy/querystring-parser": ["@smithy/querystring-parser@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-P2OdvrgiAKpkPNKlKUtWbNZKB1XjPxM086NeVhK+W+wI46pIKdWBe5QyXvhUm3MEcyS/rkLvY8rZzyUdmyDZBw=="], - - "@smithy/service-error-classification": ["@smithy/service-error-classification@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1" } }, "sha512-LlP29oSQN0Tw0b6D0Xo6BIikBswuIiGYbRACy5ujw/JgWSzTdYj46U83ssf6Ux0GyNJVivs2uReU8pt7Eu9okQ=="], - - "@smithy/shared-ini-file-loader": ["@smithy/shared-ini-file-loader@4.4.7", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-HrOKWsUb+otTeo1HxVWeEb99t5ER1XrBi/xka2Wv6NVmTbuCUC1dvlrksdvxFtODLBjsC+PHK+fuy2x/7Ynyiw=="], - - "@smithy/signature-v4": ["@smithy/signature-v4@5.3.12", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-middleware": "^4.2.12", "@smithy/util-uri-escape": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-B/FBwO3MVOL00DaRSXfXfa/TRXRheagt/q5A2NM13u7q+sHS59EOVGQNfG7DkmVtdQm5m3vOosoKAXSqn/OEgw=="], - - "@smithy/smithy-client": ["@smithy/smithy-client@4.12.7", "", { "dependencies": { "@smithy/core": "^3.23.12", "@smithy/middleware-endpoint": "^4.4.27", "@smithy/middleware-stack": "^4.2.12", "@smithy/protocol-http": "^5.3.12", "@smithy/types": "^4.13.1", "@smithy/util-stream": "^4.5.20", "tslib": "^2.6.2" } }, "sha512-q3gqnwml60G44FECaEEsdQMplYhDMZYCtYhMCzadCnRnnHIobZJjegmdoUo6ieLQlPUzvrMdIJUpx6DoPmzANQ=="], - - "@smithy/types": ["@smithy/types@4.13.1", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g=="], - - "@smithy/url-parser": ["@smithy/url-parser@4.2.12", "", { "dependencies": { "@smithy/querystring-parser": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-wOPKPEpso+doCZGIlr+e1lVI6+9VAKfL4kZWFgzVgGWY2hZxshNKod4l2LXS3PRC9otH/JRSjtEHqQ/7eLciRA=="], - - "@smithy/util-base64": ["@smithy/util-base64@4.3.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ=="], - - "@smithy/util-body-length-browser": ["@smithy/util-body-length-browser@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ=="], - - "@smithy/util-body-length-node": ["@smithy/util-body-length-node@4.2.3", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-ZkJGvqBzMHVHE7r/hcuCxlTY8pQr1kMtdsVPs7ex4mMU+EAbcXppfo5NmyxMYi2XU49eqaz56j2gsk4dHHPG/g=="], - - "@smithy/util-buffer-from": ["@smithy/util-buffer-from@4.2.2", "", { "dependencies": { "@smithy/is-array-buffer": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q=="], - - "@smithy/util-config-provider": ["@smithy/util-config-provider@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-dWU03V3XUprJwaUIFVv4iOnS1FC9HnMHDfUrlNDSh4315v0cWyaIErP8KiqGVbf5z+JupoVpNM7ZB3jFiTejvQ=="], - - "@smithy/util-defaults-mode-browser": ["@smithy/util-defaults-mode-browser@4.3.43", "", { "dependencies": { "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Qd/0wCKMaXxev/z00TvNzGCH2jlKKKxXP1aDxB6oKwSQthe3Og2dMhSayGCnsma1bK/kQX1+X7SMP99t6FgiiQ=="], - - "@smithy/util-defaults-mode-node": ["@smithy/util-defaults-mode-node@4.2.47", "", { "dependencies": { "@smithy/config-resolver": "^4.4.13", "@smithy/credential-provider-imds": "^4.2.12", "@smithy/node-config-provider": "^4.3.12", "@smithy/property-provider": "^4.2.12", "@smithy/smithy-client": "^4.12.7", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-qSRbYp1EQ7th+sPFuVcVO05AE0QH635hycdEXlpzIahqHHf2Fyd/Zl+8v0XYMJ3cgDVPa0lkMefU7oNUjAP+DQ=="], - - "@smithy/util-endpoints": ["@smithy/util-endpoints@3.3.3", "", { "dependencies": { "@smithy/node-config-provider": "^4.3.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-VACQVe50j0HZPjpwWcjyT51KUQ4AnsvEaQ2lKHOSL4mNLD0G9BjEniQ+yCt1qqfKfiAHRAts26ud7hBjamrwig=="], - - "@smithy/util-hex-encoding": ["@smithy/util-hex-encoding@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg=="], - - "@smithy/util-middleware": ["@smithy/util-middleware@4.2.12", "", { "dependencies": { "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-Er805uFUOvgc0l8nv0e0su0VFISoxhJ/AwOn3gL2NWNY2LUEldP5WtVcRYSQBcjg0y9NfG8JYrCJaYDpupBHJQ=="], - - "@smithy/util-retry": ["@smithy/util-retry@4.2.12", "", { "dependencies": { "@smithy/service-error-classification": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-1zopLDUEOwumjcHdJ1mwBHddubYF8GMQvstVCLC54Y46rqoHwlIU+8ZzUeaBcD+WCJHyDGSeZ2ml9YSe9aqcoQ=="], - - "@smithy/util-stream": ["@smithy/util-stream@4.5.20", "", { "dependencies": { "@smithy/fetch-http-handler": "^5.3.15", "@smithy/node-http-handler": "^4.5.0", "@smithy/types": "^4.13.1", "@smithy/util-base64": "^4.3.2", "@smithy/util-buffer-from": "^4.2.2", "@smithy/util-hex-encoding": "^4.2.2", "@smithy/util-utf8": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-4yXLm5n/B5SRBR2p8cZ90Sbv4zL4NKsgxdzCzp/83cXw2KxLEumt5p+GAVyRNZgQOSrzXn9ARpO0lUe8XSlSDw=="], - - "@smithy/util-uri-escape": ["@smithy/util-uri-escape@4.2.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw=="], - - "@smithy/util-utf8": ["@smithy/util-utf8@4.2.2", "", { "dependencies": { "@smithy/util-buffer-from": "^4.2.2", "tslib": "^2.6.2" } }, "sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw=="], - - "@smithy/util-waiter": ["@smithy/util-waiter@4.2.13", "", { "dependencies": { "@smithy/abort-controller": "^4.2.12", "@smithy/types": "^4.13.1", "tslib": "^2.6.2" } }, "sha512-2zdZ9DTHngRtcYxJK1GUDxruNr53kv5W2Lupe0LMU+Imr6ohQg8M2T14MNkj1Y0wS3FFwpgpGQyvuaMF7CiTmQ=="], - - "@smithy/uuid": ["@smithy/uuid@1.1.2", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g=="], - - "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], - - "@tailwindcss/node": ["@tailwindcss/node@4.1.17", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "enhanced-resolve": "^5.18.3", "jiti": "^2.6.1", "lightningcss": "1.30.2", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.1.17" } }, "sha512-csIkHIgLb3JisEFQ0vxr2Y57GUNYh447C8xzwj89U/8fdW8LhProdxvnVH6U8M2Y73QKiTIH+LWbK3V2BBZsAg=="], - - "@tailwindcss/oxide": ["@tailwindcss/oxide@4.1.17", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.1.17", "@tailwindcss/oxide-darwin-arm64": "4.1.17", "@tailwindcss/oxide-darwin-x64": "4.1.17", "@tailwindcss/oxide-freebsd-x64": "4.1.17", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.17", "@tailwindcss/oxide-linux-arm64-gnu": "4.1.17", "@tailwindcss/oxide-linux-arm64-musl": "4.1.17", "@tailwindcss/oxide-linux-x64-gnu": "4.1.17", "@tailwindcss/oxide-linux-x64-musl": "4.1.17", "@tailwindcss/oxide-wasm32-wasi": "4.1.17", "@tailwindcss/oxide-win32-arm64-msvc": "4.1.17", "@tailwindcss/oxide-win32-x64-msvc": "4.1.17" } }, "sha512-F0F7d01fmkQhsTjXezGBLdrl1KresJTcI3DB8EkScCldyKp3Msz4hub4uyYaVnk88BAS1g5DQjjF6F5qczheLA=="], - - "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.1.17", "", { "os": "android", "cpu": "arm64" }, "sha512-BMqpkJHgOZ5z78qqiGE6ZIRExyaHyuxjgrJ6eBO5+hfrfGkuya0lYfw8fRHG77gdTjWkNWEEm+qeG2cDMxArLQ=="], - - "@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.1.17", "", { "os": "darwin", "cpu": "arm64" }, "sha512-EquyumkQweUBNk1zGEU/wfZo2qkp/nQKRZM8bUYO0J+Lums5+wl2CcG1f9BgAjn/u9pJzdYddHWBiFXJTcxmOg=="], - - "@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.1.17", "", { "os": "darwin", "cpu": "x64" }, "sha512-gdhEPLzke2Pog8s12oADwYu0IAw04Y2tlmgVzIN0+046ytcgx8uZmCzEg4VcQh+AHKiS7xaL8kGo/QTiNEGRog=="], - - "@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.1.17", "", { "os": "freebsd", "cpu": "x64" }, "sha512-hxGS81KskMxML9DXsaXT1H0DyA+ZBIbyG/sSAjWNe2EDl7TkPOBI42GBV3u38itzGUOmFfCzk1iAjDXds8Oh0g=="], - - "@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.1.17", "", { "os": "linux", "cpu": "arm" }, "sha512-k7jWk5E3ldAdw0cNglhjSgv501u7yrMf8oeZ0cElhxU6Y2o7f8yqelOp3fhf7evjIS6ujTI3U8pKUXV2I4iXHQ=="], - - "@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HVDOm/mxK6+TbARwdW17WrgDYEGzmoYayrCgmLEw7FxTPLcp/glBisuyWkFz/jb7ZfiAXAXUACfyItn+nTgsdQ=="], - - "@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.1.17", "", { "os": "linux", "cpu": "arm64" }, "sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg=="], - - "@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ=="], - - "@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.1.17", "", { "os": "linux", "cpu": "x64" }, "sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ=="], - - "@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.1.17", "", { "dependencies": { "@emnapi/core": "^1.6.0", "@emnapi/runtime": "^1.6.0", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.0.7", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.4.0" }, "cpu": "none" }, "sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg=="], - - "@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.1.17", "", { "os": "win32", "cpu": "arm64" }, "sha512-JU5AHr7gKbZlOGvMdb4722/0aYbU+tN6lv1kONx0JK2cGsh7g148zVWLM0IKR3NeKLv+L90chBVYcJ8uJWbC9A=="], - - "@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.1.17", "", { "os": "win32", "cpu": "x64" }, "sha512-SKWM4waLuqx0IH+FMDUw6R66Hu4OuTALFgnleKbqhgGU30DY20NORZMZUKgLRjQXNN2TLzKvh48QXTig4h4bGw=="], - - "@tailwindcss/postcss": ["@tailwindcss/postcss@4.1.17", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.1.17", "@tailwindcss/oxide": "4.1.17", "postcss": "^8.4.41", "tailwindcss": "4.1.17" } }, "sha512-+nKl9N9mN5uJ+M7dBOOCzINw94MPstNR/GtIhz1fpZysxL/4a+No64jCBD6CPN+bIHWFx3KWuu8XJRrj/572Dw=="], - - "@types/node": ["@types/node@20.19.25", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-ZsJzA5thDQMSQO788d7IocwwQbI8B5OPzmqNvpf3NY/+MHDAS759Wo0gd2WQeXYt5AAAQjzcrTVC6SKCuYgoCQ=="], - - "@types/react": ["@types/react@19.2.7", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg=="], - - "@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="], - - "abitype": ["abitype@1.1.0", "", { "peerDependencies": { "typescript": ">=5.0.4", "zod": "^3.22.0 || ^4.0.0" }, "optionalPeers": ["typescript", "zod"] }, "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A=="], - - "bowser": ["bowser@2.14.1", "", {}, "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg=="], - - "caniuse-lite": ["caniuse-lite@1.0.30001781", "", {}, "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw=="], - - "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="], - - "csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="], - - "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], - - "enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="], - - "eventemitter3": ["eventemitter3@5.0.1", "", {}, "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA=="], - - "fast-xml-parser": ["fast-xml-parser@5.2.5", "", { "dependencies": { "strnum": "^2.1.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ=="], - - "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], - - "isows": ["isows@1.0.7", "", { "peerDependencies": { "ws": "*" } }, "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg=="], - - "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], - - "lightningcss": ["lightningcss@1.30.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.30.2", "lightningcss-darwin-arm64": "1.30.2", "lightningcss-darwin-x64": "1.30.2", "lightningcss-freebsd-x64": "1.30.2", "lightningcss-linux-arm-gnueabihf": "1.30.2", "lightningcss-linux-arm64-gnu": "1.30.2", "lightningcss-linux-arm64-musl": "1.30.2", "lightningcss-linux-x64-gnu": "1.30.2", "lightningcss-linux-x64-musl": "1.30.2", "lightningcss-win32-arm64-msvc": "1.30.2", "lightningcss-win32-x64-msvc": "1.30.2" } }, "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ=="], - - "lightningcss-android-arm64": ["lightningcss-android-arm64@1.30.2", "", { "os": "android", "cpu": "arm64" }, "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A=="], - - "lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.30.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA=="], - - "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.30.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ=="], - - "lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.30.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA=="], - - "lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.30.2", "", { "os": "linux", "cpu": "arm" }, "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA=="], - - "lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A=="], - - "lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.30.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA=="], - - "lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w=="], - - "lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.30.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA=="], - - "lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.30.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ=="], - - "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.30.2", "", { "os": "win32", "cpu": "x64" }, "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw=="], - - "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], - - "nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="], - - "next": ["next@16.0.7", "", { "dependencies": { "@next/env": "16.0.7", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.7", "@next/swc-darwin-x64": "16.0.7", "@next/swc-linux-arm64-gnu": "16.0.7", "@next/swc-linux-arm64-musl": "16.0.7", "@next/swc-linux-x64-gnu": "16.0.7", "@next/swc-linux-x64-musl": "16.0.7", "@next/swc-win32-arm64-msvc": "16.0.7", "@next/swc-win32-x64-msvc": "16.0.7", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A=="], - - "ox": ["ox@0.9.6", "", { "dependencies": { "@adraffy/ens-normalize": "^1.11.0", "@noble/ciphers": "^1.3.0", "@noble/curves": "1.9.1", "@noble/hashes": "^1.8.0", "@scure/bip32": "^1.7.0", "@scure/bip39": "^1.6.0", "abitype": "^1.0.9", "eventemitter3": "5.0.1" }, "peerDependencies": { "typescript": ">=5.4.0" }, "optionalPeers": ["typescript"] }, "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg=="], - - "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], - - "postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="], - - "react": ["react@19.2.1", "", {}, "sha512-DGrYcCWK7tvYMnWh79yrPHt+vdx9tY+1gPZa7nJQtO/p8bLTDaHp4dzwEhQB7pZ4Xe3ok4XKuEPrVuc+wlpkmw=="], - - "react-dom": ["react-dom@19.2.1", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.1" } }, "sha512-ibrK8llX2a4eOskq1mXKu/TGZj9qzomO+sNfO98M6d9zIPOEhlBkMkBUBLd1vgS0gQsLDBzA+8jJBVXDnfHmJg=="], - - "scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="], - - "semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="], - - "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], - - "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], - - "strnum": ["strnum@2.2.2", "", {}, "sha512-DnR90I+jtXNSTXWdwrEy9FakW7UX+qUZg28gj5fk2vxxl7uS/3bpI4fjFYVmdK9etptYBPNkpahuQnEwhwECqA=="], - - "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="], - - "tailwindcss": ["tailwindcss@4.1.17", "", {}, "sha512-j9Ee2YjuQqYT9bbRTfTZht9W/ytp5H+jJpZKiYdP/bpnXARAuELt9ofP0lPnmHjbga7SNQIxdTAXCmtKVYjN+Q=="], - - "tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="], - - "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - - "undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - - "viem": ["viem@2.40.3", "", { "dependencies": { "@noble/curves": "1.9.1", "@noble/hashes": "1.8.0", "@scure/bip32": "1.7.0", "@scure/bip39": "1.6.0", "abitype": "1.1.0", "isows": "1.0.7", "ox": "0.9.6", "ws": "8.18.3" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-feYfEpbgjRkZYQpwcgxqkWzjxHI5LSDAjcGetHHwDRuX9BRQHUdV8ohrCosCYpdEhus/RknD3/bOd4qLYVPPuA=="], - - "ws": ["ws@8.18.3", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg=="], - - "@aws-crypto/sha1-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - - "@aws-crypto/sha256-browser/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - - "@aws-crypto/util/@smithy/util-utf8": ["@smithy/util-utf8@2.3.0", "", { "dependencies": { "@smithy/util-buffer-from": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], - - "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="], - - "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.1", "", { "dependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1", "@tybys/wasm-util": "^0.10.1" }, "bundled": true }, "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A=="], - - "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], - - "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - - "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - - "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from": ["@smithy/util-buffer-from@2.2.0", "", { "dependencies": { "@smithy/is-array-buffer": "^2.2.0", "tslib": "^2.6.2" } }, "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA=="], - - "@aws-crypto/sha1-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - - "@aws-crypto/sha256-browser/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - - "@aws-crypto/util/@smithy/util-utf8/@smithy/util-buffer-from/@smithy/is-array-buffer": ["@smithy/is-array-buffer@2.2.0", "", { "dependencies": { "tslib": "^2.6.2" } }, "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA=="], - } -} diff --git a/src/app/api/vibenet/faucet/drip-nfv/route.ts b/src/app/api/vibenet/faucet/drip-nfv/route.ts new file mode 100644 index 00000000..5c672c37 --- /dev/null +++ b/src/app/api/vibenet/faucet/drip-nfv/route.ts @@ -0,0 +1,67 @@ +import { NextResponse } from "next/server"; +import { encodeFunctionData, isAddress, parseAbi } from "viem"; +import { + checkCooldown, + clientIp, + getAddrCooldownSecs, + getContractAddress, + getIpCooldownSecs, + getPublicClient, + getWalletClient, + recordDrip, +} from "@/lib/vibenet/faucet"; + +export const dynamic = "force-dynamic"; + +const MINT_ABI = parseAbi([ + "function mint(address to) external returns (uint256)", +]); + +export async function POST(req: Request) { + const body = await req.json().catch(() => ({})); + const { address } = body as { address?: string }; + + if (!address || !isAddress(address)) { + return NextResponse.json({ error: "Invalid address" }, { status: 400 }); + } + + const nfvAddress = await getContractAddress("nfv"); + if (!nfvAddress) { + return NextResponse.json( + { error: "NFV contract not yet deployed. Try again shortly." }, + { status: 503 }, + ); + } + + const ip = clientIp(req); + const cooldownErr = checkCooldown( + "nfv", + ip, + address, + getIpCooldownSecs(), + getAddrCooldownSecs(), + ); + if (cooldownErr) { + return NextResponse.json({ error: cooldownErr }, { status: 429 }); + } + + try { + const wallet = getWalletClient(); + const publicClient = getPublicClient(); + const data = encodeFunctionData({ + abi: MINT_ABI, + functionName: "mint", + args: [address], + }); + const hash = await wallet.sendTransaction({ to: nfvAddress, data }); + await publicClient.waitForTransactionReceipt({ hash }); + recordDrip("nfv", ip, address); + return NextResponse.json({ + tx_hash: hash, + to: address, + nfv_address: nfvAddress, + }); + } catch (e) { + return NextResponse.json({ error: String(e) }, { status: 500 }); + } +} diff --git a/src/app/api/vibenet/faucet/status/route.ts b/src/app/api/vibenet/faucet/status/route.ts index ed6c3156..8bdf1421 100644 --- a/src/app/api/vibenet/faucet/status/route.ts +++ b/src/app/api/vibenet/faucet/status/route.ts @@ -16,7 +16,10 @@ export async function GET() { const address = getFaucetAddress(); const client = getPublicClient(); const balance = await client.getBalance({ address }); - const usdvAddress = await getContractAddress("usdv"); + const [usdvAddress, nfvAddress] = await Promise.all([ + getContractAddress("usdv"), + getContractAddress("nfv"), + ]); return NextResponse.json({ address, @@ -29,6 +32,7 @@ export async function GET() { usdv_address: usdvAddress, usdv_drip_units: getUsdvDripUnits().toString(), }), + ...(nfvAddress && { nfv_address: nfvAddress }), }); } catch (e) { return NextResponse.json({ error: String(e) }, { status: 500 }); diff --git a/src/app/explorer/page.tsx b/src/app/explorer/page.tsx index 6fec7e44..b7b3a8e0 100644 --- a/src/app/explorer/page.tsx +++ b/src/app/explorer/page.tsx @@ -1,6 +1,7 @@ "use client"; import Link from "next/link"; +import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; interface BlockRow { @@ -45,6 +46,73 @@ const FAUCET_URL = ? "https://faucet.vibes.base.org" : "/faucet"; +function classifyQuery( + q: string, +): { kind: "block" | "tx" | "address"; value: string } | null { + const v = q.trim(); + if (!v) return null; + // 0x-prefixed: 40 hex chars = address, 64 hex chars = hash (block or tx) + if (/^0x[a-fA-F0-9]{40}$/.test(v)) return { kind: "address", value: v }; + if (/^0x[a-fA-F0-9]{64}$/.test(v)) return { kind: "tx", value: v }; // try tx first; falls through to block on 404 + // Plain integer = block number + if (/^\d+$/.test(v)) return { kind: "block", value: v }; + return null; +} + +function SearchBar() { + const router = useRouter(); + const [query, setQuery] = useState(""); + const [error, setError] = useState(null); + + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + const cls = classifyQuery(query); + if (!cls) { + setError("Enter a block number, block hash, tx hash, or address"); + return; + } + if (cls.kind === "address") { + router.push(`/explorer/address/${cls.value}`); + return; + } + if (cls.kind === "block") { + router.push(`/explorer/block/${cls.value}`); + return; + } + // 64-char hash: try tx first; if not found, try as block hash + try { + const res = await fetch(`/api/vibenet/explorer/tx/${cls.value}`); + if (res.ok) { + router.push(`/explorer/tx/${cls.value}`); + } else { + router.push(`/explorer/block/${cls.value}`); + } + } catch { + router.push(`/explorer/block/${cls.value}`); + } + }; + + return ( +
+ setQuery(e.target.value)} + placeholder="Block number, block hash, tx hash, or address" + className="flex-1 px-4 py-2 rounded-lg bg-white/5 border border-white/10 text-white text-sm placeholder-gray-600 focus:outline-none focus:border-blue-500 font-mono" + /> + + {error &&

{error}

} +
+ ); +} + export default function ExplorerPage() { const [blocks, setBlocks] = useState([]); const [txs, setTxs] = useState([]); @@ -90,6 +158,8 @@ export default function ExplorerPage() {
+ + {stats && (
{[ diff --git a/src/app/faucet/page.tsx b/src/app/faucet/page.tsx index 162078e3..171b119c 100644 --- a/src/app/faucet/page.tsx +++ b/src/app/faucet/page.tsx @@ -11,8 +11,11 @@ interface FaucetStatus { addr_cooldown_secs: number; usdv_address?: string; usdv_drip_units?: string; + nfv_address?: string; } +type Token = "eth" | "usdv" | "nfv"; + interface DripResult { ok: boolean; message: string; @@ -40,7 +43,7 @@ function weiToEth(wei: string): string { export default function FaucetPage() { const [status, setStatus] = useState(null); const [address, setAddress] = useState(""); - const [loading, setLoading] = useState<"eth" | "usdv" | null>(null); + const [loading, setLoading] = useState(null); const [result, setResult] = useState(null); useEffect(() => { @@ -54,17 +57,18 @@ export default function FaucetPage() { return () => clearInterval(t); }, []); - const drip = async (token: "eth" | "usdv") => { + const drip = async (token: Token) => { if (!address.match(/^0x[a-fA-F0-9]{40}$/)) { setResult({ ok: false, message: "Invalid address" }); return; } setLoading(token); setResult(null); - const endpoint = - token === "eth" - ? "/api/vibenet/faucet/drip" - : "/api/vibenet/faucet/drip-usdv"; + const endpoint = { + eth: "/api/vibenet/faucet/drip", + usdv: "/api/vibenet/faucet/drip-usdv", + nfv: "/api/vibenet/faucet/drip-nfv", + }[token]; try { const res = await fetch(endpoint, { method: "POST", @@ -150,6 +154,20 @@ export default function FaucetPage() {
)} + {status.nfv_address && ( + + )}
)} @@ -170,23 +188,33 @@ export default function FaucetPage() { />
-
+
{status?.usdv_address && ( + )} + {status?.nfv_address && ( + )}
diff --git a/src/app/page.tsx b/src/app/page.tsx index 4d7f6ec4..5b184664 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -28,15 +28,22 @@ interface ContractEntry { address: string; } -const FAUCET_URL = - typeof window !== "undefined" && window.location.hostname === "vibes.base.org" - ? "https://faucet.vibes.base.org" - : `${typeof window !== "undefined" ? window.location.protocol : "http:"}//${typeof window !== "undefined" ? window.location.hostname : "localhost"}:${typeof window !== "undefined" ? (Number(window.location.port || 80) + 3).toString() : "18083"}`; +// Subdomain hosts use absolute URLs; local dev uses same-origin paths. +function isProd() { + if (typeof window === "undefined") return false; + const h = window.location.hostname; + return h === "vibes.base.org" || h.endsWith(".vibes.base.org"); +} + +const FAUCET_URL = isProd() ? "https://faucet.vibes.base.org" : "/faucet"; +const EXPLORER_URL = isProd() ? "https://explorer.vibes.base.org" : "/explorer"; -const EXPLORER_URL = - typeof window !== "undefined" && window.location.hostname === "vibes.base.org" - ? "https://explorer.vibes.base.org" - : `${typeof window !== "undefined" ? window.location.protocol : "http:"}//${typeof window !== "undefined" ? window.location.hostname : "localhost"}/explorer`; +// Local dev: HTTP RPC is on port + 2 (proxyd on 18082 when UI is on 18080). +function localRpcUrl(): string { + if (typeof window === "undefined") return "http://localhost:18082"; + const uiPort = Number(window.location.port || 80); + return `${window.location.protocol}//${window.location.hostname}:${uiPort + 2}`; +} export default function VibeHomePage() { const [config, setConfig] = useState(null); @@ -52,31 +59,36 @@ export default function VibeHomePage() { .then(setConfig) .catch(() => null); + // Filter out metadata keys (_branch, _commit, faucetAddress) — only show real contracts. fetch("/api/vibenet/contracts") .then((r) => r.json()) .then((data: Record) => setContracts( - Object.entries(data).map(([name, address]) => ({ name, address })), + Object.entries(data) + .filter( + ([k, v]) => + !k.startsWith("_") && + k !== "faucetAddress" && + typeof v === "string" && + v.startsWith("0x"), + ) + .map(([name, address]) => ({ name, address })), ), ) .catch(() => null); - }, []); - - useEffect(() => { - if (!config) return; - const rpc = - window.location.hostname === "vibes.base.org" - ? "https://rpc.vibes.base.org" - : `${window.location.protocol}//${window.location.hostname}:${Number(window.location.port || 80) + 1}`; - setRpcUrl(rpc); - fetch("/api/vibenet/config") + // Pull chain ID from faucet/status (vibenet.yaml doesn't include it). + fetch("/api/vibenet/faucet/status") .then((r) => r.json()) - .then((cfg: VibeConfig & { chain_id?: number }) => { - if (cfg.chain_id) setChainId(cfg.chain_id.toString()); + .then((d: { chain_id?: number }) => { + if (d.chain_id) setChainId(d.chain_id.toString()); }) .catch(() => null); - }, [config]); + }, []); + + useEffect(() => { + setRpcUrl(isProd() ? "https://rpc.vibes.base.org" : localRpcUrl()); + }, []); const copyToClipboard = (text: string, key: string) => { navigator.clipboard.writeText(text).then(() => { From a195c406afd35935d62ff0863ccff83df9dba2af Mon Sep 17 00:00:00 2001 From: Chris Hunter Date: Fri, 15 May 2026 10:22:53 -0400 Subject: [PATCH 6/7] fix: correct ERC-20/721 Transfer topic constant in indexer The hardcoded ERC20_TRANSFER topic had a typo in the second half (0x...00b2d2b3179ef118821c0b55d5 instead of the real 0x...163c4a11628f55a4df523b3ef). Effect: USDV/NFV mint events were silently dropped from address_activity, so address pages didn't show token-to/token-from entries. The correct hash is keccak256("Transfer(address,address,uint256)"). Co-Authored-By: Claude Sonnet 4.6 (1M context) --- scripts/indexer.mjs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/indexer.mjs b/scripts/indexer.mjs index 22e1a5ce..20a146ba 100644 --- a/scripts/indexer.mjs +++ b/scripts/indexer.mjs @@ -18,7 +18,8 @@ const CHAIN_ID = Number(process.env.VIBESCAN_CHAIN_ID ?? "84538453"); const START_BLOCK = BigInt(process.env.VIBESCAN_START_BLOCK ?? "0"); const CONCURRENCY = Number(process.env.VIBESCAN_BACKFILL_CONCURRENCY ?? "16"); -const ERC20_TRANSFER = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f00b2d2b3179ef118821c0b55d5"; +// keccak256("Transfer(address,address,uint256)") — same topic for ERC-20 and ERC-721 +const ERC20_TRANSFER = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"; const chain = defineChain({ id: CHAIN_ID, From 5aadff5453e9b801c95c2590a257ba93567f02b4 Mon Sep 17 00:00:00 2001 From: Chris Hunter Date: Fri, 15 May 2026 10:42:58 -0400 Subject: [PATCH 7/7] style: port the original vibenet visual design (Base Blue, dark theme) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The first pass used Tailwind utility classes for a generic dark UI. The production deployments at vibes/faucet/explorer.vibes.base.org used a specific hand-crafted design with: - Base Blue accent (#0052FF), specific near-black palette - System sans + ui-monospace font stack - Brand mark (small Base-blue square) next to "base vibenet" - Specific layout primitives: chain-card with copy-to-clipboard rows, features-grid, contracts-list with watch-asset buttons, drip-buttons, stats cards with left blue border, key/value tables for detail pages Changes: - New src/styles/vibenet.css with the original CSS, scoped to .vibenet-app so it doesn't bleed into /tips - New (vibenet) route group with a layout.tsx that wraps in .vibenet-app - Move landing, faucet, explorer pages into the route group (URLs unchanged — route groups don't affect paths) - Rewrite each page to use the original semantic class names - Add wallet_addEthereumChain + wallet_watchAsset (USDV) via viem Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .../explorer/address/[addr]/page.tsx | 124 ++++ .../(vibenet)/explorer/block/[hash]/page.tsx | 140 ++++ src/app/(vibenet)/explorer/page.tsx | 273 ++++++++ src/app/(vibenet)/explorer/tx/[hash]/page.tsx | 154 ++++ src/app/(vibenet)/faucet/page.tsx | 276 ++++++++ src/app/(vibenet)/layout.tsx | 9 + src/app/(vibenet)/page.tsx | 365 ++++++++++ src/app/explorer/address/[addr]/page.tsx | 114 --- src/app/explorer/block/[hash]/page.tsx | 133 ---- src/app/explorer/page.tsx | 277 -------- src/app/explorer/tx/[hash]/page.tsx | 163 ----- src/app/faucet/page.tsx | 254 ------- src/app/page.tsx | 305 -------- src/styles/vibenet.css | 662 ++++++++++++++++++ 14 files changed, 2003 insertions(+), 1246 deletions(-) create mode 100644 src/app/(vibenet)/explorer/address/[addr]/page.tsx create mode 100644 src/app/(vibenet)/explorer/block/[hash]/page.tsx create mode 100644 src/app/(vibenet)/explorer/page.tsx create mode 100644 src/app/(vibenet)/explorer/tx/[hash]/page.tsx create mode 100644 src/app/(vibenet)/faucet/page.tsx create mode 100644 src/app/(vibenet)/layout.tsx create mode 100644 src/app/(vibenet)/page.tsx delete mode 100644 src/app/explorer/address/[addr]/page.tsx delete mode 100644 src/app/explorer/block/[hash]/page.tsx delete mode 100644 src/app/explorer/page.tsx delete mode 100644 src/app/explorer/tx/[hash]/page.tsx delete mode 100644 src/app/faucet/page.tsx delete mode 100644 src/app/page.tsx create mode 100644 src/styles/vibenet.css diff --git a/src/app/(vibenet)/explorer/address/[addr]/page.tsx b/src/app/(vibenet)/explorer/address/[addr]/page.tsx new file mode 100644 index 00000000..25088d4a --- /dev/null +++ b/src/app/(vibenet)/explorer/address/[addr]/page.tsx @@ -0,0 +1,124 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; + +interface PageProps { + params: Promise<{ addr: string }>; +} + +interface ActivityRow { + tx_hash: string; + block_num: number; + log_index: number; + role: number; + token: string | null; +} + +const ROLE_LABELS: Record = { + 0: "sender", + 1: "recipient", + 2: "creator", + 3: "token-from", + 4: "token-to", +}; + +export default function ExplorerAddressPage({ params }: PageProps) { + const [addr, setAddr] = useState(""); + const [balance, setBalance] = useState(null); + const [activity, setActivity] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + params.then((p) => setAddr(p.addr)); + }, [params]); + + useEffect(() => { + if (!addr) return; + setLoading(true); + fetch(`/api/vibenet/explorer/address/${addr}`) + .then((r) => r.json()) + .then((data) => { + setBalance(data.balance_wei ?? null); + setActivity(data.activity ?? []); + }) + .catch(() => null) + .finally(() => setLoading(false)); + }, [addr]); + + const ethBalance = balance + ? (Number(BigInt(balance)) / 1e18).toFixed(6) + : null; + + return ( + <> +
+ + + +
+ +
+

Address

+

+ {addr} +

+ +
+
Balance
+
{ethBalance !== null ? `${ethBalance} ETH` : "—"}
+
Activity
+
{activity.length} entries indexed
+
+ +

Recent Activity

+ {loading ? ( +
Loading…
+ ) : activity.length === 0 ? ( +
No activity found
+ ) : ( + + + + + + + + + + + {activity.map((row) => ( + + + + + + + ))} + +
RoleTx HashBlockToken
{ROLE_LABELS[row.role] ?? row.role} + + {row.tx_hash} + + + + #{row.block_num.toLocaleString()} + + + {row.token ? ( + + {row.token.slice(0, 10)}… + + ) : ( + + )} +
+ )} +
+ + ); +} diff --git a/src/app/(vibenet)/explorer/block/[hash]/page.tsx b/src/app/(vibenet)/explorer/block/[hash]/page.tsx new file mode 100644 index 00000000..093d5aa1 --- /dev/null +++ b/src/app/(vibenet)/explorer/block/[hash]/page.tsx @@ -0,0 +1,140 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; + +interface PageProps { + params: Promise<{ hash: string }>; +} + +interface BlockDetail { + number: string; + hash: string; + parentHash: string; + timestamp: string; + miner: string; + gasUsed: string; + gasLimit: string; + baseFeePerGas: string | null; + transactions: string[]; +} + +export default function ExplorerBlockPage({ params }: PageProps) { + const [hash, setHash] = useState(""); + const [block, setBlock] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + params.then((p) => setHash(p.hash)); + }, [params]); + + useEffect(() => { + if (!hash) return; + setLoading(true); + fetch(`/api/vibenet/explorer/block/${hash}`) + .then(async (r) => { + if (!r.ok) + throw new Error( + r.status === 404 ? "Block not found" : "Failed to fetch block", + ); + return r.json(); + }) + .then(setBlock) + .catch((e) => setError(e.message)) + .finally(() => setLoading(false)); + }, [hash]); + + const num = block ? Number.parseInt(block.number, 16) : null; + const ts = block + ? new Date(Number.parseInt(block.timestamp, 16) * 1000).toLocaleString() + : null; + + return ( + <> +
+ + + +
+ +
+

+ {num !== null ? `Block #${num.toLocaleString()}` : "Block"} +

+ {loading &&

Loading…

} + {error &&
{error}
} + {block && ( + <> +
+
Hash
+
+ {block.hash} +
+
Number
+
{num?.toLocaleString()}
+
Timestamp
+
{ts}
+
Miner
+
+ + {block.miner} + +
+
Parent
+
+ {num && num > 0 ? ( + + {block.parentHash} + + ) : ( + {block.parentHash} + )} +
+
Gas Used
+
{Number.parseInt(block.gasUsed, 16).toLocaleString()}
+
Gas Limit
+
{Number.parseInt(block.gasLimit, 16).toLocaleString()}
+
Base Fee
+
+ {block.baseFeePerGas + ? `${Number.parseInt(block.baseFeePerGas, 16).toLocaleString()} wei` + : "—"} +
+
+ +

Transactions ({block.transactions.length})

+ {block.transactions.length === 0 ? ( +
No transactions in this block
+ ) : ( + + + + + + + + + {block.transactions.map((txHash, i) => ( + + + + + ))} + +
#Hash
{i} + + {txHash} + +
+ )} + + )} +
+ + ); +} diff --git a/src/app/(vibenet)/explorer/page.tsx b/src/app/(vibenet)/explorer/page.tsx new file mode 100644 index 00000000..213993f8 --- /dev/null +++ b/src/app/(vibenet)/explorer/page.tsx @@ -0,0 +1,273 @@ +"use client"; + +import Link from "next/link"; +import { useRouter } from "next/navigation"; +import { useEffect, useState } from "react"; + +interface BlockRow { + number: number; + hash: string; + timestamp: number; + tx_count: number; + gas_used: number; +} + +interface TxRow { + hash: string; + block_num: number; + from_addr: string; + to_addr: string | null; + value: string; + status: number; +} + +interface Stats { + blocks: number; + txs: number; + addresses: number; +} + +const PROD_HOSTS = { + ui: "vibes.base.org", + faucet: "faucet.vibes.base.org", + explorer: "explorer.vibes.base.org", +}; + +function isProdHost(host: string) { + return host === PROD_HOSTS.ui || host.endsWith(".vibes.base.org"); +} + +function buildHomeUrl() { + if (typeof window === "undefined") return "/"; + return isProdHost(window.location.hostname) + ? `https://${PROD_HOSTS.ui}` + : "/"; +} + +function buildFaucetUrl() { + if (typeof window === "undefined") return "/faucet"; + return isProdHost(window.location.hostname) + ? `https://${PROD_HOSTS.faucet}` + : "/faucet"; +} + +function timeAgo(ts: number): string { + const s = Math.floor(Date.now() / 1000 - ts); + if (s < 60) return `${s}s ago`; + if (s < 3600) return `${Math.floor(s / 60)}m ago`; + return `${Math.floor(s / 3600)}h ago`; +} + +function shortHash(h: string, n = 18) { + return `${h.slice(0, n)}…`; +} + +function classifyQuery( + q: string, +): { kind: "block" | "tx" | "address"; value: string } | null { + const v = q.trim(); + if (!v) return null; + if (/^0x[a-fA-F0-9]{40}$/.test(v)) return { kind: "address", value: v }; + if (/^0x[a-fA-F0-9]{64}$/.test(v)) return { kind: "tx", value: v }; + if (/^\d+$/.test(v)) return { kind: "block", value: v }; + return null; +} + +function SearchBar() { + const router = useRouter(); + const [query, setQuery] = useState(""); + const [error, setError] = useState(null); + + const onSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); + const cls = classifyQuery(query); + if (!cls) { + setError("Enter a block #, hash, or address"); + return; + } + if (cls.kind === "address") { + router.push(`/explorer/address/${cls.value}`); + return; + } + if (cls.kind === "block") { + router.push(`/explorer/block/${cls.value}`); + return; + } + // 64-char hash: try tx first, fall through to block + try { + const res = await fetch(`/api/vibenet/explorer/tx/${cls.value}`); + if (res.ok) router.push(`/explorer/tx/${cls.value}`); + else router.push(`/explorer/block/${cls.value}`); + } catch { + router.push(`/explorer/block/${cls.value}`); + } + }; + + return ( +
+ setQuery(e.target.value)} + placeholder="Block #, block hash, tx hash, or address" + /> + + {error && ( + + {error} + + )} +
+ ); +} + +export default function ExplorerPage() { + const [blocks, setBlocks] = useState([]); + const [txs, setTxs] = useState([]); + const [stats, setStats] = useState(null); + const [loading, setLoading] = useState(true); + const [homeUrl, setHomeUrl] = useState("/"); + const [faucetUrl, setFaucetUrl] = useState("/faucet"); + + useEffect(() => { + setHomeUrl(buildHomeUrl()); + setFaucetUrl(buildFaucetUrl()); + + const load = async () => { + const [blocksRes, statsRes] = await Promise.all([ + fetch("/api/vibenet/explorer/blocks").then((r) => r.json()), + fetch("/api/vibenet/explorer/stats").then((r) => r.json()), + ]); + setBlocks(blocksRes.blocks ?? []); + setTxs(blocksRes.txs ?? []); + setStats(statsRes); + setLoading(false); + }; + load().catch(() => setLoading(false)); + const t = setInterval(load, 5_000); + return () => clearInterval(t); + }, []); + + return ( + <> +
+ + + +
+ +
+ + + {stats && ( +
+
+ Blocks + {stats.blocks.toLocaleString()} +
+
+ Transactions + {stats.txs.toLocaleString()} +
+
+ Addresses + {stats.addresses.toLocaleString()} +
+
+ )} + +
+
+

Latest Blocks

+ {loading ? ( +
Indexing…
+ ) : blocks.length === 0 ? ( +
No blocks yet
+ ) : ( + + + + + + + + + + + {blocks.slice(0, 10).map((b) => ( + + + + + + + ))} + +
BlockHashTxsAge
+ + #{b.number.toLocaleString()} + + + {shortHash(b.hash, 22)} + {b.tx_count} + {timeAgo(b.timestamp)} +
+ )} +
+ +
+

Latest Transactions

+ {loading ? ( +
Indexing…
+ ) : txs.length === 0 ? ( +
No transactions yet
+ ) : ( + + + + + + + + + + {txs.slice(0, 10).map((tx) => ( + + + + + + ))} + +
HashFrom → ToBlock
+ + {shortHash(tx.hash, 18)} + + + + {tx.from_addr.slice(0, 10)}… + {tx.to_addr + ? ` → ${tx.to_addr.slice(0, 10)}…` + : " → (create)"} + + + + #{tx.block_num.toLocaleString()} + +
+ )} +
+
+
+ + ); +} diff --git a/src/app/(vibenet)/explorer/tx/[hash]/page.tsx b/src/app/(vibenet)/explorer/tx/[hash]/page.tsx new file mode 100644 index 00000000..f6b8a5fb --- /dev/null +++ b/src/app/(vibenet)/explorer/tx/[hash]/page.tsx @@ -0,0 +1,154 @@ +"use client"; + +import Link from "next/link"; +import { useEffect, useState } from "react"; + +interface PageProps { + params: Promise<{ hash: string }>; +} + +interface TxDetail { + hash: string; + blockHash: string; + blockNumber: string; + from: string; + to: string | null; + value: string; + gas: string; + gasPrice: string; + nonce: string; + input: string; + transactionIndex: string; + status?: number; +} + +function weiToEth(hex: string): string { + const n = BigInt(hex); + if (n === 0n) return "0 ETH"; + const eth = Number(n) / 1e18; + return `${eth.toFixed(6)} ETH`; +} + +export default function ExplorerTxPage({ params }: PageProps) { + const [hash, setHash] = useState(""); + const [tx, setTx] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + params.then((p) => setHash(p.hash)); + }, [params]); + + useEffect(() => { + if (!hash) return; + setLoading(true); + fetch(`/api/vibenet/explorer/tx/${hash}`) + .then(async (r) => { + if (!r.ok) + throw new Error( + r.status === 404 + ? "Transaction not found" + : "Failed to fetch transaction", + ); + return r.json(); + }) + .then(setTx) + .catch((e) => setError(e.message)) + .finally(() => setLoading(false)); + }, [hash]); + + const blockNum = tx ? Number.parseInt(tx.blockNumber, 16) : null; + + return ( + <> +
+ + + +
+ +
+

Transaction

+ {loading &&

Loading…

} + {error &&
{error}
} + {tx && ( + <> + {tx.status !== undefined && ( +

+ Status:{" "} + + {tx.status === 1 ? "Success" : "Failed"} + +

+ )} +
+
Hash
+
+ {tx.hash} +
+
Block
+
+ + #{blockNum?.toLocaleString()} + +
+
From
+
+ + {tx.from} + +
+
To
+
+ {tx.to ? ( + + {tx.to} + + ) : ( + (contract creation) + )} +
+
Value
+
{weiToEth(tx.value)}
+
Gas Limit
+
{Number.parseInt(tx.gas, 16).toLocaleString()}
+
Gas Price
+
{Number.parseInt(tx.gasPrice, 16).toLocaleString()} wei
+
Nonce
+
{Number.parseInt(tx.nonce, 16).toString()}
+
Index
+
{Number.parseInt(tx.transactionIndex, 16).toString()}
+
+ + {tx.input && tx.input !== "0x" && ( + <> +

Input Data

+
+ {tx.input} +
+ + )} + + )} +
+ + ); +} diff --git a/src/app/(vibenet)/faucet/page.tsx b/src/app/(vibenet)/faucet/page.tsx new file mode 100644 index 00000000..9e9d3ff1 --- /dev/null +++ b/src/app/(vibenet)/faucet/page.tsx @@ -0,0 +1,276 @@ +"use client"; + +import { useEffect, useState } from "react"; + +interface FaucetStatus { + address: string; + chain_id: number; + drip_wei: string; + balance_wei: string; + ip_cooldown_secs: number; + addr_cooldown_secs: number; + usdv_address?: string; + usdv_drip_units?: string; + nfv_address?: string; +} + +type Token = "eth" | "usdv" | "nfv"; + +interface DripResult { + ok: boolean; + message: string; + tx_hash?: string; +} + +const PROD_HOSTS = { + ui: "vibes.base.org", + faucet: "faucet.vibes.base.org", + explorer: "explorer.vibes.base.org", +}; + +function isProdHost(host: string) { + return host === PROD_HOSTS.ui || host.endsWith(".vibes.base.org"); +} + +function buildHomeUrl() { + if (typeof window === "undefined") return "/"; + return isProdHost(window.location.hostname) + ? `https://${PROD_HOSTS.ui}` + : "/"; +} + +function buildExplorerUrl() { + if (typeof window === "undefined") return "/explorer"; + return isProdHost(window.location.hostname) + ? `https://${PROD_HOSTS.explorer}` + : "/explorer"; +} + +function weiToEth(wei: string, digits = 4): string { + const n = BigInt(wei); + const eth = Number(n) / 1e18; + return eth.toFixed(digits); +} + +function shortAddr(a: string) { + return `${a.slice(0, 8)}…${a.slice(-6)}`; +} + +const ENDPOINTS: Record = { + eth: "/api/vibenet/faucet/drip", + usdv: "/api/vibenet/faucet/drip-usdv", + nfv: "/api/vibenet/faucet/drip-nfv", +}; + +const LABELS: Record = { + eth: "Request ETH", + usdv: "Mint 1000 USDV", + nfv: "Mint NFV (NFT)", +}; + +export default function FaucetPage() { + const [status, setStatus] = useState(null); + const [address, setAddress] = useState(""); + const [loading, setLoading] = useState(null); + const [result, setResult] = useState(null); + const [homeUrl, setHomeUrl] = useState("/"); + const [explorerUrl, setExplorerUrl] = useState("/explorer"); + + useEffect(() => { + setHomeUrl(buildHomeUrl()); + setExplorerUrl(buildExplorerUrl()); + + const load = () => + fetch("/api/vibenet/faucet/status") + .then((r) => r.json()) + .then(setStatus) + .catch(() => null); + load(); + const t = setInterval(load, 15_000); + return () => clearInterval(t); + }, []); + + const drip = async (token: Token) => { + if (!address.match(/^0x[a-fA-F0-9]{40}$/)) { + setResult({ ok: false, message: "Invalid address" }); + return; + } + setLoading(token); + setResult(null); + try { + const res = await fetch(ENDPOINTS[token], { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ address }), + }); + const data = await res.json(); + if (res.ok) { + const msg = + token === "eth" + ? `Sent ${weiToEth(data.amount_wei ?? "0", 4)} ETH to ${shortAddr(address)}` + : token === "usdv" + ? `Minted USDV to ${shortAddr(address)}` + : `Minted NFV to ${shortAddr(address)}`; + setResult({ ok: true, message: msg, tx_hash: data.tx_hash }); + } else { + setResult({ ok: false, message: data.error ?? "Request failed" }); + } + } catch { + setResult({ ok: false, message: "Network error" }); + } finally { + setLoading(null); + } + }; + + const cooldownLabel = + status && + (status.ip_cooldown_secs >= 3600 + ? `${Math.round(status.ip_cooldown_secs / 3600)}h` + : status.ip_cooldown_secs >= 60 + ? `${Math.round(status.ip_cooldown_secs / 60)}m` + : `${status.ip_cooldown_secs}s`); + + return ( + <> +
+ + + +
+ +
+
+

Faucet

+

+ Drip testnet ETH or mint USDV / NFV to any address. +

+ +
+ {status ? ( +
+
+ Faucet:{" "} + + {status.address} + +
+
+ Balance:{" "} + {weiToEth(status.balance_wei, 2)} ETH ·{" "} + Drips:{" "} + {weiToEth(status.drip_wei, 4)} ETH per request +
+
+ Cooldown: {cooldownLabel} per IP and per + recipient address +
+ {status.usdv_address && ( + + )} + {status.nfv_address && ( + + )} +
+ ) : ( +
Loading status…
+ )} + +
{ + e.preventDefault(); + drip("eth"); + }} + > + + setAddress(e.target.value)} + placeholder="0x…" + pattern="^0x[a-fA-F0-9]{40}$" + required + /> +
+ {(["eth", "usdv", "nfv"] as Token[]).map((token) => { + const enabled = + token === "eth" || + (token === "usdv" && !!status?.usdv_address) || + (token === "nfv" && !!status?.nfv_address); + if (!enabled) return null; + return ( + + ); + })} +
+
+ + {result && ( +
+
{result.message}
+ {result.tx_hash && ( + + )} +
+ )} +
+
+
+ + + + ); +} diff --git a/src/app/(vibenet)/layout.tsx b/src/app/(vibenet)/layout.tsx new file mode 100644 index 00000000..6e18ae8c --- /dev/null +++ b/src/app/(vibenet)/layout.tsx @@ -0,0 +1,9 @@ +import "@/styles/vibenet.css"; + +export default function VibenetLayout({ + children, +}: { + children: React.ReactNode; +}) { + return
{children}
; +} diff --git a/src/app/(vibenet)/page.tsx b/src/app/(vibenet)/page.tsx new file mode 100644 index 00000000..6293e360 --- /dev/null +++ b/src/app/(vibenet)/page.tsx @@ -0,0 +1,365 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { createWalletClient, custom } from "viem"; + +declare global { + interface Window { + // biome-ignore lint/suspicious/noExplicitAny: EIP-1193 provider + ethereum?: any; + } +} + +interface Feature { + title: string; + description: string; + link?: string; +} + +interface VibeConfig { + title?: string; + subtitle?: string; + features?: Feature[]; + branch?: string; + commit?: string; +} + +const PROD_HOSTS = { + ui: "vibes.base.org", + rpc: "rpc.vibes.base.org", + faucet: "faucet.vibes.base.org", + explorer: "explorer.vibes.base.org", +}; + +function isProd() { + if (typeof window === "undefined") return false; + const h = window.location.hostname; + return h === PROD_HOSTS.ui || h.endsWith(".vibes.base.org"); +} + +// In production each subdomain is reachable on its own host. Locally we +// surface vibescan + faucet as path-routed siblings of the landing page. +function buildRpcUrl() { + if (isProd()) return `https://${PROD_HOSTS.rpc}`; + if (typeof window === "undefined") return "http://localhost:18082"; + const port = Number(window.location.port || 80); + return `${window.location.protocol}//${window.location.hostname}:${port + 2}`; +} + +function buildFaucetUrl() { + if (isProd()) return `https://${PROD_HOSTS.faucet}`; + return "/faucet"; +} + +function buildExplorerUrl() { + if (isProd()) return `https://${PROD_HOSTS.explorer}`; + return "/explorer"; +} + +const CONTRACT_LABELS: Record = { + faucetAddress: "Faucet", + usdv: "USDV (ERC-20)", + nfv: "NFV (ERC-721)", +}; + +const WATCHABLE_TOKENS: Record< + string, + { type: "ERC20"; symbol: string; decimals: number } +> = { + usdv: { type: "ERC20", symbol: "USDV", decimals: 6 }, +}; + +export default function VibeHomePage() { + const [config, setConfig] = useState({}); + const [contracts, setContracts] = useState | null>( + null, + ); + const [chainId, setChainId] = useState(""); + const [rpcUrl, setRpcUrl] = useState(""); + const [faucetUrl, setFaucetUrl] = useState(""); + const [explorerUrl, setExplorerUrl] = useState(""); + const [copied, setCopied] = useState(null); + const [walletStatus, setWalletStatus] = useState(""); + const [watchStatus, setWatchStatus] = useState>({}); + + useEffect(() => { + setRpcUrl(buildRpcUrl()); + setFaucetUrl(buildFaucetUrl()); + setExplorerUrl(buildExplorerUrl()); + + fetch("/api/vibenet/config") + .then((r) => r.json()) + .then(setConfig) + .catch(() => null); + + fetch("/api/vibenet/contracts") + .then((r) => r.json()) + .then(setContracts) + .catch(() => setContracts(null)); + + fetch("/api/vibenet/faucet/status") + .then((r) => r.json()) + .then((d: { chain_id?: number }) => { + if (d.chain_id) setChainId(String(d.chain_id)); + }) + .catch(() => null); + }, []); + + const copy = async (text: string, key: string) => { + try { + await navigator.clipboard.writeText(text); + setCopied(key); + setTimeout(() => setCopied(null), 1500); + } catch { + /* noop */ + } + }; + + const addToWallet = async () => { + if (!window.ethereum) { + setWalletStatus("No browser wallet detected on this page."); + return; + } + try { + const wallet = createWalletClient({ transport: custom(window.ethereum) }); + await wallet.addChain({ + chain: { + id: Number(chainId), + name: "base vibenet", + nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, + rpcUrls: { default: { http: [rpcUrl] } }, + blockExplorers: { + default: { name: "vibescan", url: explorerUrl }, + }, + }, + }); + setWalletStatus( + "Network added. Your wallet should now be on base vibenet.", + ); + } catch (err) { + const e = err as { shortMessage?: string; message?: string }; + setWalletStatus( + `Wallet did not add the network: ${e.shortMessage ?? e.message ?? String(err)}`, + ); + } + }; + + const watchAsset = async ( + address: string, + meta: (typeof WATCHABLE_TOKENS)[string], + ) => { + if (!window.ethereum) { + setWatchStatus((s) => ({ ...s, [address]: "No wallet detected" })); + setTimeout(() => setWatchStatus((s) => ({ ...s, [address]: "" })), 1500); + return; + } + try { + const wallet = createWalletClient({ transport: custom(window.ethereum) }); + await wallet.watchAsset({ + type: meta.type, + options: { + address: address as `0x${string}`, + symbol: meta.symbol, + decimals: meta.decimals, + }, + }); + setWatchStatus((s) => ({ ...s, [address]: `${meta.symbol} added` })); + } catch (err) { + const e = err as { shortMessage?: string }; + setWatchStatus((s) => ({ + ...s, + [address]: e.shortMessage ?? "Rejected", + })); + } finally { + setTimeout(() => setWatchStatus((s) => ({ ...s, [address]: "" })), 1800); + } + }; + + const features = config.features ?? []; + const contractEntries = contracts + ? Object.entries(contracts).filter( + ([k, v]) => + !k.startsWith("_") && + k !== "faucetAddress" && + typeof v === "string" && + /^0x[0-9a-fA-F]{40}$/.test(v), + ) + : null; + + return ( + <> +
+ + + +
+ +
+
+

{config.title ?? "base vibenet"}

+

+ {config.subtitle ?? + "An ephemeral Base devnet for trying out in-flight features."} +

+
+ +
+

Features

+ {features.length === 0 ? ( +

+ No branch-specific features declared for this vibe. +

+ ) : ( +
+ {features.map((f) => ( +
+
{f.title}
+ {f.description && ( +
{f.description}
+ )} + {f.link && ( + + Learn more → + + )} +
+ ))} +
+ )} +
+ +
+

Connect

+
+
+ Chain ID + +
+
+ RPC URL + +
+
+ Explorer + + {explorerUrl} + +
+

+ Public RPC access is IP rate limited. +

+
+ +
+ {walletStatus && ( +

+ {walletStatus} +

+ )} +
+
+ +
+

Deployed Contracts

+
+ {!contractEntries ? ( +

+ Loading… +

+ ) : contractEntries.length === 0 ? ( +

+ No contracts deployed on this vibe. +

+ ) : ( + contractEntries.map(([k, v]) => { + const meta = WATCHABLE_TOKENS[k]; + return ( +
+ + {CONTRACT_LABELS[k] ?? k} + + + {v} + + {meta ? ( + + ) : ( + + )} +
+ ); + }) + )} +
+
+
+ +
+ + branch {config.branch ?? "unknown"} + + + commit {(config.commit ?? "unknown").slice(0, 12)} + + + + base.org + +
+ + ); +} diff --git a/src/app/explorer/address/[addr]/page.tsx b/src/app/explorer/address/[addr]/page.tsx deleted file mode 100644 index 89db1c0b..00000000 --- a/src/app/explorer/address/[addr]/page.tsx +++ /dev/null @@ -1,114 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { useEffect, useState } from "react"; - -interface PageProps { - params: Promise<{ addr: string }>; -} - -interface ActivityRow { - tx_hash: string; - block_num: number; - role: number; -} - -const ROLE_LABELS: Record = { - 0: "Sender", - 1: "Recipient", - 2: "Creator", - 3: "Token from", - 4: "Token to", -}; - -export default function ExplorerAddressPage({ params }: PageProps) { - const [addr, setAddr] = useState(""); - const [balance, setBalance] = useState(null); - const [activity, setActivity] = useState([]); - const [loading, setLoading] = useState(true); - - useEffect(() => { - params.then((p) => setAddr(p.addr)); - }, [params]); - - useEffect(() => { - if (!addr) return; - setLoading(true); - fetch(`/api/vibenet/explorer/address/${addr}`) - .then((r) => r.json()) - .then((data) => { - setBalance(data.balance_wei ?? null); - setActivity(data.activity ?? []); - }) - .catch(() => null) - .finally(() => setLoading(false)); - }, [addr]); - - const ethBalance = balance - ? (Number(BigInt(balance)) / 1e18).toFixed(6) - : null; - - return ( -
-
- - ← Explorer - - / - - {addr.slice(0, 14)}… - -
- -
-
-

Address

-

{addr}

-
- - {ethBalance !== null && ( -
-
Balance
-
- {ethBalance} ETH -
-
- )} - -
-

- Activity ({activity.length}) -

- {loading ? ( -

Loading…

- ) : activity.length === 0 ? ( -

No activity found

- ) : ( -
- {activity.map((row, i) => ( - - - {ROLE_LABELS[row.role] ?? "—"} - - - {row.tx_hash} - - - #{row.block_num.toLocaleString()} - - - ))} -
- )} -
-
-
- ); -} diff --git a/src/app/explorer/block/[hash]/page.tsx b/src/app/explorer/block/[hash]/page.tsx deleted file mode 100644 index 446d8946..00000000 --- a/src/app/explorer/block/[hash]/page.tsx +++ /dev/null @@ -1,133 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { useEffect, useState } from "react"; - -interface PageProps { - params: Promise<{ hash: string }>; -} - -interface BlockDetail { - number: string; - hash: string; - parentHash: string; - timestamp: string; - miner: string; - gasUsed: string; - gasLimit: string; - baseFeePerGas: string | null; - transactions: string[]; -} - -export default function ExplorerBlockPage({ params }: PageProps) { - const [hash, setHash] = useState(""); - const [block, setBlock] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - params.then((p) => setHash(p.hash)); - }, [params]); - - useEffect(() => { - if (!hash) return; - setLoading(true); - fetch(`/api/vibenet/explorer/block/${hash}`) - .then(async (r) => { - if (!r.ok) - throw new Error( - r.status === 404 ? "Block not found" : "Failed to fetch block", - ); - return r.json(); - }) - .then(setBlock) - .catch((e) => setError(e.message)) - .finally(() => setLoading(false)); - }, [hash]); - - const num = block ? parseInt(block.number, 16) : null; - const ts = block - ? new Date(parseInt(block.timestamp, 16) * 1000).toLocaleString() - : null; - - return ( -
-
- - ← Explorer - - / - - Block{" "} - {num !== null ? `#${num.toLocaleString()}` : hash.slice(0, 12) + "…"} - -
- -
- {loading &&

Loading…

} - {error &&

{error}

} - {block && ( -
-

- Block #{num?.toLocaleString()} -

-
- {[ - ["Hash", block.hash], - ["Number", num?.toLocaleString() ?? "—"], - ["Timestamp", ts ?? "—"], - ["Miner", block.miner], - ["Parent Hash", block.parentHash], - ["Gas Used", parseInt(block.gasUsed, 16).toLocaleString()], - ["Gas Limit", parseInt(block.gasLimit, 16).toLocaleString()], - [ - "Base Fee", - block.baseFeePerGas - ? `${parseInt(block.baseFeePerGas, 16)} wei` - : "—", - ], - ["Transactions", block.transactions.length.toString()], - ].map(([label, value], i) => ( -
- - {label} - - - {value} - -
- ))} -
- - {block.transactions.length > 0 && ( -
-

- Transactions ({block.transactions.length}) -

-
- {block.transactions.map((txHash, i) => ( - - - {txHash} - - - ))} -
-
- )} -
- )} -
-
- ); -} diff --git a/src/app/explorer/page.tsx b/src/app/explorer/page.tsx deleted file mode 100644 index b7b3a8e0..00000000 --- a/src/app/explorer/page.tsx +++ /dev/null @@ -1,277 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; - -interface BlockRow { - number: number; - hash: string; - timestamp: number; - tx_count: number; - gas_used: number; -} - -interface TxRow { - hash: string; - block_num: number; - from_addr: string; - to_addr: string | null; - value: string; - status: number; -} - -interface Stats { - blocks: number; - txs: number; - addresses: number; -} - -function timeAgo(ts: number): string { - const s = Math.floor(Date.now() / 1000 - ts); - if (s < 60) return `${s}s ago`; - if (s < 3600) return `${Math.floor(s / 60)}m ago`; - return `${Math.floor(s / 3600)}h ago`; -} - -const HOME_URL = - typeof window !== "undefined" && - window.location.hostname === "explorer.vibes.base.org" - ? "https://vibes.base.org" - : "/"; - -const FAUCET_URL = - typeof window !== "undefined" && - window.location.hostname === "explorer.vibes.base.org" - ? "https://faucet.vibes.base.org" - : "/faucet"; - -function classifyQuery( - q: string, -): { kind: "block" | "tx" | "address"; value: string } | null { - const v = q.trim(); - if (!v) return null; - // 0x-prefixed: 40 hex chars = address, 64 hex chars = hash (block or tx) - if (/^0x[a-fA-F0-9]{40}$/.test(v)) return { kind: "address", value: v }; - if (/^0x[a-fA-F0-9]{64}$/.test(v)) return { kind: "tx", value: v }; // try tx first; falls through to block on 404 - // Plain integer = block number - if (/^\d+$/.test(v)) return { kind: "block", value: v }; - return null; -} - -function SearchBar() { - const router = useRouter(); - const [query, setQuery] = useState(""); - const [error, setError] = useState(null); - - const onSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(null); - const cls = classifyQuery(query); - if (!cls) { - setError("Enter a block number, block hash, tx hash, or address"); - return; - } - if (cls.kind === "address") { - router.push(`/explorer/address/${cls.value}`); - return; - } - if (cls.kind === "block") { - router.push(`/explorer/block/${cls.value}`); - return; - } - // 64-char hash: try tx first; if not found, try as block hash - try { - const res = await fetch(`/api/vibenet/explorer/tx/${cls.value}`); - if (res.ok) { - router.push(`/explorer/tx/${cls.value}`); - } else { - router.push(`/explorer/block/${cls.value}`); - } - } catch { - router.push(`/explorer/block/${cls.value}`); - } - }; - - return ( -
- setQuery(e.target.value)} - placeholder="Block number, block hash, tx hash, or address" - className="flex-1 px-4 py-2 rounded-lg bg-white/5 border border-white/10 text-white text-sm placeholder-gray-600 focus:outline-none focus:border-blue-500 font-mono" - /> - - {error &&

{error}

} -
- ); -} - -export default function ExplorerPage() { - const [blocks, setBlocks] = useState([]); - const [txs, setTxs] = useState([]); - const [stats, setStats] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - const load = async () => { - const [blocksRes, statsRes] = await Promise.all([ - fetch("/api/vibenet/explorer/blocks").then((r) => r.json()), - fetch("/api/vibenet/explorer/stats").then((r) => r.json()), - ]); - setBlocks(blocksRes.blocks ?? []); - setTxs(blocksRes.txs ?? []); - setStats(statsRes); - setLoading(false); - }; - load().catch(() => setLoading(false)); - const t = setInterval(load, 5_000); - return () => clearInterval(t); - }, []); - - return ( -
-
- - base vibenet - - -
- -
- - - {stats && ( -
- {[ - { label: "Blocks", value: stats.blocks.toLocaleString() }, - { label: "Transactions", value: stats.txs.toLocaleString() }, - { label: "Addresses", value: stats.addresses.toLocaleString() }, - ].map((s) => ( -
-
{s.label}
-
- {s.value} -
-
- ))} -
- )} - -
-
-

- Latest Blocks -

-
- {loading ? ( -
- Indexing… -
- ) : blocks.length === 0 ? ( -
- No blocks yet -
- ) : ( - blocks.slice(0, 10).map((b, i) => ( - -
- B -
-
-
- #{b.number.toLocaleString()} -
-
- {b.hash.slice(0, 18)}… -
-
-
-
- {b.tx_count} txns -
-
- {timeAgo(b.timestamp)} -
-
- - )) - )} -
-
- -
-

- Latest Transactions -

-
- {loading ? ( -
- Indexing… -
- ) : txs.length === 0 ? ( -
- No transactions yet -
- ) : ( - txs.slice(0, 10).map((tx, i) => ( - -
- T -
-
-
- {tx.hash.slice(0, 18)}… -
-
- {tx.from_addr.slice(0, 10)}… - {tx.to_addr - ? ` → ${tx.to_addr.slice(0, 10)}…` - : " (create)"} -
-
-
- #{tx.block_num.toLocaleString()} -
- - )) - )} -
-
-
-
-
- ); -} diff --git a/src/app/explorer/tx/[hash]/page.tsx b/src/app/explorer/tx/[hash]/page.tsx deleted file mode 100644 index 4fa56be0..00000000 --- a/src/app/explorer/tx/[hash]/page.tsx +++ /dev/null @@ -1,163 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { useEffect, useState } from "react"; - -interface PageProps { - params: Promise<{ hash: string }>; -} - -interface TxDetail { - hash: string; - blockHash: string; - blockNumber: string; - from: string; - to: string | null; - value: string; - gas: string; - gasPrice: string; - nonce: string; - input: string; - transactionIndex: string; - status?: number; -} - -function weiToEth(hex: string): string { - const n = BigInt(hex); - if (n === 0n) return "0 ETH"; - const eth = Number(n) / 1e18; - return `${eth.toFixed(6)} ETH`; -} - -export default function ExplorerTxPage({ params }: PageProps) { - const [hash, setHash] = useState(""); - const [tx, setTx] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - useEffect(() => { - params.then((p) => setHash(p.hash)); - }, [params]); - - useEffect(() => { - if (!hash) return; - setLoading(true); - fetch(`/api/vibenet/explorer/tx/${hash}`) - .then(async (r) => { - if (!r.ok) - throw new Error( - r.status === 404 - ? "Transaction not found" - : "Failed to fetch transaction", - ); - return r.json(); - }) - .then(setTx) - .catch((e) => setError(e.message)) - .finally(() => setLoading(false)); - }, [hash]); - - const blockNum = tx ? parseInt(tx.blockNumber, 16) : null; - - return ( -
-
- - ← Explorer - - / - - {hash.slice(0, 14)}… - -
- -
- {loading &&

Loading…

} - {error &&

{error}

} - {tx && ( -
-
-

Transaction

- {tx.status !== undefined && ( - - {tx.status === 1 ? "Success" : "Failed"} - - )} -
-
- {[ - ["Hash", tx.hash], - [ - "Block", - blockNum !== null - ? `#${blockNum.toLocaleString()}` - : tx.blockHash, - ], - ["From", tx.from], - ["To", tx.to ?? "(contract creation)"], - ["Value", weiToEth(tx.value)], - ["Gas Limit", parseInt(tx.gas, 16).toLocaleString()], - ["Gas Price", `${parseInt(tx.gasPrice, 16)} wei`], - ["Nonce", parseInt(tx.nonce, 16).toString()], - ["Index", parseInt(tx.transactionIndex, 16).toString()], - ].map(([label, value], i) => ( -
- - {label} - - - {label === "Block" && blockNum !== null ? ( - - {value} - - ) : label === "From" ? ( - - {value} - - ) : label === "To" && tx.to ? ( - - {value} - - ) : ( - value - )} - -
- ))} -
- - {tx.input && tx.input !== "0x" && ( -
-

- Input Data -

-
- - {tx.input} - -
-
- )} -
- )} -
-
- ); -} diff --git a/src/app/faucet/page.tsx b/src/app/faucet/page.tsx deleted file mode 100644 index 171b119c..00000000 --- a/src/app/faucet/page.tsx +++ /dev/null @@ -1,254 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; - -interface FaucetStatus { - address: string; - chain_id: number; - drip_wei: string; - balance_wei: string; - ip_cooldown_secs: number; - addr_cooldown_secs: number; - usdv_address?: string; - usdv_drip_units?: string; - nfv_address?: string; -} - -type Token = "eth" | "usdv" | "nfv"; - -interface DripResult { - ok: boolean; - message: string; - tx_hash?: string; -} - -const HOME_URL = - typeof window !== "undefined" && - window.location.hostname === "faucet.vibes.base.org" - ? "https://vibes.base.org" - : "/"; - -const EXPLORER_URL = - typeof window !== "undefined" && - window.location.hostname === "faucet.vibes.base.org" - ? "https://explorer.vibes.base.org" - : "/explorer"; - -function weiToEth(wei: string): string { - const n = BigInt(wei); - const eth = Number(n) / 1e18; - return eth.toFixed(4); -} - -export default function FaucetPage() { - const [status, setStatus] = useState(null); - const [address, setAddress] = useState(""); - const [loading, setLoading] = useState(null); - const [result, setResult] = useState(null); - - useEffect(() => { - const load = () => - fetch("/api/vibenet/faucet/status") - .then((r) => r.json()) - .then(setStatus) - .catch(() => null); - load(); - const t = setInterval(load, 15_000); - return () => clearInterval(t); - }, []); - - const drip = async (token: Token) => { - if (!address.match(/^0x[a-fA-F0-9]{40}$/)) { - setResult({ ok: false, message: "Invalid address" }); - return; - } - setLoading(token); - setResult(null); - const endpoint = { - eth: "/api/vibenet/faucet/drip", - usdv: "/api/vibenet/faucet/drip-usdv", - nfv: "/api/vibenet/faucet/drip-nfv", - }[token]; - try { - const res = await fetch(endpoint, { - method: "POST", - headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ address }), - }); - const data = await res.json(); - if (res.ok) { - setResult({ ok: true, message: "Success!", tx_hash: data.tx_hash }); - } else { - setResult({ ok: false, message: data.error ?? "Request failed" }); - } - } catch { - setResult({ ok: false, message: "Network error" }); - } finally { - setLoading(null); - } - }; - - return ( -
-
- - base vibenet - - -
- -
-

Faucet

-

- Drip testnet ETH or mint USDV (Vibe USD) to any address. -

- -
- {status && ( -
-
- Balance:{" "} - - {weiToEth(status.balance_wei)} ETH - -
-
- Drip amount:{" "} - - {weiToEth(status.drip_wei)} ETH - -
-
- Cooldown:{" "} - - {status.ip_cooldown_secs / 3600}h per IP / address - -
- {status.usdv_address && ( - - )} - {status.nfv_address && ( - - )} -
- )} - -
- - setAddress(e.target.value)} - placeholder="0x..." - className="w-full px-4 py-2.5 rounded-lg bg-white/5 border border-white/10 text-white font-mono text-sm placeholder-gray-600 focus:outline-none focus:border-blue-500" - /> -
- -
- - {status?.usdv_address && ( - - )} - {status?.nfv_address && ( - - )} -
- - {result && ( -
-

{result.message}

- {result.tx_hash && ( - - {result.tx_hash} - - )} -
- )} -
-
- - -
- ); -} diff --git a/src/app/page.tsx b/src/app/page.tsx deleted file mode 100644 index 5b184664..00000000 --- a/src/app/page.tsx +++ /dev/null @@ -1,305 +0,0 @@ -"use client"; - -import { useEffect, useState } from "react"; - -declare global { - interface Window { - // biome-ignore lint/suspicious/noExplicitAny: EIP-1193 provider - ethereum?: any; - } -} - -interface Feature { - title: string; - description: string; - link?: string; -} - -interface VibeConfig { - title: string; - subtitle: string; - features: Feature[]; - branch: string; - commit: string; -} - -interface ContractEntry { - name: string; - address: string; -} - -// Subdomain hosts use absolute URLs; local dev uses same-origin paths. -function isProd() { - if (typeof window === "undefined") return false; - const h = window.location.hostname; - return h === "vibes.base.org" || h.endsWith(".vibes.base.org"); -} - -const FAUCET_URL = isProd() ? "https://faucet.vibes.base.org" : "/faucet"; -const EXPLORER_URL = isProd() ? "https://explorer.vibes.base.org" : "/explorer"; - -// Local dev: HTTP RPC is on port + 2 (proxyd on 18082 when UI is on 18080). -function localRpcUrl(): string { - if (typeof window === "undefined") return "http://localhost:18082"; - const uiPort = Number(window.location.port || 80); - return `${window.location.protocol}//${window.location.hostname}:${uiPort + 2}`; -} - -export default function VibeHomePage() { - const [config, setConfig] = useState(null); - const [contracts, setContracts] = useState(null); - const [rpcUrl, setRpcUrl] = useState(""); - const [chainId, setChainId] = useState(""); - const [copied, setCopied] = useState(null); - const [walletStatus, setWalletStatus] = useState(""); - - useEffect(() => { - fetch("/api/vibenet/config") - .then((r) => r.json()) - .then(setConfig) - .catch(() => null); - - // Filter out metadata keys (_branch, _commit, faucetAddress) — only show real contracts. - fetch("/api/vibenet/contracts") - .then((r) => r.json()) - .then((data: Record) => - setContracts( - Object.entries(data) - .filter( - ([k, v]) => - !k.startsWith("_") && - k !== "faucetAddress" && - typeof v === "string" && - v.startsWith("0x"), - ) - .map(([name, address]) => ({ name, address })), - ), - ) - .catch(() => null); - - // Pull chain ID from faucet/status (vibenet.yaml doesn't include it). - fetch("/api/vibenet/faucet/status") - .then((r) => r.json()) - .then((d: { chain_id?: number }) => { - if (d.chain_id) setChainId(d.chain_id.toString()); - }) - .catch(() => null); - }, []); - - useEffect(() => { - setRpcUrl(isProd() ? "https://rpc.vibes.base.org" : localRpcUrl()); - }, []); - - const copyToClipboard = (text: string, key: string) => { - navigator.clipboard.writeText(text).then(() => { - setCopied(key); - setTimeout(() => setCopied(null), 2000); - }); - }; - - const addToWallet = async () => { - if (!window.ethereum) { - setWalletStatus("No wallet detected"); - return; - } - try { - await window.ethereum.request({ - method: "wallet_addEthereumChain", - params: [ - { - chainId: `0x${Number(chainId).toString(16)}`, - chainName: config?.title ?? "base vibenet", - rpcUrls: [rpcUrl], - nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18 }, - blockExplorerUrls: [EXPLORER_URL], - }, - ], - }); - setWalletStatus("Network added!"); - } catch { - setWalletStatus("Failed to add network"); - } - }; - - return ( -
-
- - base vibenet - - -
- -
-
-

- {config?.title ?? "base vibenet"} -

-

- {config?.subtitle ?? - "An ephemeral Base devnet for trying out in-flight features."} -

-
- - {config?.features && config.features.length > 0 && ( -
-

Features

-
- {config.features.map((f) => ( -
-

{f.title}

-

{f.description}

- {f.link && ( - - Learn more ↗ - - )} -
- ))} -
-
- )} - -
-

Connect

-
- {chainId && ( -
- Chain ID - -
- )} - {rpcUrl && ( -
- RPC URL - -
- )} -
- Explorer - - {EXPLORER_URL} - -
-

- Public RPC access is IP rate limited. -

- {chainId && ( -
- - {walletStatus && ( -

{walletStatus}

- )} -
- )} -
-
- - {contracts && contracts.length > 0 && ( -
-

- Deployed Contracts -

-
- {contracts.map((c, i) => ( -
- - {c.name} - - - {c.address} - -
- ))} -
-
- )} -
- - -
- ); -} diff --git a/src/styles/vibenet.css b/src/styles/vibenet.css new file mode 100644 index 00000000..cde531c1 --- /dev/null +++ b/src/styles/vibenet.css @@ -0,0 +1,662 @@ +/* base vibenet — landing, faucet, explorer. + * + * Palette + type approximates docs.base.org: near-black canvas, Base Blue + * accent (#0052FF), thin borders, generous whitespace, system sans for + * prose and a mono stack for anything hex. + * + * Body styles are scoped to .vibenet-app so they don't bleed into + * non-vibenet routes (e.g. /tips). + */ + +.vibenet-app { + --bg: #0a0b0f; + --bg-elev: #0f121a; + --card: #12151d; + --card-hover: #161a24; + --border: #1f2330; + --border-strong: #2a3040; + --fg: #ffffff; + --fg-dim: #8a93a6; + --fg-dimmer: #5b6478; + --accent: #0052ff; + --accent-hover: #3d75ff; + --accent-dim: #002a80; + --ok: #4ade80; + --err: #f87171; + --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace; + --sans: + -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", system-ui, + sans-serif; + --radius: 8px; + --radius-lg: 12px; + + min-height: 100vh; + background: var(--bg); + color: var(--fg); + font-family: var(--sans); + line-height: 1.55; + font-size: 15px; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.vibenet-app *, +.vibenet-app *::before, +.vibenet-app *::after { + box-sizing: border-box; +} + +.vibenet-app a { + color: var(--accent-hover); + text-decoration: none; +} +.vibenet-app a:hover { + color: #6e92ff; + text-decoration: underline; +} + +/* ---------- header / nav ------------------------------------------------ */ + +.vibenet-app .site-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 1.5rem; + padding: 18px 28px; + border-bottom: 1px solid var(--border); + background: var(--bg); + position: sticky; + top: 0; + z-index: 10; + backdrop-filter: blur(6px); +} + +.vibenet-app .brand { + display: inline-flex; + align-items: center; + gap: 10px; + color: var(--fg); + font-weight: 700; + font-size: 17px; + letter-spacing: 0.01em; +} +.vibenet-app .brand:hover { + color: var(--fg); + text-decoration: none; +} + +.vibenet-app .brand-mark { + display: inline-block; + width: 20px; + height: 20px; + background: var(--accent); + border-radius: 4px; +} + +.vibenet-app .site-nav { + display: flex; + align-items: center; + gap: 1.25rem; + font-size: 14px; +} +.vibenet-app .site-nav a, +.vibenet-app .site-nav .current { + color: var(--fg-dim); + padding: 4px 2px; + border-bottom: 1px solid transparent; +} +.vibenet-app .site-nav a:hover { + color: var(--fg); + border-bottom-color: var(--accent); + text-decoration: none; +} +.vibenet-app .site-nav .current { + color: var(--fg); +} + +/* ---------- layout ------------------------------------------------------ */ + +.vibenet-app main { + max-width: 960px; + margin: 0 auto; + padding: 2.5rem 28px 3rem; +} +.vibenet-app main.wide { + max-width: 1200px; +} + +.vibenet-app main section + section { + margin-top: 2.5rem; +} +.vibenet-app main h2 { + font-size: 1.25rem; + font-weight: 600; + margin: 0 0 1rem; + letter-spacing: -0.01em; +} +.vibenet-app main h3 { + font-size: 1.05rem; + font-weight: 600; + margin: 1.5rem 0 0.5rem; +} + +.vibenet-app .muted { + color: var(--fg-dim); +} +.vibenet-app .small { + font-size: 13px; +} + +/* ---------- hero / chain card ------------------------------------------- */ + +.vibenet-app .hero { + margin-bottom: 0.5rem; +} + +.vibenet-app .page-title { + font-size: 2rem; + margin: 0 0 0.25rem; + letter-spacing: -0.02em; + font-weight: 700; +} + +.vibenet-app .subtitle { + margin: 0 0 1.25rem; + color: var(--fg-dim); + font-size: 1.05rem; +} + +.vibenet-app .chain-card { + background: var(--card); + border: 1px solid var(--border); + border-radius: var(--radius-lg); + padding: 1.15rem 1.5rem; + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.vibenet-app .chain-row { + display: grid; + grid-template-columns: 110px 1fr; + gap: 1rem; + align-items: center; + font-size: 14px; + min-height: 28px; +} + +.vibenet-app .chain-label { + color: var(--fg-dim); + text-transform: uppercase; + font-size: 11px; + letter-spacing: 0.08em; + font-weight: 600; +} + +.vibenet-app .chain-value { + font-family: var(--mono); + font-size: 13px; + color: var(--fg); + overflow: hidden; +} + +.vibenet-app code { + font-family: var(--mono); + background: transparent; + padding: 0; + color: var(--fg); +} + +.vibenet-app button.chain-value.copyable { + display: flex; + align-items: center; + justify-content: space-between; + gap: 0.75rem; + width: 100%; + background: transparent; + border: 1px solid transparent; + padding: 4px 8px; + margin: 0; + border-radius: 6px; + color: var(--fg); + text-align: left; + font-weight: 400; + cursor: pointer; + min-width: 0; + transition: + background 0.1s ease, + border-color 0.1s ease; +} +.vibenet-app button.chain-value.copyable:hover { + background: var(--bg-elev); + border-color: var(--border); +} +.vibenet-app button.chain-value.copyable.copied { + border-color: var(--ok); +} +.vibenet-app .copy-hint { + font-family: var(--sans); + font-size: 11px; + color: var(--fg-dimmer); + text-transform: uppercase; + letter-spacing: 0.08em; + opacity: 0; + transition: + opacity 0.1s ease, + color 0.1s ease; + flex-shrink: 0; +} +.vibenet-app button.chain-value.copyable:hover .copy-hint, +.vibenet-app button.chain-value.copyable:focus-visible .copy-hint, +.vibenet-app button.chain-value.copyable.copied .copy-hint { + opacity: 1; +} +.vibenet-app button.chain-value.copyable.copied .copy-hint { + color: var(--ok); +} +.vibenet-app a.chain-value { + display: block; + padding: 4px 8px; + border: 1px solid transparent; + border-radius: 6px; +} + +.vibenet-app .truncate { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; + min-width: 0; +} + +.vibenet-app .chain-actions { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + margin-top: 0.4rem; +} + +.vibenet-app .notice { + margin: 0.25rem 0 0; + color: var(--fg-dim); +} + +/* ---------- features grid ----------------------------------------------- */ + +.vibenet-app .features-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); + gap: 0.75rem; +} + +.vibenet-app .feature-card { + background: var(--card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 1rem 1.1rem; + transition: + border-color 0.1s ease, + background 0.1s ease; + display: flex; + flex-direction: column; + gap: 0.4rem; +} +.vibenet-app .feature-card:hover { + border-color: var(--border-strong); + background: var(--card-hover); +} +.vibenet-app .feature-card .feature-title { + font-weight: 600; + color: var(--fg); + font-size: 14px; +} +.vibenet-app .feature-card .feature-desc { + color: var(--fg-dim); + font-size: 13px; + line-height: 1.5; +} +.vibenet-app .feature-card a { + font-size: 13px; +} + +/* ---------- contracts list ---------------------------------------------- */ + +.vibenet-app .contracts-list { + background: var(--card); + border: 1px solid var(--border); + border-radius: var(--radius); + overflow: hidden; +} +.vibenet-app .contract-row { + display: grid; + grid-template-columns: 160px 1fr auto; + gap: 1rem; + padding: 0.75rem 1rem; + align-items: center; + border-bottom: 1px solid var(--border); +} +.vibenet-app .contract-row:last-child { + border-bottom: none; +} +.vibenet-app .contract-label { + color: var(--fg-dim); + font-size: 13px; + font-weight: 500; +} +.vibenet-app .contract-addr { + font-family: var(--mono); + font-size: 13px; + word-break: break-all; + color: var(--accent-hover); +} + +/* ---------- forms / buttons --------------------------------------------- */ + +.vibenet-app input, +.vibenet-app button, +.vibenet-app textarea, +.vibenet-app select { + font: inherit; +} + +.vibenet-app input[type="text"], +.vibenet-app input:not([type]) { + padding: 0.55rem 0.75rem; + border-radius: 6px; + border: 1px solid var(--border); + background: var(--bg-elev); + color: var(--fg); + font-family: var(--mono); + font-size: 13px; + width: 100%; +} +.vibenet-app input:focus { + outline: none; + border-color: var(--accent); + box-shadow: 0 0 0 2px rgba(0, 82, 255, 0.25); +} + +.vibenet-app button { + cursor: pointer; + background: var(--accent); + color: #ffffff; + border: 1px solid var(--accent); + padding: 0.55rem 1rem; + border-radius: 6px; + font-weight: 600; + font-size: 14px; + transition: + background 0.1s ease, + border-color 0.1s ease; +} +.vibenet-app button:hover:not(:disabled) { + background: var(--accent-hover); + border-color: var(--accent-hover); +} +.vibenet-app button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.vibenet-app button.secondary { + background: var(--card); + color: var(--fg); + border-color: var(--border); +} +.vibenet-app button.secondary:hover:not(:disabled) { + background: var(--card-hover); + border-color: var(--accent); +} +.vibenet-app button.small { + padding: 0.35rem 0.75rem; + font-size: 12px; +} + +/* ---------- faucet -------------------------------------------------------- */ + +.vibenet-app .faucet-status { + font-size: 13px; + line-height: 1.7; + color: var(--fg-dim); +} +.vibenet-app .faucet-status strong { + color: var(--fg); + font-weight: 600; +} +.vibenet-app .faucet-status code { + font-family: var(--mono); + font-size: 12px; + word-break: break-all; +} + +.vibenet-app .faucet-form { + display: flex; + flex-direction: column; + gap: 0.5rem; + margin-top: 1rem; +} +.vibenet-app .faucet-form label { + font-size: 12px; + color: var(--fg-dim); + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 600; +} + +.vibenet-app .drip-buttons { + display: flex; + gap: 0.75rem; + flex-wrap: wrap; + margin-top: 0.5rem; +} +.vibenet-app .request-btn { + min-height: 48px; + padding: 0.75rem 1.2rem; + font-size: 15px; + flex: 1 1 220px; +} + +.vibenet-app .drip-result { + margin-top: 0.75rem; + border: 1px solid transparent; + border-radius: var(--radius); + padding: 0; +} +.vibenet-app .drip-result:empty { + display: none; +} +.vibenet-app .drip-result.success, +.vibenet-app .drip-result.error { + padding: 0.8rem 0.9rem; + background: var(--bg-elev); + border-color: var(--border); +} +.vibenet-app .drip-result.success { + border-color: rgba(74, 222, 128, 0.35); +} +.vibenet-app .drip-result.error { + border-color: rgba(248, 113, 113, 0.45); + color: var(--err); +} +.vibenet-app .drip-result-title { + font-weight: 600; + margin-bottom: 0.25rem; +} +.vibenet-app .drip-result-meta { + color: var(--fg-dim); + font-size: 13px; + word-break: break-all; +} +.vibenet-app .tx-link { + font-family: var(--mono); +} + +/* ---------- explorer ----------------------------------------------------- */ + +.vibenet-app .stats { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); + gap: 12px; + margin-bottom: 24px; +} +.vibenet-app .stat { + background: var(--card); + border: 1px solid var(--border); + border-left: 3px solid var(--accent); + border-radius: 6px; + padding: 12px 16px; + display: flex; + flex-direction: column; + gap: 4px; +} +.vibenet-app .stat .label { + color: var(--fg-dim); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.06em; +} +.vibenet-app .stat .value { + font-family: var(--mono); + font-size: 20px; +} + +.vibenet-app .search { + display: flex; + gap: 8px; + margin-bottom: 24px; +} +.vibenet-app .search input { + width: 100%; +} +.vibenet-app .search button { + padding: 6px 14px; +} + +.vibenet-app .two-col { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 24px; +} +@media (max-width: 900px) { + .vibenet-app .two-col { + grid-template-columns: 1fr; + } +} + +.vibenet-app table { + width: 100%; + border-collapse: collapse; + background: var(--card); + border: 1px solid var(--border); + border-radius: 6px; + overflow: hidden; +} +.vibenet-app th, +.vibenet-app td { + text-align: left; + padding: 8px 12px; + border-bottom: 1px solid var(--border); + vertical-align: top; +} +.vibenet-app th { + font-weight: 500; + color: var(--fg-dim); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.06em; + background: var(--bg-elev); +} +.vibenet-app tr:last-child td { + border-bottom: none; +} + +.vibenet-app code.addr { + font-size: 11.5px; + letter-spacing: -0.01em; + word-break: break-all; + overflow-wrap: anywhere; +} + +.vibenet-app .kv { + display: grid; + grid-template-columns: 140px 1fr; + gap: 8px 16px; + background: var(--card); + border: 1px solid var(--border); + border-radius: 6px; + padding: 12px 16px; +} +.vibenet-app .kv dt { + color: var(--fg-dim); + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.06em; + font-weight: 500; +} +.vibenet-app .kv dd { + margin: 0; + font-family: var(--mono); + font-size: 13px; + word-break: break-all; +} + +.vibenet-app .empty { + padding: 24px; + text-align: center; + color: var(--fg-dim); + background: var(--card); + border: 1px solid var(--border); + border-radius: 6px; +} + +/* ---------- footer ------------------------------------------------------- */ + +.vibenet-app .site-footer { + max-width: 960px; + margin: 2rem auto 0; + padding: 1.5rem 28px 2rem; + border-top: 1px solid var(--border); + color: var(--fg-dim); + font-size: 13px; + display: flex; + gap: 1.5rem; + align-items: center; + flex-wrap: wrap; +} +.vibenet-app .site-footer code { + font-family: var(--mono); + font-size: 12px; + background: var(--bg-elev); + padding: 0.05em 0.35em; + border-radius: 3px; + border: 1px solid var(--border); +} +.vibenet-app .footer-spacer { + flex: 1; +} + +/* ---------- responsive --------------------------------------------------- */ + +@media (max-width: 640px) { + .vibenet-app main { + padding: 1.5rem 18px 2rem; + } + .vibenet-app .site-header { + padding: 14px 18px; + } + .vibenet-app .site-nav { + gap: 0.75rem; + } + .vibenet-app .chain-row { + grid-template-columns: 80px 1fr; + gap: 0.5rem; + font-size: 13px; + } + .vibenet-app .contract-row { + grid-template-columns: 1fr; + gap: 0.25rem; + } + .vibenet-app .page-title { + font-size: 1.6rem; + } +}