From ba3a19abfe8bc5fe695753249ea06e85b084b669 Mon Sep 17 00:00:00 2001 From: Erhnysr Date: Sat, 23 May 2026 15:35:58 +0300 Subject: [PATCH 1/5] feat: add file-sharing example with shareable download links --- apps/file-sharing/.env.example | 4 + apps/file-sharing/README.md | 65 ++++ apps/file-sharing/package.json | 38 +++ apps/file-sharing/src/server.ts | 165 ++++++++++ apps/file-sharing/tsconfig.json | 12 + pnpm-lock.yaml | 558 ++++++++++++++++++++++++++++++++ 6 files changed, 842 insertions(+) create mode 100644 apps/file-sharing/.env.example create mode 100644 apps/file-sharing/README.md create mode 100644 apps/file-sharing/package.json create mode 100644 apps/file-sharing/src/server.ts create mode 100644 apps/file-sharing/tsconfig.json diff --git a/apps/file-sharing/.env.example b/apps/file-sharing/.env.example new file mode 100644 index 0000000..465e962 --- /dev/null +++ b/apps/file-sharing/.env.example @@ -0,0 +1,4 @@ +SHELBY_ACCOUNT_PRIVATE_KEY=your_private_key_here +SHELBY_API_KEY=your_api_key_here +SHELBY_ACCOUNT_ADDRESS=your_account_address_here +PORT=3000 diff --git a/apps/file-sharing/README.md b/apps/file-sharing/README.md new file mode 100644 index 0000000..5cc476e --- /dev/null +++ b/apps/file-sharing/README.md @@ -0,0 +1,65 @@ +# Shelby File Sharing Example + +A full-stack example demonstrating decentralized file sharing built on the Shelby SDK. Upload any file, receive a unique shareable link, and let anyone download it — every file is SHA-256 hashed and stored on Shelby decentralized hot storage with Aptos blockchain provenance. + +Think of it as a decentralized alternative to WeTransfer. + +## Prerequisites + +- Node.js >= 22 +- pnpm +- A Shelby account with testnet tokens +- Shelby API key + +## Installation + +Navigate to the file-sharing directory and install dependencies: + + cd apps/file-sharing + pnpm install + +## Environment Variables + +Copy the example file and fill in your credentials: + + cp .env.example .env + +Required variables: + + SHELBY_ACCOUNT_PRIVATE_KEY=your_private_key_here + SHELBY_API_KEY=your_api_key_here + PORT=3000 + +## Usage + + pnpm dev + +Open http://localhost:3000 in your browser. + +## How It Works + +1. Upload — User selects a file; the server pushes it to Shelby via client.upload() +2. Hash — A SHA-256 digest is computed locally before upload for integrity verification +3. Link — A unique drop ID is generated and stored in drops.json +4. Share — Anyone with the link can hit /drop/:id/download to stream the file from Shelby via client.download() +5. Verify — The SHA-256 hash is displayed on the download page so recipients can verify file integrity + +## API + +| Method | Path | Description | +|--------|------|-------------| +| POST | /upload | Upload a file multipart/form-data | +| GET | /drop/:id | Get drop metadata | +| GET | /drop/:id/download | Download file from Shelby | +| GET | /drops | List all drops | + +## Project Structure + + apps/file-sharing/ + src/ + server.ts # Express server + Shelby SDK integration + public/ # Static frontend + .env.example + package.json + README.md + tsconfig.json diff --git a/apps/file-sharing/package.json b/apps/file-sharing/package.json new file mode 100644 index 0000000..6a0ff4b --- /dev/null +++ b/apps/file-sharing/package.json @@ -0,0 +1,38 @@ +{ + "name": "@shelby-protocol/file-sharing-example", + "version": "1.0.0", + "description": "An example app demonstrating decentralized file sharing with shareable download links using the Shelby SDK", + "type": "module", + "scripts": { + "dev": "tsx --env-file=.env src/server.ts", + "lint": "biome check .", + "fmt": "biome check . --write" + }, + "keywords": [ + "shelby", + "sdk", + "blob", + "storage", + "file-sharing" + ], + "author": "erhnysr", + "license": "MIT", + "dependencies": { + "@aptos-labs/ts-sdk": "^5.1.1", + "@shelby-protocol/sdk": "latest", + "express": "^4.21.2", + "multer": "^2.0.1", + "uuid": "^11.1.0" + }, + "devDependencies": { + "@biomejs/biome": "2.2.4", + "@types/express": "^5.0.3", + "@types/multer": "^1.4.12", + "@types/uuid": "^10.0.0", + "tsx": "^4.20.5", + "typescript": "^5.9.2" + }, + "engines": { + "node": ">=22" + } +} diff --git a/apps/file-sharing/src/server.ts b/apps/file-sharing/src/server.ts new file mode 100644 index 0000000..c1e549c --- /dev/null +++ b/apps/file-sharing/src/server.ts @@ -0,0 +1,165 @@ +import crypto from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import { Readable } from "node:stream"; +import { pipeline } from "node:stream/promises"; +import type { ReadableStream } from "node:stream/web"; +import { Account, AccountAddress, Ed25519PrivateKey, Network } from "@aptos-labs/ts-sdk"; +import { ShelbyNodeClient } from "@shelby-protocol/sdk/node"; +import express from "express"; +import multer from "multer"; +import { v4 as uuidv4 } from "uuid"; + +const PORT = process.env.PORT ?? 3000; +const DB_PATH = path.join(process.cwd(), "drops.json"); +const UPLOAD_DIR = path.join(process.cwd(), "uploads"); +const TTL_MICROS = 30 * 24 * 60 * 60 * 1_000_000; + +if (!process.env.SHELBY_ACCOUNT_PRIVATE_KEY) { + throw new Error("Missing SHELBY_ACCOUNT_PRIVATE_KEY"); +} +if (!process.env.SHELBY_API_KEY) { + throw new Error("Missing SHELBY_API_KEY"); +} +if (!process.env.SHELBY_ACCOUNT_ADDRESS) { + throw new Error("Missing SHELBY_ACCOUNT_ADDRESS"); +} + +const client = new ShelbyNodeClient({ + network: Network.TESTNET, + apiKey: process.env.SHELBY_API_KEY, +}); + +const signer = Account.fromPrivateKey({ + privateKey: new Ed25519PrivateKey(process.env.SHELBY_ACCOUNT_PRIVATE_KEY), +}); + +const accountAddress = AccountAddress.fromString(process.env.SHELBY_ACCOUNT_ADDRESS); + +interface Drop { + id: string; + fileName: string; + fileSize: number; + blobName: string; + sha256: string; + uploadedAt: string; + expiresAt: string; + downloads: number; +} + +function loadDB(): Record { + if (!fs.existsSync(DB_PATH)) return {}; + return JSON.parse(fs.readFileSync(DB_PATH, "utf8")) as Record; +} + +function saveDB(db: Record): void { + fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2)); +} + +if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true }); + +const upload = multer({ dest: UPLOAD_DIR }); +const app = express(); + +app.use(express.json()); +app.use(express.static(path.join(import.meta.dirname, "..", "public"))); + +app.post("/upload", upload.single("file"), async (req, res) => { + if (!req.file) { + res.status(400).json({ error: "No file provided" }); + return; + } + + const { originalname, path: tmpPath, size } = req.file; + const safeName = originalname.replace(/[^a-zA-Z0-9._-]/g, "_"); + const id = uuidv4().split("-")[0]; + const blobName = `file-sharing/${id}-${safeName}`; + const fileData = fs.readFileSync(tmpPath); + const sha256 = crypto.createHash("sha256").update(fileData).digest("hex"); + const expirationMicros = Date.now() * 1000 + TTL_MICROS; + + try { + await client.upload({ + blobData: fileData, + signer, + blobName, + expirationMicros, + }); + + fs.unlinkSync(tmpPath); + + const drop: Drop = { + id, + fileName: originalname, + fileSize: size, + blobName, + sha256, + uploadedAt: new Date().toISOString(), + expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), + downloads: 0, + }; + + const db = loadDB(); + db[id] = drop; + saveDB(db); + + console.log(`✓ Uploaded ${originalname} → ${blobName}`); + res.json({ success: true, id, sha256, expiresAt: drop.expiresAt }); + } catch (err) { + if (fs.existsSync(tmpPath)) fs.unlinkSync(tmpPath); + console.error("Upload error:", err); + res.status(500).json({ error: "Upload failed" }); + } +}); + +app.get("/drop/:id", (req, res) => { + const db = loadDB(); + const drop = db[req.params.id]; + if (!drop) { + res.status(404).json({ error: "Drop not found" }); + return; + } + res.json(drop); +}); + +app.get("/drop/:id/download", async (req, res) => { + const db = loadDB(); + const drop = db[req.params.id]; + if (!drop) { + res.status(404).json({ error: "Drop not found" }); + return; + } + + try { + const { readable } = await client.download({ + account: accountAddress, + blobName: drop.blobName, + }); + + db[drop.id].downloads += 1; + saveDB(db); + + res.setHeader("Content-Disposition", `attachment; filename="${drop.fileName}"`); + res.setHeader("Content-Type", "application/octet-stream"); + + await pipeline( + Readable.fromWeb(readable as ReadableStream), + res, + ); + } catch (err) { + console.error("Download error:", err); + res.status(500).json({ error: "Download failed" }); + } +}); + +app.get("/drops", (_req, res) => { + const db = loadDB(); + const list = Object.values(db).sort( + (a, b) => new Date(b.uploadedAt).getTime() - new Date(a.uploadedAt).getTime(), + ); + res.json(list); +}); + +app.listen(PORT, () => { + console.log(`\n⚡ Shelby File Sharing running at http://localhost:${PORT}\n`); +}); diff --git a/apps/file-sharing/tsconfig.json b/apps/file-sharing/tsconfig.json new file mode 100644 index 0000000..ad2682f --- /dev/null +++ b/apps/file-sharing/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "outDir": "dist" + }, + "include": ["src/**/*"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index d0989b1..1e088ae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -223,6 +223,43 @@ importers: specifier: ~5.9.3 version: 5.9.3 + apps/file-sharing: + dependencies: + '@aptos-labs/ts-sdk': + specifier: ^5.1.1 + version: 5.2.1(got@11.8.6) + '@shelby-protocol/sdk': + specifier: latest + version: 0.3.1(@aptos-labs/ts-sdk@5.2.1(got@11.8.6)) + express: + specifier: ^4.21.2 + version: 4.22.2 + multer: + specifier: ^2.0.1 + version: 2.1.1 + uuid: + specifier: ^11.1.0 + version: 11.1.1 + devDependencies: + '@biomejs/biome': + specifier: 2.2.4 + version: 2.2.4 + '@types/express': + specifier: ^5.0.3 + version: 5.0.6 + '@types/multer': + specifier: ^1.4.12 + version: 1.4.13 + '@types/uuid': + specifier: ^10.0.0 + version: 10.0.0 + tsx: + specifier: ^4.20.5 + version: 4.21.0 + typescript: + specifier: ^5.9.2 + version: 5.9.3 + apps/list-blob: dependencies: '@aptos-labs/ts-sdk': @@ -2050,6 +2087,11 @@ packages: peerDependencies: '@aptos-labs/ts-sdk': ^2.0.1 || ^3.0.0 || ^4.0.0 || ^5.0.0 + '@shelby-protocol/sdk@0.3.1': + resolution: {integrity: sha512-4Q3Q6BuK/IhS2Z0Eqb8LuVORd8udFFI9zdWswxdl0CHcmFK4h9Ysb4BQdWcHE8LfpwUOi+U54Gk55t7WW6Ns6w==} + peerDependencies: + '@aptos-labs/ts-sdk': ^5.2.1 || ^6.0.0 + '@shelby-protocol/solana-kit@0.2.0': resolution: {integrity: sha512-lKIBrVNZxeIod1RlG4VM+YcBbZIkIWLp9sBlae7H+EV7dzE/t5ECUuKb9MrH1a/S213rAnrV8OpxDazoYp39vw==} peerDependencies: @@ -2950,6 +2992,9 @@ packages: '@tybys/wasm-util@0.10.1': resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + '@types/cacheable-request@6.0.3': resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} @@ -2968,9 +3013,18 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@5.0.6': + resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} + '@types/http-cache-semantics@4.2.0': resolution: {integrity: sha512-L3LgimLHXtGkWikKnsPg0/VFx9OGZaC+eN1u4r+OB1XRqH3meBIAVC2zr1WdMH+RHmnRkqliQAOHNJ/E0j/e0Q==} + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -2983,6 +3037,9 @@ packages: '@types/ms@2.1.0': resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/multer@1.4.13': + resolution: {integrity: sha512-bhhdtPw7JqCiEfC9Jimx5LqX9BDIPJEh2q/fQ4bqbBPtyEZYr3cvF22NwG0DmPZNYA0CAf2CnqDB4KIGGpJcaw==} + '@types/node@12.20.55': resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} @@ -3004,6 +3061,12 @@ packages: '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/qs@6.15.1': + resolution: {integrity: sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/react-dom@18.3.7': resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} peerDependencies: @@ -3023,6 +3086,15 @@ packages: '@types/responselike@1.0.3': resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-static@2.2.0': + resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} + + '@types/uuid@10.0.0': + resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/uuid@8.3.4': resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} @@ -3314,6 +3386,10 @@ packages: zod: optional: true + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -3338,6 +3414,9 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + append-field@1.0.0: + resolution: {integrity: sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -3353,6 +3432,9 @@ packages: resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} engines: {node: '>= 0.4'} + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + array-includes@3.1.9: resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} engines: {node: '>= 0.4'} @@ -3436,6 +3518,10 @@ packages: bn.js@5.2.2: resolution: {integrity: sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==} + body-parser@1.20.5: + resolution: {integrity: sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + borsh@0.7.0: resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} @@ -3463,6 +3549,9 @@ packages: bs58@6.0.0: resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + buffer-layout@1.2.2: resolution: {integrity: sha512-kWSuLN694+KTk8SrYvCqwP2WcgQjoRCiF5b4QDvkkz8EmgD+aWAIceGFKMIAdmF/pH+vpgNV3d3kAKorcdAmWA==} engines: {node: '>=4.5'} @@ -3474,6 +3563,14 @@ packages: resolution: {integrity: sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==} engines: {node: '>=6.14.2'} + busboy@1.6.0: + resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} + engines: {node: '>=10.16.0'} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + cac@6.7.14: resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} engines: {node: '>=8'} @@ -3571,9 +3668,28 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concat-stream@2.0.0: + resolution: {integrity: sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==} + engines: {'0': node >= 6.0} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + core-util-is@1.0.3: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} @@ -3637,6 +3753,14 @@ packages: resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} engines: {node: '>=0.11'} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + debug@3.2.7: resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==} peerDependencies: @@ -3709,10 +3833,18 @@ packages: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-browser@5.3.0: resolution: {integrity: sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==} @@ -3742,12 +3874,19 @@ packages: ed2curve@0.3.0: resolution: {integrity: sha512-8w2fmmq3hv9rCrcI7g9hms2pMunQr1JINfcjwR9tAyZqhtyaMN991lF/ZfHfr5tzZQ8c7y7aBgZbjfbd0fjFwQ==} + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + end-of-stream@1.4.5: resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} @@ -3812,6 +3951,9 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -3935,6 +4077,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + eth-rpc-errors@4.0.3: resolution: {integrity: sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==} @@ -3958,6 +4104,10 @@ packages: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} + express@4.22.2: + resolution: {integrity: sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==} + engines: {node: '>= 0.10.0'} + extension-port-stream@3.0.0: resolution: {integrity: sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw==} engines: {node: '>=12.0.0'} @@ -4009,6 +4159,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -4041,6 +4195,10 @@ packages: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + fraction.js@5.3.4: resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} @@ -4058,6 +4216,10 @@ packages: react-dom: optional: true + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + fsevents@2.3.3: resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} @@ -4186,6 +4348,10 @@ packages: http-cache-semantics@4.2.0: resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + http2-wrapper@1.0.3: resolution: {integrity: sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==} engines: {node: '>=10.19.0'} @@ -4193,6 +4359,10 @@ packages: humanize-ms@1.2.1: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -4219,6 +4389,10 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + is-arguments@1.2.0: resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} engines: {node: '>= 0.4'} @@ -4545,10 +4719,21 @@ packages: media-query-parser@2.0.2: resolution: {integrity: sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==} + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + micro-ftch@0.3.1: resolution: {integrity: sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==} @@ -4564,6 +4749,11 @@ packages: resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} engines: {node: '>= 0.6'} + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} engines: {node: '>=4'} @@ -4613,12 +4803,19 @@ packages: react-dom: optional: true + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + multer@2.1.1: + resolution: {integrity: sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==} + engines: {node: '>= 10.16.0'} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4632,6 +4829,10 @@ packages: natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + next-themes@0.4.6: resolution: {integrity: sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==} peerDependencies: @@ -4744,6 +4945,10 @@ packages: resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} engines: {node: '>= 0.4'} + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} @@ -4808,6 +5013,10 @@ packages: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -4819,6 +5028,9 @@ packages: path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + path-to-regexp@0.1.13: + resolution: {integrity: sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==} + pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -4878,6 +5090,10 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + proxy-from-env@1.1.0: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} @@ -4892,6 +5108,10 @@ packages: resolution: {integrity: sha512-gjVMHOt7CX+BQd7JLQ9fnS4kJK4Lj4u+Conq52tcCbW7YH3mATTtBbTMA+7cQ1rKOkDo61olFHJReawe+XFxIA==} engines: {node: '>= 20.19.0'} + qs@6.15.2: + resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==} + engines: {node: '>=0.6'} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -4899,6 +5119,14 @@ packages: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + react-dom@19.2.4: resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: @@ -5030,6 +5258,9 @@ packages: resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} engines: {node: '>= 0.4'} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} @@ -5042,6 +5273,14 @@ packages: engines: {node: '>=10'} hasBin: true + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -5054,6 +5293,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + sharp@0.34.5: resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -5112,6 +5354,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} @@ -5125,6 +5371,10 @@ packages: stream-json@1.9.1: resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} + streamsearch@1.1.0: + resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} + engines: {node: '>=10.0.0'} + string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -5237,6 +5487,10 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + toml@3.0.0: resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} @@ -5307,6 +5561,10 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + typed-array-buffer@1.0.3: resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} engines: {node: '>= 0.4'} @@ -5323,6 +5581,9 @@ packages: resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} engines: {node: '>= 0.4'} + typedarray@0.0.6: + resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} + typescript-eslint@8.53.0: resolution: {integrity: sha512-xHURCQNxZ1dsWn0sdOaOfCSQG0HKeqSj9OexIxrz6ypU6wHYOdX2I3D2b8s8wFSsSOYJb+6q283cLiLlkEsBYw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -5363,6 +5624,10 @@ packages: undici-types@7.19.2: resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -5415,14 +5680,27 @@ packages: util@0.12.5: resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@11.1.1: + resolution: {integrity: sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==} + hasBin: true + uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + viem@2.44.2: resolution: {integrity: sha512-nHY872t/T3flLpVsnvQT/89bwbrJwxaL917FDv7Oxy4E5FWIFkokRQOKXG3P+hgl30QYVZxi9o2SUHLnebycxw==} peerDependencies: @@ -7250,6 +7528,17 @@ snapshots: p-limit: 7.1.1 zod: 3.25.76 + '@shelby-protocol/sdk@0.3.1(@aptos-labs/ts-sdk@5.2.1(got@11.8.6))': + dependencies: + '@aptos-labs/ts-sdk': 5.2.1(got@11.8.6) + '@shelby-protocol/clay-codes': 0.0.2 + '@shelby-protocol/reed-solomon': 0.0.1 + graphql: 16.12.0 + graphql-request: 7.4.0(graphql@16.12.0) + graphql-tag: 2.12.6(graphql@16.12.0) + p-limit: 7.1.1 + zod: 3.25.76 + '@shelby-protocol/solana-kit@0.2.0(@wallet-standard/core@1.1.1)(bs58@6.0.0)(bufferutil@4.1.0)(got@11.8.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@5.0.10)': dependencies: '@aptos-labs/derived-wallet-solana': 0.12.0(@aptos-labs/ts-sdk@5.2.1(got@11.8.6))(@wallet-standard/core@1.1.1)(bs58@6.0.0)(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) @@ -8314,6 +8603,11 @@ snapshots: tslib: 2.8.1 optional: true + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 22.19.7 + '@types/cacheable-request@6.0.3': dependencies: '@types/http-cache-semantics': 4.2.0 @@ -8338,8 +8632,23 @@ snapshots: '@types/estree@1.0.8': {} + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 22.19.7 + '@types/qs': 6.15.1 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@5.0.6': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 5.1.1 + '@types/serve-static': 2.2.0 + '@types/http-cache-semantics@4.2.0': {} + '@types/http-errors@2.0.5': {} + '@types/json-schema@7.0.15': {} '@types/json5@0.0.29': {} @@ -8350,6 +8659,10 @@ snapshots: '@types/ms@2.1.0': {} + '@types/multer@1.4.13': + dependencies: + '@types/express': 5.0.6 + '@types/node@12.20.55': {} '@types/node@20.19.30': @@ -8375,6 +8688,10 @@ snapshots: '@types/prop-types@15.7.15': {} + '@types/qs@6.15.1': {} + + '@types/range-parser@1.2.7': {} + '@types/react-dom@18.3.7(@types/react@18.3.27)': dependencies: '@types/react': 18.3.27 @@ -8396,6 +8713,17 @@ snapshots: dependencies: '@types/node': 22.19.7 + '@types/send@1.2.1': + dependencies: + '@types/node': 22.19.7 + + '@types/serve-static@2.2.0': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 22.19.7 + + '@types/uuid@10.0.0': {} + '@types/uuid@8.3.4': {} '@types/ws@7.4.7': @@ -8681,6 +9009,11 @@ snapshots: typescript: 5.9.3 zod: 4.3.5 + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + acorn-jsx@5.3.2(acorn@8.15.0): dependencies: acorn: 8.15.0 @@ -8704,6 +9037,8 @@ snapshots: dependencies: color-convert: 2.0.1 + append-field@1.0.0: {} + argparse@2.0.1: {} aria-hidden@1.2.6: @@ -8717,6 +9052,8 @@ snapshots: call-bound: 1.0.4 is-array-buffer: 3.0.5 + array-flatten@1.1.1: {} + array-includes@3.1.9: dependencies: call-bind: 1.0.8 @@ -8826,6 +9163,23 @@ snapshots: bn.js@5.2.2: {} + body-parser@1.20.5: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.15.2 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + borsh@0.7.0: dependencies: bn.js: 5.2.2 @@ -8863,6 +9217,8 @@ snapshots: dependencies: base-x: 5.0.1 + buffer-from@1.1.2: {} + buffer-layout@1.2.2: {} buffer@6.0.3: @@ -8874,6 +9230,12 @@ snapshots: dependencies: node-gyp-build: 4.8.4 + busboy@1.6.0: + dependencies: + streamsearch: 1.1.0 + + bytes@3.1.2: {} + cac@6.7.14: {} cacheable-lookup@5.0.4: {} @@ -8968,8 +9330,25 @@ snapshots: concat-map@0.0.1: {} + concat-stream@2.0.0: + dependencies: + buffer-from: 1.1.2 + inherits: 2.0.4 + readable-stream: 3.6.2 + typedarray: 0.0.6 + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + convert-source-map@2.0.0: {} + cookie-signature@1.0.7: {} + + cookie@0.7.2: {} + core-util-is@1.0.3: {} crc-32@1.2.2: {} @@ -9032,6 +9411,10 @@ snapshots: dependencies: '@babel/runtime': 7.28.6 + debug@2.6.9: + dependencies: + ms: 2.0.0 + debug@3.2.7: dependencies: ms: 2.1.3 @@ -9076,8 +9459,12 @@ snapshots: delayed-stream@1.0.0: {} + depd@2.0.0: {} + dequal@2.0.3: {} + destroy@1.2.0: {} + detect-browser@5.3.0: {} detect-libc@2.1.2: {} @@ -9107,10 +9494,14 @@ snapshots: dependencies: tweetnacl: 1.0.3 + ee-first@1.1.1: {} + electron-to-chromium@1.5.267: {} emoji-regex@9.2.2: {} + encodeurl@2.0.0: {} + end-of-stream@1.4.5: dependencies: once: 1.4.0 @@ -9274,6 +9665,8 @@ snapshots: escalade@3.2.0: {} + escape-html@1.0.3: {} + escape-string-regexp@4.0.0: {} eslint-config-next@16.0.10(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): @@ -9555,6 +9948,8 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + eth-rpc-errors@4.0.3: dependencies: fast-safe-stringify: 2.1.1 @@ -9587,6 +9982,42 @@ snapshots: expect-type@1.3.0: {} + express@4.22.2: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.5 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.13 + proxy-addr: 2.0.7 + qs: 6.15.2 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + extension-port-stream@3.0.0: dependencies: readable-stream: 3.6.2 @@ -9633,6 +10064,18 @@ snapshots: dependencies: to-regex-range: 5.0.1 + finalhandler@1.3.2: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -9663,6 +10106,8 @@ snapshots: dependencies: fetch-blob: 3.2.0 + forwarded@0.2.0: {} + fraction.js@5.3.4: {} framer-motion@12.29.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4): @@ -9674,6 +10119,8 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + fresh@0.5.2: {} + fsevents@2.3.3: optional: true @@ -9805,6 +10252,14 @@ snapshots: http-cache-semantics@4.2.0: {} + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + http2-wrapper@1.0.3: dependencies: quick-lru: 5.1.1 @@ -9814,6 +10269,10 @@ snapshots: dependencies: ms: 2.1.3 + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + ieee754@1.2.1: {} ignore@5.3.2: {} @@ -9835,6 +10294,8 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 + ipaddr.js@1.9.1: {} + is-arguments@1.2.0: dependencies: call-bound: 1.0.4 @@ -10142,8 +10603,14 @@ snapshots: dependencies: '@babel/runtime': 7.28.6 + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + merge2@1.4.1: {} + methods@1.1.2: {} + micro-ftch@0.3.1: {} micromatch@4.0.8: @@ -10157,6 +10624,8 @@ snapshots: dependencies: mime-db: 1.52.0 + mime@1.6.0: {} + mimic-response@1.0.1: {} mimic-response@3.1.0: {} @@ -10191,16 +10660,27 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) + ms@2.0.0: {} + ms@2.1.2: {} ms@2.1.3: {} + multer@2.1.1: + dependencies: + append-field: 1.0.0 + busboy: 1.6.0 + concat-stream: 2.0.0 + type-is: 1.6.18 + nanoid@3.3.11: {} napi-postinstall@0.3.4: {} natural-compare@1.4.0: {} + negotiator@0.6.3: {} + next-themes@0.4.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: react: 19.2.4 @@ -10318,6 +10798,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.1 + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + once@1.4.0: dependencies: wrappy: 1.0.2 @@ -10387,12 +10871,16 @@ snapshots: dependencies: callsites: 3.1.0 + parseurl@1.3.3: {} + path-exists@4.0.0: {} path-key@3.1.1: {} path-parse@1.0.7: {} + path-to-regexp@0.1.13: {} + pathe@2.0.3: {} pathval@2.0.1: {} @@ -10440,6 +10928,11 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + proxy-from-env@1.1.0: {} pump@3.0.3: @@ -10451,10 +10944,23 @@ snapshots: qr@0.5.4: {} + qs@6.15.2: + dependencies: + side-channel: 1.1.0 + queue-microtask@1.2.3: {} quick-lru@5.1.1: {} + range-parser@1.2.1: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 @@ -10655,12 +11161,41 @@ snapshots: es-errors: 1.3.0 is-regex: 1.2.1 + safer-buffer@2.1.2: {} + scheduler@0.27.0: {} semver@6.3.1: {} semver@7.7.3: {} + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -10683,6 +11218,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.1.1 + setprototypeof@1.2.0: {} + sharp@0.34.5: dependencies: '@img/colour': 1.0.0 @@ -10782,6 +11319,8 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.2: {} + std-env@3.10.0: {} stop-iteration-iterator@1.1.0: @@ -10795,6 +11334,8 @@ snapshots: dependencies: stream-chain: 2.2.5 + streamsearch@1.1.0: {} + string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.8 @@ -10911,6 +11452,8 @@ snapshots: dependencies: is-number: 7.0.0 + toidentifier@1.0.1: {} + toml@3.0.0: {} tr46@0.0.3: {} @@ -10972,6 +11515,11 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + typed-array-buffer@1.0.3: dependencies: call-bound: 1.0.4 @@ -11005,6 +11553,8 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 + typedarray@0.0.6: {} + typescript-eslint@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@typescript-eslint/eslint-plugin': 8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) @@ -11039,6 +11589,8 @@ snapshots: undici-types@7.19.2: {} + unpipe@1.0.0: {} + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.4 @@ -11125,10 +11677,16 @@ snapshots: is-typed-array: 1.1.15 which-typed-array: 1.1.20 + utils-merge@1.0.1: {} + + uuid@11.1.1: {} + uuid@8.3.2: {} uuid@9.0.1: {} + vary@1.1.2: {} + viem@2.44.2(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10)(zod@4.3.5): dependencies: '@noble/curves': 1.9.1 From ea2c1b631020efc8380105b612cbb812e0af4af4 Mon Sep 17 00:00:00 2001 From: Erhnysr Date: Sat, 23 May 2026 16:00:45 +0300 Subject: [PATCH 2/5] fix: sanitize Content-Disposition filename and guard against post-header errors --- apps/file-sharing/src/server.ts | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/apps/file-sharing/src/server.ts b/apps/file-sharing/src/server.ts index c1e549c..7dd5370 100644 --- a/apps/file-sharing/src/server.ts +++ b/apps/file-sharing/src/server.ts @@ -56,6 +56,11 @@ function saveDB(db: Record): void { fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2)); } +// Sanitize filename for Content-Disposition header to prevent header injection +function sanitizeFilename(name: string): string { + return name.replace(/[^\w.\-]/g, "_"); +} + if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true }); const upload = multer({ dest: UPLOAD_DIR }); @@ -130,25 +135,33 @@ app.get("/drop/:id/download", async (req, res) => { return; } + // Sanitize filename to prevent Content-Disposition header injection + const safeFileName = sanitizeFilename(drop.fileName); + try { const { readable } = await client.download({ account: accountAddress, blobName: drop.blobName, }); + // Set headers BEFORE starting the stream + res.setHeader("Content-Disposition", `attachment; filename="${safeFileName}"`); + res.setHeader("Content-Type", "application/octet-stream"); + + // Update download count db[drop.id].downloads += 1; saveDB(db); - res.setHeader("Content-Disposition", `attachment; filename="${drop.fileName}"`); - res.setHeader("Content-Type", "application/octet-stream"); - await pipeline( Readable.fromWeb(readable as ReadableStream), res, ); } catch (err) { - console.error("Download error:", err); - res.status(500).json({ error: "Download failed" }); + // Only send error response if headers haven't been sent yet + if (!res.headersSent) { + console.error("Download error:", err); + res.status(500).json({ error: "Download failed" }); + } } }); From f6602109b296755233cd7324b443c199f0087824 Mon Sep 17 00:00:00 2001 From: Erhnysr Date: Sat, 23 May 2026 16:03:17 +0300 Subject: [PATCH 3/5] fix: add async write mutex for drops.json and serve landing page for /d/:id routes --- apps/file-sharing/src/server.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/apps/file-sharing/src/server.ts b/apps/file-sharing/src/server.ts index 7dd5370..5d8e461 100644 --- a/apps/file-sharing/src/server.ts +++ b/apps/file-sharing/src/server.ts @@ -47,6 +47,9 @@ interface Drop { downloads: number; } +// Simple async mutex to prevent concurrent drops.json writes +let dbWriteLock: Promise = Promise.resolve(); + function loadDB(): Record { if (!fs.existsSync(DB_PATH)) return {}; return JSON.parse(fs.readFileSync(DB_PATH, "utf8")) as Record; @@ -56,7 +59,15 @@ function saveDB(db: Record): void { fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2)); } -// Sanitize filename for Content-Disposition header to prevent header injection +function updateDB(fn: (db: Record) => void): Promise { + dbWriteLock = dbWriteLock.then(() => { + const db = loadDB(); + fn(db); + saveDB(db); + }); + return dbWriteLock; +} + function sanitizeFilename(name: string): string { return name.replace(/[^\w.\-]/g, "_"); } @@ -69,6 +80,11 @@ const app = express(); app.use(express.json()); app.use(express.static(path.join(import.meta.dirname, "..", "public"))); +// Serve the download page for shareable drop links +app.get("/d/:id", (_req, res) => { + res.sendFile(path.join(import.meta.dirname, "..", "public", "index.html")); +}); + app.post("/upload", upload.single("file"), async (req, res) => { if (!req.file) { res.status(400).json({ error: "No file provided" }); @@ -104,9 +120,7 @@ app.post("/upload", upload.single("file"), async (req, res) => { downloads: 0, }; - const db = loadDB(); - db[id] = drop; - saveDB(db); + await updateDB((db) => { db[id] = drop; }); console.log(`✓ Uploaded ${originalname} → ${blobName}`); res.json({ success: true, id, sha256, expiresAt: drop.expiresAt }); @@ -135,7 +149,6 @@ app.get("/drop/:id/download", async (req, res) => { return; } - // Sanitize filename to prevent Content-Disposition header injection const safeFileName = sanitizeFilename(drop.fileName); try { @@ -144,20 +157,16 @@ app.get("/drop/:id/download", async (req, res) => { blobName: drop.blobName, }); - // Set headers BEFORE starting the stream res.setHeader("Content-Disposition", `attachment; filename="${safeFileName}"`); res.setHeader("Content-Type", "application/octet-stream"); - // Update download count - db[drop.id].downloads += 1; - saveDB(db); + await updateDB((db) => { db[drop.id].downloads += 1; }); await pipeline( Readable.fromWeb(readable as ReadableStream), res, ); } catch (err) { - // Only send error response if headers haven't been sent yet if (!res.headersSent) { console.error("Download error:", err); res.status(500).json({ error: "Download failed" }); From 38b4ac7f85836d389c4814e3e35b0475d576eb36 Mon Sep 17 00:00:00 2001 From: Erhnysr Date: Thu, 8 Oct 2026 20:50:37 +0000 Subject: [PATCH 4/5] fix(file-sharing): add the missing frontend and keep the drops.json lock usable after an error The app served ../public and sent public/index.html for /d/:id links, but no public directory was committed, so / and every share link returned 404. Add public/index.html: an upload form that shows the share link and SHA-256, and the download page for /d/:id that reads /drop/:id. updateDB chained each update onto the previous promise, so one failed update (a corrupt drops.json, for example) left the lock rejected and every later update was skipped without running. The caller still gets its own error, but the lock now always settles. The download counter also no longer throws when the drop is missing from the file. Use sanitizeFilename for the blob name instead of a second copy of the same regex, apply the formatting biome check asks for, and list SHELBY_ACCOUNT_ADDRESS in the README, which the server requires. --- apps/file-sharing/README.md | 4 +- apps/file-sharing/public/index.html | 120 ++++++++++++++++++++++++++++ apps/file-sharing/src/server.ts | 39 ++++++--- 3 files changed, 152 insertions(+), 11 deletions(-) create mode 100644 apps/file-sharing/public/index.html diff --git a/apps/file-sharing/README.md b/apps/file-sharing/README.md index 5cc476e..e784fdc 100644 --- a/apps/file-sharing/README.md +++ b/apps/file-sharing/README.md @@ -28,6 +28,7 @@ Required variables: SHELBY_ACCOUNT_PRIVATE_KEY=your_private_key_here SHELBY_API_KEY=your_api_key_here + SHELBY_ACCOUNT_ADDRESS=your_account_address_here PORT=3000 ## Usage @@ -58,7 +59,8 @@ Open http://localhost:3000 in your browser. apps/file-sharing/ src/ server.ts # Express server + Shelby SDK integration - public/ # Static frontend + public/ + index.html # Upload page, and the download page served for /d/:id links .env.example package.json README.md diff --git a/apps/file-sharing/public/index.html b/apps/file-sharing/public/index.html new file mode 100644 index 0000000..9bc785d --- /dev/null +++ b/apps/file-sharing/public/index.html @@ -0,0 +1,120 @@ + + + + + + Shelby File Sharing + + + +

Shelby File Sharing

+ + + + + + + + + + diff --git a/apps/file-sharing/src/server.ts b/apps/file-sharing/src/server.ts index 5d8e461..a8d3e88 100644 --- a/apps/file-sharing/src/server.ts +++ b/apps/file-sharing/src/server.ts @@ -4,7 +4,12 @@ import path from "node:path"; import { Readable } from "node:stream"; import { pipeline } from "node:stream/promises"; import type { ReadableStream } from "node:stream/web"; -import { Account, AccountAddress, Ed25519PrivateKey, Network } from "@aptos-labs/ts-sdk"; +import { + Account, + AccountAddress, + Ed25519PrivateKey, + Network, +} from "@aptos-labs/ts-sdk"; import { ShelbyNodeClient } from "@shelby-protocol/sdk/node"; import express from "express"; import multer from "multer"; @@ -34,7 +39,9 @@ const signer = Account.fromPrivateKey({ privateKey: new Ed25519PrivateKey(process.env.SHELBY_ACCOUNT_PRIVATE_KEY), }); -const accountAddress = AccountAddress.fromString(process.env.SHELBY_ACCOUNT_ADDRESS); +const accountAddress = AccountAddress.fromString( + process.env.SHELBY_ACCOUNT_ADDRESS, +); interface Drop { id: string; @@ -60,16 +67,19 @@ function saveDB(db: Record): void { } function updateDB(fn: (db: Record) => void): Promise { - dbWriteLock = dbWriteLock.then(() => { + const run = dbWriteLock.then(() => { const db = loadDB(); fn(db); saveDB(db); }); - return dbWriteLock; + // The caller still sees this update's error, but the lock itself always settles, so one + // failed update (a corrupt drops.json, say) does not skip every update queued after it. + dbWriteLock = run.catch(() => {}); + return run; } function sanitizeFilename(name: string): string { - return name.replace(/[^\w.\-]/g, "_"); + return name.replace(/[^\w.-]/g, "_"); } if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true }); @@ -92,7 +102,7 @@ app.post("/upload", upload.single("file"), async (req, res) => { } const { originalname, path: tmpPath, size } = req.file; - const safeName = originalname.replace(/[^a-zA-Z0-9._-]/g, "_"); + const safeName = sanitizeFilename(originalname); const id = uuidv4().split("-")[0]; const blobName = `file-sharing/${id}-${safeName}`; const fileData = fs.readFileSync(tmpPath); @@ -120,7 +130,9 @@ app.post("/upload", upload.single("file"), async (req, res) => { downloads: 0, }; - await updateDB((db) => { db[id] = drop; }); + await updateDB((db) => { + db[id] = drop; + }); console.log(`✓ Uploaded ${originalname} → ${blobName}`); res.json({ success: true, id, sha256, expiresAt: drop.expiresAt }); @@ -157,10 +169,16 @@ app.get("/drop/:id/download", async (req, res) => { blobName: drop.blobName, }); - res.setHeader("Content-Disposition", `attachment; filename="${safeFileName}"`); + res.setHeader( + "Content-Disposition", + `attachment; filename="${safeFileName}"`, + ); res.setHeader("Content-Type", "application/octet-stream"); - await updateDB((db) => { db[drop.id].downloads += 1; }); + await updateDB((db) => { + const current = db[drop.id]; + if (current) current.downloads += 1; + }); await pipeline( Readable.fromWeb(readable as ReadableStream), @@ -177,7 +195,8 @@ app.get("/drop/:id/download", async (req, res) => { app.get("/drops", (_req, res) => { const db = loadDB(); const list = Object.values(db).sort( - (a, b) => new Date(b.uploadedAt).getTime() - new Date(a.uploadedAt).getTime(), + (a, b) => + new Date(b.uploadedAt).getTime() - new Date(a.uploadedAt).getTime(), ); res.json(list); }); From b049735228cad216d25f1c87a38330646900d780 Mon Sep 17 00:00:00 2001 From: Erhnysr Date: Fri, 9 Oct 2026 19:50:29 +0000 Subject: [PATCH 5/5] fix(file-sharing): open the drop page for share links with a trailing slash Express serves index.html for /d/:id/ as well as /d/:id, but the page only matched the unslashed path, so a link with a trailing slash showed the upload form instead of the file. --- apps/file-sharing/public/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/file-sharing/public/index.html b/apps/file-sharing/public/index.html index 9bc785d..8dfb7b2 100644 --- a/apps/file-sharing/public/index.html +++ b/apps/file-sharing/public/index.html @@ -112,7 +112,7 @@

Shelby File Sharing

}); } - const match = location.pathname.match(/^\/d\/([^/]+)$/); + const match = location.pathname.match(/^\/d\/([^/]+)\/?$/); if (match) showDrop(decodeURIComponent(match[1])); else showUpload();