diff --git a/.dockerignore b/.dockerignore index d8b0603..cede9bf 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,12 @@ -node_modules -.next -dist +node_modules/ +dist/ +.next/ .env .env.local *.log -.git -.turbo +.turbo/ +.git/ +.gitea/ +coverage/ +.DS_Store +docs/ diff --git a/.env.example b/.env.example index 8f0bc3c..4ac732a 100644 --- a/.env.example +++ b/.env.example @@ -1,24 +1,76 @@ # PostgreSQL -DATABASE_URL=postgresql://postgres:postgres@localhost:5432/cryptowallet_devphase3 +# Для локального dev: DB_HOST=localhost +# Для Docker Compose: DB_HOST переопределяется на 'postgres' в docker-compose.yml DB_HOST=localhost DB_PORT=5432 DB_USER=postgres DB_PASSWORD=postgres -DB_NAME=cryptowallet_devphase3 +DB_NAME=cryptowallet_v2 + +# Database Pool +DATABASE_POOL_SIZE=10 +DATABASE_MAX_OVERFLOW=20 +DATABASE_POOL_TIMEOUT=30 +DATABASE_POOL_RECYCLE=3600 +DATABASE_ECHO=false + +# Vault (AppRole auth) +VAULT_ADDR= +VAULT_ROLE_ID= +VAULT_SECRET_ID= +VAULT_MOUNT_POINT=dev-secrets +VAULT_SECRET_PATH=database +VAULT_JWT_KID_PATH=jwt/kid +VAULT_JWT_KIDS_PREFIX=jwt/kids + +# CSRF +CSRF_COOKIE_SECURE=false +CSRF_COOKIE_HTTPONLY=true +CSRF_COOKIE_SAMESITE=Lax +CSRF_COOKIE_PATH=/ +CSRF_COOKIE_DOMAIN= + +# JWT +JWT_ALGORITHM=RS256 +JWT_ACCESS_TTL_SECONDS=900 +JWT_REFRESH_TTL_SECONDS=2592000 +JWT_ISSUER=auth-service +JWT_AUDIENCE=wallet-service + +# Docs +DOCS_USERNAME=admin +DOCS_PASSWORD=admin + +# Redis +REDIS_HOST=keydb +REDIS_PORT=6379 +REDIS_PASSWORD=keydb +REDIS_DB=0 + +# RabbitMQ +RABBIT_EMAIL_CODE_QUEUE=email.verification_code +RABBIT_PUBLISH_PERSIST=true +RABBIT_CONNECT_TIMEOUT=5 + +# Logging +LOG_LEVEL=INFO +LOG_FORMAT=JSON + +# CORS +CORS_ORIGINS=http://localhost:3000,http://localhost:8000 +CORS_ALLOW_CREDENTIALS=true + +# Rate Limiting +RATE_LIMIT_REQUESTS=60 +RATE_LIMIT_WINDOW=60 # Server API_PORT=3001 FRONTEND_URL=http://localhost:3000 RELAY_API_KEY= -# BITOK auth service -BITOK_JWKS_URL=http://localhost:8000/.well-known/jwks.json -BITOK_ISSUER=auth-service -BITOK_AUDIENCE=wallet-service +# TRON +TRON_API_KEY= -# RabbitMQ -RABBITMQ_URL=amqp://guest:guest@localhost:5672/ - -# Rate Limiting -RATE_LIMIT_LOGIN_MAX=5 -RATE_LIMIT_LOGIN_WINDOW_MS=900000 +# Jupiter (Solana DEX aggregator) +JUPITER_API_KEY= diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 8a8b8a6..0000000 --- a/.gitignore +++ /dev/null @@ -1,11 +0,0 @@ -node_modules/ -dist/ -.next/ -.env -.env.local -*.log -.turbo/ -coverage/ -.DS_Store -vault/data/ -vault/init-keys.json diff --git a/BITOK/.dockerignore b/BITOK/.dockerignore deleted file mode 100644 index 4f65d19..0000000 --- a/BITOK/.dockerignore +++ /dev/null @@ -1,22 +0,0 @@ -__pycache__/ -*.pyc -*.pyo -*.pyd -.pytest_cache/ -.mypy_cache/ -.ruff_cache/ -.cache/ - -.venv/ -venv/ - -.env - -.git/ -.gitignore - -dist/ -build/ -*.egg-info/ - -.DS_Store diff --git a/BITOK/.gitignore b/BITOK/.gitignore deleted file mode 100644 index fb62f93..0000000 --- a/BITOK/.gitignore +++ /dev/null @@ -1,141 +0,0 @@ -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# C extensions -*.so -*.pyd -*.dll - -# Distribution / packaging -.Python -build/ -dist/ -downloads/ -eggs/ -.eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -wheels/ -share/python-wheels/ -*.egg-info/ -.installed.cfg -*.egg - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt - -# Unit test / coverage reports -htmlcov/ -.tox/ -.nox/ -.coverage -.coverage.* -.cache/ -.pytest_cache/ -nosetests.xml -coverage.xml -*.cover -*.py,cover -.hypothesis/ - -# Type checkers / linters -.mypy_cache/ -.dmypy.json -dmypy.json -.pyre/ -.pytype/ -.ruff_cache/ - -# Jupyter Notebook -.ipynb_checkpoints/ - -# Environments -.env -.env.* -.venv/ -venv/ -ENV/ -env/ -env.bak/ -venv.bak/ - -# Poetry -poetry.lock - -# Pipenv -Pipfile.lock - -# Hatch -.hatch/ - -# pyenv -.python-version - -# Logs -*.log -logs/ - -# Local databases -*.sqlite3 -*.db - -# Secrets / credentials -secrets.json -credentials.json -*.pem -*.key -*.crt - -# OS generated files -.DS_Store -Thumbs.db -Desktop.ini - -# PyCharm / IntelliJ IDEA -.idea/ -*.iml -out/ - -# VS Code (optional) -.vscode/ - -# Temporary files -*.tmp -*.temp -*.swp -*.swo -*~ - -# Sphinx docs -docs/_build/ - -# mkdocs -site/ - -# celery -celerybeat-schedule -celerybeat.pid - -# mypy compiled cache -.mypy_cache/ - -# pyinstaller -*.manifest -*.spec - -# pytest debug -pytestdebug.log - -# Local config overrides -config.local.py -settings.local.py - -# Vault / local dev secrets -.env.vault -vault.token \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d0994cb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,83 @@ +# syntax=docker/dockerfile:1.7 +# ───────────────────────────────────────────────────────────────────────────── +# Production Dockerfile for CryptoWallet API +# Multi-stage: deps → build → prod-deps → runtime +# Runtime: non-root user, tini (signal handling), wget (healthcheck) +# ───────────────────────────────────────────────────────────────────────────── + +ARG NODE_VERSION=20.19-alpine +ARG PNPM_VERSION=10.28.2 + +# ───────────────────────────────────────────────────────────────────────────── +# Base: node + pnpm +# ───────────────────────────────────────────────────────────────────────────── +FROM node:${NODE_VERSION} AS base +ARG PNPM_VERSION +RUN corepack enable && corepack prepare pnpm@${PNPM_VERSION} --activate +WORKDIR /app +ENV NODE_ENV=production \ + CI=true \ + HUSKY=0 + +# ───────────────────────────────────────────────────────────────────────────── +# deps: install ALL deps (incl. dev) for TypeScript build +# ───────────────────────────────────────────────────────────────────────────── +FROM base AS deps +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY apps/api/package.json apps/api/ +RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \ + pnpm install --frozen-lockfile --prod=false + +# ───────────────────────────────────────────────────────────────────────────── +# build: compile TypeScript → dist/ +# ───────────────────────────────────────────────────────────────────────────── +FROM base AS build +COPY --from=deps /app/node_modules ./node_modules +COPY --from=deps /app/apps/api/node_modules ./apps/api/node_modules +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY apps/api ./apps/api +RUN cd apps/api && pnpm build + +# ───────────────────────────────────────────────────────────────────────────── +# prod-deps: only production dependencies (smaller image) +# ───────────────────────────────────────────────────────────────────────────── +FROM base AS prod-deps +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY apps/api/package.json apps/api/ +RUN --mount=type=cache,id=pnpm-store,target=/root/.local/share/pnpm/store \ + pnpm install --frozen-lockfile --prod + +# ───────────────────────────────────────────────────────────────────────────── +# runtime: minimal production image +# ───────────────────────────────────────────────────────────────────────────── +FROM node:${NODE_VERSION} AS runtime + +# tini for proper PID 1 / signal handling +# wget for healthcheck +RUN apk add --no-cache tini wget && \ + addgroup -S -g 1001 app && \ + adduser -S -u 1001 -G app app + +WORKDIR /app/apps/api + +# Copy production dependencies +COPY --from=prod-deps --chown=app:app /app/node_modules /app/node_modules +COPY --from=prod-deps --chown=app:app /app/apps/api/node_modules ./node_modules + +# Copy compiled code + static assets +COPY --from=build --chown=app:app /app/apps/api/dist ./dist +COPY --from=build --chown=app:app /app/apps/api/swagger.json ./swagger.json +COPY --from=build --chown=app:app /app/apps/api/package.json ./package.json + +USER app + +ENV NODE_ENV=production \ + API_PORT=3001 + +EXPOSE 3001 + +HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \ + CMD wget -qO- http://localhost:3001/api/health || exit 1 + +ENTRYPOINT ["/sbin/tini", "--"] +CMD ["node", "dist/index.js"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..972a3b7 --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# CryptoWallet API — Production Deploy Bundle + +Самодостаточная папка для деплоя на Linux-сервер. Содержит всё нужное для сборки и запуска продакшн-версии API. + +## Состав + +``` +deployserver/ +├── Dockerfile # Multi-stage production build +├── docker-compose.yml # PostgreSQL + API +├── .env.example # Шаблон переменных окружения +├── .dockerignore +├── start.sh # Автоматический deploy скрипт +├── apps/api/ # Исходник API +│ ├── src/ +│ ├── package.json +│ ├── tsconfig.json +│ ├── swagger.json +│ └── .eslintrc.json +├── package.json # Монорепо root +├── pnpm-workspace.yaml +└── pnpm-lock.yaml +``` + +## Требования + +- Ubuntu 20.04+ / Debian 11+ / любой Linux с Docker 24+ +- Docker Compose plugin (`docker compose` команда) +- Исходящий HTTPS на Vault (`corp.vault.elcsa.ru:443`) + +## Быстрый старт + +```bash +# 1. Скопировать папку на сервер (или git clone и cd deployserver) +scp -r deployserver user@server:/opt/cryptowallet +ssh user@server +cd /opt/cryptowallet + +# 2. Установить Docker (если нет) +curl -fsSL https://get.docker.com | sudo sh +sudo usermod -aG docker $USER +newgrp docker + +# 3. Настроить .env +cp .env.example .env +nano .env # заполнить VAULT_ROLE_ID, VAULT_SECRET_ID + +# 4. Запустить +chmod +x start.sh +./start.sh + +# 5. Открыть порт наружу +sudo ufw allow 22/tcp +sudo ufw allow 3001/tcp +sudo ufw enable +``` + +## Проверка + +```bash +curl http://localhost:3001/api/health +# → {"success":true,"data":{"status":"ok"}} + +curl http://:3001/api/health # извне +``` + +Swagger UI: `http://:3001/api/docs` + +## Порты + +| Порт | Назначение | Открыть наружу? | +|------|-----------|-----------------| +| 3001 | API HTTP | ✅ да (`ufw allow 3001`) | +| 5432 | PostgreSQL | ❌ нет (только docker network) | +| 443 (out) | Vault | исходящий, обычно открыт | + +## Управление + +```bash +docker compose logs -f api # смотреть логи +docker compose restart api # рестарт +docker compose down # остановить +docker compose down -v # + удалить БД (ОСТОРОЖНО) +docker compose ps # статус +docker compose exec postgres psql -U postgres cryptowallet_v2 # подключиться к БД +``` + +## Обновление + +```bash +# Скопировать новую версию deployserver/ +docker compose build --pull api +docker compose up -d +``` + +Миграции применятся автоматически при старте API. + +## Безопасность Dockerfile + +- **Non-root user** (uid 1001) — контейнер не работает от root +- **tini** как PID 1 — корректная обработка `SIGTERM` / `SIGKILL` +- **Multi-stage build** — в финальный образ попадают только production deps + компилированный dist +- **Alpine base** — минимальный образ (~150 MB) +- **Healthcheck** — Docker рестартит контейнер если API упал +- **Log rotation** — max 5×20MB логов, не забьёт диск + +## Troubleshooting + +**`Vault AppRole login failed`** +- Проверь VAULT_ROLE_ID / VAULT_SECRET_ID в .env +- Проверь доступ: `curl -v https://corp.vault.elcsa.ru/v1/sys/health` + +**API рестартуется в цикле** +- `docker compose logs api` — смотри ошибку +- Скорее всего БД не поднялась: `docker compose logs postgres` + +**Port 3001 занят** +- `sudo lsof -i :3001` +- Измени порт: в `docker-compose.yml` `"3002:3001"` и в `ufw allow 3002` + +**Нет места на диске** +- `docker system prune -a` — удалит старые образы +- `docker compose logs --tail=0 --no-log-prefix > /dev/null` — логи ротейтятся автоматически + +## Автозапуск при reboot + +Restart policy `unless-stopped` уже настроен. Убедись что Docker стартует: +```bash +sudo systemctl enable docker +``` diff --git a/apps/api/.eslintrc.json b/apps/api/.eslintrc.json new file mode 100644 index 0000000..9223e0a --- /dev/null +++ b/apps/api/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "root": true, + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parserOptions": { + "ecmaVersion": 2022, + "sourceType": "module" + }, + "rules": { + "@typescript-eslint/no-explicit-any": "warn", + "@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }], + "@typescript-eslint/no-namespace": "off", + "no-console": "off" + }, + "ignorePatterns": ["dist/", "node_modules/"] +} diff --git a/apps/api/Dockerfile b/apps/api/Dockerfile deleted file mode 100644 index 14e8b3c..0000000 --- a/apps/api/Dockerfile +++ /dev/null @@ -1,47 +0,0 @@ -# Build stage -FROM node:20-alpine AS builder - -RUN apk add --no-cache python3 make g++ -RUN corepack enable && corepack prepare pnpm@10.28.2 --activate - -WORKDIR /app - -# Copy workspace config -COPY package.json pnpm-workspace.yaml pnpm-lock.yaml turbo.json ./ -COPY apps/api/package.json apps/api/ -COPY packages/shared/package.json packages/shared/ - -# Enable hoisting so tsc can find all deps -RUN echo "node-linker=hoisted" > .npmrc -RUN pnpm install --frozen-lockfile - -# Copy source -COPY apps/api/ apps/api/ -COPY packages/shared/ packages/shared/ - -# Build api (node_modules are hoisted, tsc available at root) -RUN cd apps/api && ../../node_modules/.bin/tsc \ - && rm -f dist/db/migrations/*.d.ts dist/db/migrations/*.d.ts.map dist/db/migrations/*.js.map - -# Runtime stage -FROM node:20-alpine - -RUN apk add --no-cache curl - -WORKDIR /app - -# Copy built output (includes compiled migrations + knexfile in dist/db/) -COPY --from=builder /app/apps/api/dist ./dist -COPY --from=builder /app/apps/api/package.json ./ - -# Copy node_modules (runtime deps including bcrypt native) -COPY --from=builder /app/node_modules ./node_modules -COPY --from=builder /app/apps/api/node_modules ./apps_node_modules - -# Entrypoint -COPY apps/api/docker-entrypoint.sh ./docker-entrypoint.sh -RUN chmod +x ./docker-entrypoint.sh - -EXPOSE 3001 - -ENTRYPOINT ["./docker-entrypoint.sh"] diff --git a/apps/api/docker-entrypoint.sh b/apps/api/docker-entrypoint.sh deleted file mode 100644 index 7fb1b37..0000000 --- a/apps/api/docker-entrypoint.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -set -e - -# If Vault init-keys exist, extract root token -if [ -f /vault/file/init-keys.json ]; then - export VAULT_TOKEN=$(tr -d ' \n' < /vault/file/init-keys.json | grep -o '"root_token":"[^"]*"' | cut -d'"' -f4) - echo "[API] Vault token loaded from init-keys.json" -fi - -# Run migrations -node node_modules/knex/bin/cli.js migrate:latest --knexfile dist/db/knexfile.js -echo "[API] Migrations complete" - -# Start server -exec node dist/index.js diff --git a/apps/api/package.json b/apps/api/package.json index 6a3008c..9677106 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -8,39 +8,33 @@ "start": "node dist/index.js", "migrate": "node --require ts-node/register node_modules/knex/bin/cli.js migrate:latest --knexfile src/db/knexfile.ts", "migrate:rollback": "node --require ts-node/register node_modules/knex/bin/cli.js migrate:rollback --knexfile src/db/knexfile.ts", - "db:fresh": "pnpm db:reset && pnpm migrate", - "db:reset": "node --require ts-node/register src/db/reset-db.ts", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "eslint src/ --ext .ts" }, "dependencies": { - "@cryptowallet/shared": "workspace:*", - "amqplib": "^1.0.3", - "bcrypt": "^5.1.1", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.0", "ethers": "5.7.2", "express": "^4.21.0", - "express-rate-limit": "^7.4.0", "helmet": "^8.0.0", "jose": "^6.2.2", - "jsonwebtoken": "^9.0.0", "knex": "^3.1.0", "pg": "^8.13.0", + "swagger-ui-express": "^5.0.1", "ulidx": "^2.4.1", - "uuid": "^11.0.0", "zod": "^3.23.0" }, "devDependencies": { - "@types/amqplib": "^0.10.8", - "@types/bcrypt": "^5.0.2", "@types/cookie-parser": "^1.4.7", "@types/cors": "^2.8.17", "@types/express": "^5.0.0", "@types/express-serve-static-core": "^5.1.1", - "@types/jsonwebtoken": "^9.0.0", "@types/node": "^20.0.0", - "@types/uuid": "^10.0.0", + "@types/swagger-ui-express": "^4.1.8", + "@typescript-eslint/eslint-plugin": "^7.18.0", + "@typescript-eslint/parser": "^7.18.0", + "eslint": "^8.57.1", "ts-node": "^10.9.0", "ts-node-dev": "^2.0.0", "typescript": "^5.6.0" diff --git a/apps/api/src/app.ts b/apps/api/src/app.ts index 1ac5e5d..6362bcd 100644 --- a/apps/api/src/app.ts +++ b/apps/api/src/app.ts @@ -2,11 +2,13 @@ import express from 'express'; import helmet from 'helmet'; import cors from 'cors'; import cookieParser from 'cookie-parser'; +import swaggerUi from 'swagger-ui-express'; import { env } from './config/env'; +import { swaggerSpec } from './config/swagger'; +import { traceMiddleware } from './middleware/trace'; +import { authMiddleware } from './middleware/auth'; import { errorHandler } from './middleware/error-handler'; -import walletSetupRoutes from './routes/wallet-setup.routes'; import walletRoutes from './routes/wallet.routes'; -import vaultRoutes from './routes/vault.routes'; import relayProxyRoutes from './routes/relay-proxy.routes'; import tronProxyRoutes from './routes/tron-proxy.routes'; import solSwapProxyRoutes from './routes/sol-swap-proxy.routes'; @@ -20,20 +22,26 @@ app.use(helmet()); app.use(cors({ origin: env.frontendUrl, credentials: true })); app.use(express.json()); app.use(cookieParser()); +app.use(traceMiddleware); +// ── PUBLIC endpoints (no auth) ──────────────────────────────────────────────── app.get('/api/health', (_req, res) => { res.json({ success: true, data: { status: 'ok' } }); }); -app.use('/api/wallet', walletSetupRoutes); -app.use('/api/wallets', walletRoutes); -app.use('/api/vault', vaultRoutes); -app.use('/api/relay', relayProxyRoutes); -app.use('/api/tron', tronProxyRoutes); -app.use('/api/sol/swap', solSwapProxyRoutes); -app.use('/api/tron/swap', tronSwapProxyRoutes); -app.use('/api/btc', btcProxyRoutes); -app.use('/api/bsc/swap', bscSwapProxyRoutes); +app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); +app.get('/api/docs/swagger.json', (_req, res) => { + res.json(swaggerSpec); +}); + +// ── PROTECTED endpoints (JWT required) ──────────────────────────────────────── +app.use('/api/wallets', authMiddleware, walletRoutes); +app.use('/api/relay', authMiddleware, relayProxyRoutes); +app.use('/api/tron', authMiddleware, tronProxyRoutes); +app.use('/api/sol/swap', authMiddleware, solSwapProxyRoutes); +app.use('/api/tron/swap', authMiddleware, tronSwapProxyRoutes); +app.use('/api/btc', authMiddleware, btcProxyRoutes); +app.use('/api/bsc/swap', authMiddleware, bscSwapProxyRoutes); app.use(errorHandler); diff --git a/apps/api/src/config/env.ts b/apps/api/src/config/env.ts index 947d006..54fba82 100644 --- a/apps/api/src/config/env.ts +++ b/apps/api/src/config/env.ts @@ -1,55 +1,158 @@ import dotenv from 'dotenv'; import path from 'path'; -import { fetchVaultSecrets } from './vault'; +import { vaultAppRoleLogin, fetchVaultKV2 } from './vault'; +import { logger } from '../lib/logger'; dotenv.config({ path: path.resolve(__dirname, '../../../../.env') }); +const p = process.env; + export let env = { db: { - host: process.env.DB_HOST || 'localhost', - port: parseInt(process.env.DB_PORT || '5432'), - user: process.env.DB_USER || 'postgres', - password: process.env.DB_PASSWORD || 'postgres', - name: process.env.DB_NAME || 'cryptowallet', + host: p.DB_HOST || 'localhost', + port: parseInt(p.DB_PORT || '5432'), + user: p.DB_USER || 'postgres', + password: p.DB_PASSWORD || 'postgres', + name: p.DB_NAME || 'cryptowallet_v2', + poolSize: parseInt(p.DATABASE_POOL_SIZE || '10'), + maxOverflow: parseInt(p.DATABASE_MAX_OVERFLOW || '20'), + poolTimeout: parseInt(p.DATABASE_POOL_TIMEOUT || '30'), + poolRecycle: parseInt(p.DATABASE_POOL_RECYCLE || '3600'), + echo: p.DATABASE_ECHO === 'true', }, - port: parseInt(process.env.API_PORT || '3001'), - frontendUrl: process.env.FRONTEND_URL || 'http://localhost:3000', - relayApiKey: process.env.RELAY_API_KEY || null, - tronApiKey: process.env.TRON_API_KEY || null, - jupiterApiKey: process.env.JUPITER_API_KEY || null, - jupiterReferralAccount: process.env.JUPITER_REFERRAL_ACCOUNT || null, - jupiterFeeBps: parseInt(process.env.JUPITER_FEE_BPS || '70'), // 0.7% - - // BITOK auth service - bitokJwksUrl: process.env.BITOK_JWKS_URL || 'http://localhost:8000/.well-known/jwks.json', - bitokIssuer: process.env.BITOK_ISSUER || 'auth-service', - bitokAudience: process.env.BITOK_AUDIENCE || 'wallet-service', - - // RabbitMQ - rabbitmqUrl: process.env.RABBITMQ_URL || 'amqp://guest:guest@localhost:5672/', - rabbitmqExchange: process.env.RABBITMQ_EXCHANGE || 'bitok.events', - rabbitmqWalletQueue: process.env.RABBITMQ_WALLET_QUEUE || 'wallet.user_events', + jwt: { + algorithm: p.JWT_ALGORITHM || 'RS256', + issuer: p.JWT_ISSUER || 'auth-service', + audience: p.JWT_AUDIENCE || 'bitforce', + accessTtl: parseInt(p.JWT_ACCESS_TTL_SECONDS || '900'), + refreshTtl: parseInt(p.JWT_REFRESH_TTL_SECONDS || '2592000'), + }, + vault: { + addr: p.VAULT_ADDR || '', + roleId: p.VAULT_ROLE_ID || '', + secretId: p.VAULT_SECRET_ID || '', + mount: p.VAULT_MOUNT_POINT || 'dev-secrets', + secretPath: p.VAULT_SECRET_PATH || 'database', + jwtKidPath: p.VAULT_JWT_KID_PATH || 'jwt/kid', + jwtKidsPrefix: p.VAULT_JWT_KIDS_PREFIX || 'jwt/kids', + }, + csrf: { + cookieSecure: p.CSRF_COOKIE_SECURE === 'true', + cookieHttpOnly: p.CSRF_COOKIE_HTTPONLY !== 'false', + cookieSameSite: p.CSRF_COOKIE_SAMESITE || 'Lax', + cookiePath: p.CSRF_COOKIE_PATH || '/', + cookieDomain: p.CSRF_COOKIE_DOMAIN || '', + }, + docs: { + username: p.DOCS_USERNAME || 'admin', + password: p.DOCS_PASSWORD || 'admin', + }, + redis: { + host: p.REDIS_HOST || 'keydb', + port: parseInt(p.REDIS_PORT || '6379'), + password: p.REDIS_PASSWORD || 'keydb', + db: parseInt(p.REDIS_DB || '0'), + }, + rabbit: { + emailCodeQueue: p.RABBIT_EMAIL_CODE_QUEUE || 'email.verification_code', + publishPersist: p.RABBIT_PUBLISH_PERSIST !== 'false', + connectTimeout: parseInt(p.RABBIT_CONNECT_TIMEOUT || '5'), + }, + log: { + level: p.LOG_LEVEL || 'INFO', + format: p.LOG_FORMAT || 'JSON', + }, + cors: { + origins: (p.CORS_ORIGINS || 'http://localhost:3000').split(','), + allowCredentials: p.CORS_ALLOW_CREDENTIALS !== 'false', + }, + rateLimit: { + requests: parseInt(p.RATE_LIMIT_REQUESTS || '60'), + window: parseInt(p.RATE_LIMIT_WINDOW || '60'), + }, + port: parseInt(p.API_PORT || '3001'), + frontendUrl: p.FRONTEND_URL || 'http://localhost:3000', + relayApiKey: p.RELAY_API_KEY || null, + tronApiKey: p.TRON_API_KEY || null, + jupiterApiKey: p.JUPITER_API_KEY || null, + jupiterReferralAccount: p.JUPITER_REFERRAL_ACCOUNT || null, + jupiterFeeBps: parseInt(p.JUPITER_FEE_BPS || '70'), }; -export async function initEnv(): Promise { - const secrets = await fetchVaultSecrets(); +let vaultToken: string | null = null; - if (secrets) { - console.log('[ENV] Loaded secrets from Vault'); - env = { - ...env, - db: { - host: secrets.db_host, - port: parseInt(secrets.db_port), - user: secrets.db_user, - password: secrets.db_password, - name: secrets.db_name, - }, - relayApiKey: secrets.relay_api_key || null, - tronApiKey: secrets.tron_api_key || env.tronApiKey, - jupiterApiKey: secrets.jupiter_api_key || env.jupiterApiKey, - }; - } else { - console.log('[ENV] Vault not available, using env vars'); - } +export function getVaultToken(): string | null { + return vaultToken; +} + +export async function initEnv(): Promise { + const { addr, roleId, secretId, mount, secretPath } = env.vault; + + if (!addr || !roleId || !secretId) { + logger.info('Vault not configured, using .env'); + return; + } + + const token = await vaultAppRoleLogin(addr, roleId, secretId); + if (!token) { + logger.warn('Vault AppRole login failed, using .env fallback'); + return; + } + + vaultToken = token; + logger.info('Vault AppRole login successful'); + + const secrets = await fetchVaultKV2(addr, token, mount, secretPath); + if (!secrets) { + logger.warn('Failed to read DB secrets from Vault'); + return; + } + + logger.info('Loaded DB secrets from Vault'); + + const s = (key: string) => secrets[key]; + const si = (key: string, fallback: number) => { + const v = secrets[key]; + return v ? parseInt(v) : fallback; + }; + + env = { + ...env, + db: { + host: s('DB_HOST') || env.db.host, + port: si('DB_PORT', env.db.port), + user: s('DB_USER') || env.db.user, + password: s('DB_PASSWORD') || env.db.password, + name: s('DB_NAME') || env.db.name, + poolSize: si('DATABASE_POOL_SIZE', env.db.poolSize), + maxOverflow: si('DATABASE_MAX_OVERFLOW', env.db.maxOverflow), + poolTimeout: si('DATABASE_POOL_TIMEOUT', env.db.poolTimeout), + poolRecycle: si('DATABASE_POOL_RECYCLE', env.db.poolRecycle), + echo: secrets['DATABASE_ECHO'] === 'true', + }, + jwt: { + ...env.jwt, + issuer: s('JWT_ISSUER') || env.jwt.issuer, + audience: s('JWT_AUDIENCE') || env.jwt.audience, + accessTtl: si('JWT_ACCESS_TTL_SECONDS', env.jwt.accessTtl), + refreshTtl: si('JWT_REFRESH_TTL_SECONDS', env.jwt.refreshTtl), + }, + redis: { + host: s('REDIS_HOST') || env.redis.host, + port: si('REDIS_PORT', env.redis.port), + password: s('REDIS_PASSWORD') || env.redis.password, + db: si('REDIS_DB', env.redis.db), + }, + cors: { + origins: s('CORS_ORIGINS') ? s('CORS_ORIGINS')!.split(',') : env.cors.origins, + allowCredentials: secrets['CORS_ALLOW_CREDENTIALS'] !== 'false', + }, + rateLimit: { + requests: si('RATE_LIMIT_REQUESTS', env.rateLimit.requests), + window: si('RATE_LIMIT_WINDOW', env.rateLimit.window), + }, + relayApiKey: s('RELAY_API_KEY') || env.relayApiKey, + tronApiKey: s('TRON_API_KEY') || env.tronApiKey, + jupiterApiKey: s('JUPITER_API_KEY') || env.jupiterApiKey, + }; } diff --git a/apps/api/src/config/swagger.ts b/apps/api/src/config/swagger.ts new file mode 100644 index 0000000..b78c3e4 --- /dev/null +++ b/apps/api/src/config/swagger.ts @@ -0,0 +1,5 @@ +import fs from 'fs'; +import path from 'path'; + +const swaggerPath = path.resolve(__dirname, '../../swagger.json'); +export const swaggerSpec = JSON.parse(fs.readFileSync(swaggerPath, 'utf-8')); diff --git a/apps/api/src/config/vault.ts b/apps/api/src/config/vault.ts index 742ae83..c3a36fe 100644 --- a/apps/api/src/config/vault.ts +++ b/apps/api/src/config/vault.ts @@ -1,29 +1,42 @@ -interface VaultSecrets { - db_host: string; - db_port: string; - db_user: string; - db_password: string; - db_name: string; - relay_api_key: string; - tron_api_key: string; - jupiter_api_key: string; -} - -export async function fetchVaultSecrets(): Promise { - const vaultAddr = process.env.VAULT_ADDR; - const vaultToken = process.env.VAULT_TOKEN; - - if (!vaultAddr || !vaultToken) return null; - +export async function vaultAppRoleLogin( + addr: string, + roleId: string, + secretId: string, +): Promise { try { - const res = await fetch(`${vaultAddr}/v1/kv/data/cryptowallet`, { - headers: { 'X-Vault-Token': vaultToken }, + const res = await fetch(`${addr}/v1/auth/approle/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ role_id: roleId, secret_id: secretId }), + signal: AbortSignal.timeout(5000), }); if (!res.ok) return null; - const body = (await res.json()) as { data: { data: VaultSecrets } }; - return body.data.data; + const body = (await res.json()) as { auth?: { client_token?: string } }; + return body?.auth?.client_token ?? null; + } catch { + return null; + } +} + +export async function fetchVaultKV2( + addr: string, + token: string, + mount: string, + path: string, +): Promise | null> { + try { + const url = `${addr}/v1/${mount}/data/${path}`; + const res = await fetch(url, { + headers: { 'X-Vault-Token': token }, + signal: AbortSignal.timeout(5000), + }); + + if (!res.ok) return null; + + const body = (await res.json()) as { data?: { data?: Record } }; + return body?.data?.data ?? null; } catch { return null; } diff --git a/apps/api/src/controllers/vault.controller.ts b/apps/api/src/controllers/vault.controller.ts deleted file mode 100644 index 0e4b816..0000000 --- a/apps/api/src/controllers/vault.controller.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { Request, Response } from 'express'; -import { UserModel } from '../models/user.model'; - -export const VaultController = { - async getVault(req: Request, res: Response) { - try { - const user = await UserModel.findByBitokUserId(req.user!.bitokUserId); - if (!user) { - res.status(404).json({ success: false, error: 'User not found' }); - return; - } - res.json({ - success: true, - data: { - encryptedVault: user.encrypted_vault, - vaultSalt: user.vault_salt, - }, - }); - } catch (err: any) { - res.status(500).json({ success: false, error: err.message }); - } - }, -}; diff --git a/apps/api/src/controllers/wallet-setup.controller.ts b/apps/api/src/controllers/wallet-setup.controller.ts deleted file mode 100644 index 9c90a42..0000000 --- a/apps/api/src/controllers/wallet-setup.controller.ts +++ /dev/null @@ -1,118 +0,0 @@ -import { Request, Response } from 'express'; -import { UserModel } from '../models/user.model'; -import { WalletModel } from '../models/wallet.model'; -import { db } from '../config/database'; -import { generateUlid } from '../utils/ulid'; - -export const WalletSetupController = { - async setup(req: Request, res: Response) { - try { - const { bitokUserId, email } = req.user!; - const { encryptedVault, vaultSalt, wallets } = req.body; - - // Check if user already exists - const existing = await UserModel.findByBitokUserId(bitokUserId); - if (existing) { - res.status(409).json({ success: false, error: 'Wallet already set up for this user' }); - return; - } - - const result = await db.transaction(async (trx) => { - const [user] = await trx('users') - .insert({ - id: generateUlid(), - bitok_user_id: bitokUserId, - email: email || null, - encrypted_vault: encryptedVault, - vault_salt: vaultSalt, - }) - .returning('*'); - - const walletRows = await trx('wallets') - .insert( - wallets.map((w: { chain: string; address: string; derivationPath: string }) => ({ - id: generateUlid(), - user_id: user.id, - chain: w.chain, - address: w.address, - derivation_path: w.derivationPath, - })) - ) - .returning('*'); - - return { user, wallets: walletRows }; - }); - - res.status(201).json({ - success: true, - data: { - user: { - id: result.user.id, - bitokUserId: result.user.bitok_user_id, - email: result.user.email, - }, - wallets: result.wallets.map((w: any) => ({ - chain: w.chain, - address: w.address, - derivationPath: w.derivation_path, - })), - }, - }); - } catch (err: any) { - console.error('[WalletSetup] Error:', err.message); - res.status(500).json({ success: false, error: 'Failed to set up wallet' }); - } - }, - - async confirmMnemonic(req: Request, res: Response) { - try { - const { bitokUserId } = req.user!; - const user = await UserModel.findByBitokUserId(bitokUserId); - if (!user) { - res.status(404).json({ success: false, error: 'Wallet not found' }); - return; - } - await UserModel.setMnemonicShown(user.id); - res.json({ success: true, data: { mnemonicShown: true } }); - } catch (err: any) { - console.error('[ConfirmMnemonic] Error:', err.message); - res.status(500).json({ success: false, error: 'Failed to confirm mnemonic' }); - } - }, - - async unlock(req: Request, res: Response) { - try { - const { bitokUserId } = req.user!; - - const user = await UserModel.findByBitokUserId(bitokUserId); - if (!user) { - res.status(404).json({ success: false, error: 'Wallet not found' }); - return; - } - - if (user.deleted) { - res.status(403).json({ success: false, error: 'Account has been deleted' }); - return; - } - - const wallets = await WalletModel.findByUserId(user.id); - - res.json({ - success: true, - data: { - encryptedVault: user.encrypted_vault, - vaultSalt: user.vault_salt, - wallets: wallets.map((w) => ({ - chain: w.chain, - address: w.address, - derivationPath: w.derivation_path, - })), - mnemonicShown: user.mnemonic_shown, - }, - }); - } catch (err: any) { - console.error('[WalletUnlock] Error:', err.message); - res.status(500).json({ success: false, error: 'Failed to unlock wallet' }); - } - }, -}; diff --git a/apps/api/src/controllers/wallet.controller.ts b/apps/api/src/controllers/wallet.controller.ts index f1619e8..97ef59e 100644 --- a/apps/api/src/controllers/wallet.controller.ts +++ b/apps/api/src/controllers/wallet.controller.ts @@ -1,17 +1,10 @@ import { Request, Response } from 'express'; -import { UserModel } from '../models/user.model'; import { WalletModel } from '../models/wallet.model'; export const WalletController = { async getWallets(req: Request, res: Response) { try { - const user = await UserModel.findByBitokUserId(req.user!.bitokUserId); - if (!user) { - res.status(404).json({ success: false, error: 'User not found' }); - return; - } - - const wallets = await WalletModel.findByUserId(user.id); + const wallets = await WalletModel.findByUserId(req.auth!.userId); res.json({ success: true, data: wallets.map((w) => ({ diff --git a/apps/api/src/db/knexfile.ts b/apps/api/src/db/knexfile.ts index 84e520e..03d53be 100644 --- a/apps/api/src/db/knexfile.ts +++ b/apps/api/src/db/knexfile.ts @@ -12,7 +12,7 @@ const config: Knex.Config = { port: parseInt(process.env.DB_PORT || '5432'), user: process.env.DB_USER || 'postgres', password: process.env.DB_PASSWORD || 'postgres', - database: process.env.DB_NAME || 'cryptowallet', + database: process.env.DB_NAME || 'cryptowallet_v2', }, migrations: { directory: path.resolve(__dirname, 'migrations'), diff --git a/apps/api/src/db/migrations/001_create_users.ts b/apps/api/src/db/migrations/001_create_users.ts index 5de0cc0..6d2c69b 100644 --- a/apps/api/src/db/migrations/001_create_users.ts +++ b/apps/api/src/db/migrations/001_create_users.ts @@ -3,12 +3,21 @@ import type { Knex } from 'knex'; export async function up(knex: Knex): Promise { await knex.schema.createTable('users', (t) => { t.string('id', 26).primary(); - t.string('username', 64).notNullable().unique(); - t.text('password_hash').notNullable(); - t.text('pin_hash').notNullable(); - t.text('encrypted_vault').notNullable(); - t.string('vault_salt', 128).notNullable(); - t.boolean('mnemonic_shown').notNullable().defaultTo(false); + t.string('email', 255).notNullable().unique(); + t.string('password_hash', 255).notNullable(); + t.string('last_name', 128).nullable(); + t.string('first_name', 128).nullable(); + t.string('middle_name', 128).nullable(); + t.date('birth_date').nullable(); + t.string('crypto_wallet', 255).nullable(); + t.string('phone', 16).nullable(); + t.string('bik', 9).nullable(); + t.string('account_number', 20).nullable(); + t.string('card_number', 19).nullable(); + t.string('inn', 12).nullable(); + t.boolean('kyc_verified').notNullable().defaultTo(false); + t.timestamp('kyc_verified_at', { useTz: true }).nullable(); + t.boolean('is_deleted').notNullable().defaultTo(false); t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now()); t.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(knex.fn.now()); }); diff --git a/apps/api/src/db/migrations/003_create_sessions.ts b/apps/api/src/db/migrations/003_create_sessions.ts index cccde1d..86929e9 100644 --- a/apps/api/src/db/migrations/003_create_sessions.ts +++ b/apps/api/src/db/migrations/003_create_sessions.ts @@ -3,16 +3,22 @@ import type { Knex } from 'knex'; export async function up(knex: Knex): Promise { await knex.schema.createTable('sessions', (t) => { t.string('id', 26).primary(); + t.string('sid', 26).notNullable().unique(); t.string('user_id', 26).notNullable().references('id').inTable('users').onDelete('CASCADE'); - t.text('refresh_token_hash').notNullable(); - t.string('user_agent').nullable(); - t.specificType('ip_address', 'inet').nullable(); - t.timestamp('expires_at', { useTz: true }).notNullable(); + t.string('device_id', 26).nullable(); + t.string('user_agent', 500).nullable(); + t.string('first_ip', 64).nullable(); + t.string('last_ip', 64).nullable(); + t.timestamp('last_seen_at', { useTz: true }).nullable(); + t.timestamp('revoked_at', { useTz: true }).nullable(); + t.string('refresh_jti_hash', 255).nullable(); + t.timestamp('refresh_expires_at', { useTz: true }).nullable(); t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now()); + t.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(knex.fn.now()); }); await knex.schema.raw('CREATE INDEX idx_sessions_user_id ON sessions(user_id)'); - await knex.schema.raw('CREATE INDEX idx_sessions_expires ON sessions(expires_at)'); + await knex.schema.raw('CREATE INDEX idx_sessions_sid ON sessions(sid)'); } export async function down(knex: Knex): Promise { diff --git a/apps/api/src/db/migrations/004_create_login_attempts.ts b/apps/api/src/db/migrations/004_create_login_attempts.ts deleted file mode 100644 index f1f5162..0000000 --- a/apps/api/src/db/migrations/004_create_login_attempts.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Knex } from 'knex'; - -export async function up(knex: Knex): Promise { - await knex.schema.createTable('login_attempts', (t) => { - t.string('id', 26).primary(); - t.string('username', 64).notNullable(); - t.specificType('ip_address', 'inet').notNullable(); - t.boolean('success').notNullable().defaultTo(false); - t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now()); - }); - - await knex.schema.raw('CREATE INDEX idx_login_attempts_username_created ON login_attempts(username, created_at)'); - await knex.schema.raw('CREATE INDEX idx_login_attempts_ip_created ON login_attempts(ip_address, created_at)'); -} - -export async function down(knex: Knex): Promise { - await knex.schema.dropTableIfExists('login_attempts'); -} diff --git a/apps/api/src/db/migrations/005_simplify_users_for_bitok.ts b/apps/api/src/db/migrations/005_simplify_users_for_bitok.ts deleted file mode 100644 index 8e64b83..0000000 --- a/apps/api/src/db/migrations/005_simplify_users_for_bitok.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { Knex } from 'knex'; - -export async function up(knex: Knex): Promise { - await knex.schema.alterTable('users', (t) => { - t.dropColumn('username'); - t.dropColumn('password_hash'); - t.dropColumn('pin_hash'); - - t.string('bitok_user_id', 26).notNullable().unique(); - t.string('email', 255).nullable(); - t.boolean('kyc_verified').notNullable().defaultTo(false); - t.string('kyc_level', 20).nullable(); - t.boolean('deleted').notNullable().defaultTo(false); - - t.index(['bitok_user_id'], 'idx_users_bitok_user_id'); - }); -} - -export async function down(knex: Knex): Promise { - await knex.schema.alterTable('users', (t) => { - t.dropIndex(['bitok_user_id'], 'idx_users_bitok_user_id'); - - t.dropColumn('bitok_user_id'); - t.dropColumn('email'); - t.dropColumn('kyc_verified'); - t.dropColumn('kyc_level'); - t.dropColumn('deleted'); - - t.string('username', 64).notNullable().unique(); - t.text('password_hash').notNullable(); - t.text('pin_hash').notNullable(); - }); -} diff --git a/apps/api/src/db/migrations/006_drop_sessions_create_processed_events.ts b/apps/api/src/db/migrations/006_drop_sessions_create_processed_events.ts deleted file mode 100644 index ee702b4..0000000 --- a/apps/api/src/db/migrations/006_drop_sessions_create_processed_events.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { Knex } from 'knex'; - -export async function up(knex: Knex): Promise { - await knex.schema.dropTableIfExists('sessions'); - await knex.schema.dropTableIfExists('login_attempts'); - - await knex.schema.createTable('processed_events', (t) => { - t.string('event_id', 26).primary(); - t.string('event_type', 64).notNullable(); - t.string('payload_hash', 64).notNullable(); - t.timestamp('processed_at', { useTz: true }).notNullable().defaultTo(knex.fn.now()); - }); -} - -export async function down(knex: Knex): Promise { - await knex.schema.dropTableIfExists('processed_events'); - - await knex.schema.createTable('sessions', (t) => { - t.string('id', 26).primary(); - t.string('user_id', 26).notNullable().references('id').inTable('users').onDelete('CASCADE'); - t.text('refresh_token_hash').notNullable(); - t.string('user_agent').nullable(); - t.specificType('ip_address', 'inet').nullable(); - t.timestamp('expires_at', { useTz: true }).notNullable(); - t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now()); - }); - - await knex.schema.createTable('login_attempts', (t) => { - t.string('id', 26).primary(); - t.string('username', 64).notNullable(); - t.specificType('ip_address', 'inet').notNullable(); - t.boolean('success').notNullable().defaultTo(false); - t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(knex.fn.now()); - }); -} diff --git a/apps/api/src/db/reset-db.ts b/apps/api/src/db/reset-db.ts deleted file mode 100644 index 8ff8ca4..0000000 --- a/apps/api/src/db/reset-db.ts +++ /dev/null @@ -1,49 +0,0 @@ -import dotenv from 'dotenv'; -import path from 'path'; -import knex from 'knex'; - -// Load .env from repo root (works when running from apps/api) -dotenv.config({ path: path.resolve(__dirname, '../../../../.env') }); -dotenv.config({ path: path.resolve(process.cwd(), '.env') }); - -const dbName = process.env.DB_NAME || 'cryptowallet_devphase3'; -if (!/^[a-zA-Z0-9_]+$/.test(dbName)) { - console.error('[DB Reset] Invalid DB_NAME'); - process.exit(1); -} - -const baseConnection = { - host: process.env.DB_HOST || 'localhost', - port: parseInt(process.env.DB_PORT || '5432'), - user: process.env.DB_USER || 'postgres', - password: process.env.DB_PASSWORD || 'postgres', -}; - -async function reset() { - const admin = knex({ - client: 'pg', - connection: { ...baseConnection, database: 'postgres' }, - }); - - try { - await admin.raw( - `SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ? AND pid <> pg_backend_pid()`, - [dbName] - ); - } catch { - // Ignore if no connections - } - - const safeName = dbName.replace(/"/g, '""'); - await admin.raw(`DROP DATABASE IF EXISTS "${safeName}"`); - await admin.raw(`CREATE DATABASE "${safeName}"`); - - await admin.destroy(); - console.log('[DB Reset] Database dropped and recreated:', dbName); -} - -reset().catch((err: unknown) => { - console.error('[DB Reset] Failed:', err instanceof Error ? err.message : String(err)); - if (err instanceof Error && err.stack) console.error(err.stack); - process.exit(1); -}); diff --git a/apps/api/src/events/connection.ts b/apps/api/src/events/connection.ts deleted file mode 100644 index fd488bc..0000000 --- a/apps/api/src/events/connection.ts +++ /dev/null @@ -1,59 +0,0 @@ -import amqplib, { type Channel, type ChannelModel } from 'amqplib'; -import { env } from '../config/env'; - -let connectionModel: ChannelModel | null = null; -let channel: Channel | null = null; - -const DLX_EXCHANGE = `${env.rabbitmqExchange}.dlx`; -const DLQ_NAME = `${env.rabbitmqWalletQueue}.dlq`; - -export async function createRabbitConnection(): Promise { - connectionModel = await amqplib.connect(env.rabbitmqUrl); - - connectionModel.on('error', (err) => { - console.error('[RabbitMQ] Connection error:', err.message); - }); - - connectionModel.on('close', () => { - console.warn('[RabbitMQ] Connection closed. Reconnecting in 5s...'); - setTimeout(() => createRabbitConnection().catch(console.error), 5000); - }); - - channel = await connectionModel.createChannel(); - await channel.prefetch(1); - - // Declare main exchange - await channel.assertExchange(env.rabbitmqExchange, 'topic', { durable: true }); - - // Declare DLX and DLQ - await channel.assertExchange(DLX_EXCHANGE, 'topic', { durable: true }); - await channel.assertQueue(DLQ_NAME, { durable: true }); - await channel.bindQueue(DLQ_NAME, DLX_EXCHANGE, '#'); - - // Declare main queue with DLX - await channel.assertQueue(env.rabbitmqWalletQueue, { - durable: true, - arguments: { - 'x-dead-letter-exchange': DLX_EXCHANGE, - }, - }); - - // Bind routing keys - await channel.bindQueue(env.rabbitmqWalletQueue, env.rabbitmqExchange, 'user.kyc_verified'); - await channel.bindQueue(env.rabbitmqWalletQueue, env.rabbitmqExchange, 'user.deleted'); - - console.log('[RabbitMQ] Connected and queues declared'); - - return channel; -} - -export async function closeRabbitConnection(): Promise { - try { - if (channel) await channel.close(); - if (connectionModel) await connectionModel.close(); - } catch { - // ignore close errors - } - channel = null; - connectionModel = null; -} diff --git a/apps/api/src/events/consumer.ts b/apps/api/src/events/consumer.ts deleted file mode 100644 index 903fcd0..0000000 --- a/apps/api/src/events/consumer.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { Channel, ConsumeMessage } from 'amqplib'; -import crypto from 'crypto'; -import { db } from '../config/database'; -import { env } from '../config/env'; -import { handleKycVerified } from './handlers/kyc-verified.handler'; -import { handleUserDeleted } from './handlers/deleted.handler'; - -const MAX_RETRIES = 3; - -interface BitokEvent { - event_id: string; - event_type: string; - payload: Record; - occurred_at: string; - schema_version: number; -} - -function isValidEvent(msg: unknown): msg is BitokEvent { - if (!msg || typeof msg !== 'object') return false; - const e = msg as Record; - return ( - typeof e.event_id === 'string' && - typeof e.event_type === 'string' && - typeof e.payload === 'object' && - e.payload !== null && - typeof e.occurred_at === 'string' - ); -} - -function getRetryCount(msg: ConsumeMessage): number { - const xDeath = msg.properties.headers?.['x-death'] as Array<{ count: number }> | undefined; - if (!xDeath || xDeath.length === 0) return 0; - return xDeath[0].count ?? 0; -} - -function hashPayload(payload: Record): string { - return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex'); -} - -async function isAlreadyProcessed(eventId: string): Promise { - const row = await db('processed_events').where({ event_id: eventId }).first(); - return !!row; -} - -async function markProcessed(eventId: string, eventType: string, payloadHash: string): Promise { - await db('processed_events').insert({ - event_id: eventId, - event_type: eventType, - payload_hash: payloadHash, - }); -} - -export async function startConsumer(channel: Channel): Promise { - console.log('[Consumer] Listening on queue:', env.rabbitmqWalletQueue); - - await channel.consume(env.rabbitmqWalletQueue, async (msg) => { - if (!msg) return; - - let parsed: unknown; - try { - parsed = JSON.parse(msg.content.toString()); - } catch { - console.error('[Consumer] Invalid JSON, nacking without requeue'); - channel.nack(msg, false, false); - return; - } - - if (!isValidEvent(parsed)) { - console.error('[Consumer] Schema validation failed, nacking without requeue'); - channel.nack(msg, false, false); - return; - } - - const event = parsed; - - // Idempotency check - try { - if (await isAlreadyProcessed(event.event_id)) { - console.log(`[Consumer] Event ${event.event_id} already processed, acking`); - channel.ack(msg); - return; - } - } catch (err) { - console.error('[Consumer] DB error checking idempotency, nacking with requeue'); - channel.nack(msg, false, true); - return; - } - - // Check retry count - const retries = getRetryCount(msg); - if (retries >= MAX_RETRIES) { - console.error(`[Consumer] Event ${event.event_id} exceeded max retries (${MAX_RETRIES}), sending to DLQ`); - channel.nack(msg, false, false); - return; - } - - try { - switch (event.event_type) { - case 'user.kyc_verified': - await handleKycVerified(event.payload); - break; - case 'user.deleted': - await handleUserDeleted(event.payload); - break; - default: - console.warn(`[Consumer] Unknown event type: ${event.event_type}, acking`); - channel.ack(msg); - return; - } - - const payloadHash = hashPayload(event.payload); - await markProcessed(event.event_id, event.event_type, payloadHash); - channel.ack(msg); - console.log(`[Consumer] Processed event: ${event.event_id} (${event.event_type})`); - } catch (err: any) { - console.error(`[Consumer] Handler error for ${event.event_id}:`, err.message); - // DB/handler error -- requeue for retry - channel.nack(msg, false, true); - } - }); -} diff --git a/apps/api/src/events/handlers/deleted.handler.ts b/apps/api/src/events/handlers/deleted.handler.ts deleted file mode 100644 index 6ba0e5c..0000000 --- a/apps/api/src/events/handlers/deleted.handler.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { UserModel } from '../../models/user.model'; - -interface UserDeletedPayload { - bitok_user_id: string; - reason: string; -} - -export async function handleUserDeleted(payload: Record): Promise { - const data = payload as unknown as UserDeletedPayload; - - if (!data.bitok_user_id) { - throw new Error('Invalid user.deleted payload: missing bitok_user_id'); - } - - await UserModel.softDelete(data.bitok_user_id); - console.log(`[UserDeleted] Soft-deleted user ${data.bitok_user_id} reason=${data.reason}`); -} diff --git a/apps/api/src/events/handlers/kyc-verified.handler.ts b/apps/api/src/events/handlers/kyc-verified.handler.ts deleted file mode 100644 index 80eec9f..0000000 --- a/apps/api/src/events/handlers/kyc-verified.handler.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { UserModel } from '../../models/user.model'; - -interface KycVerifiedPayload { - bitok_user_id: string; - kyc_verified: boolean; - kyc_level: string; -} - -export async function handleKycVerified(payload: Record): Promise { - const data = payload as unknown as KycVerifiedPayload; - - if (!data.bitok_user_id || typeof data.kyc_verified !== 'boolean') { - throw new Error('Invalid kyc_verified payload'); - } - - await UserModel.updateKyc(data.bitok_user_id, data.kyc_verified, data.kyc_level || null); - console.log(`[KYC] Updated KYC for user ${data.bitok_user_id}: verified=${data.kyc_verified}, level=${data.kyc_level}`); -} diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index 1e21d0a..c2d4285 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -1,23 +1,43 @@ +import knex from 'knex'; +import knexConfig from './db/knexfile'; import app from './app'; -import { env, initEnv } from './config/env'; -import { createRabbitConnection } from './events/connection'; -import { startConsumer } from './events/consumer'; +import { env, initEnv, getVaultToken } from './config/env'; +import { loadJwtKeysFromVault } from './services/jwt.service'; +import { logger } from './lib/logger'; async function main() { + logger.info(`Wallet service instance started with id ${logger.instanceId}`); + await initEnv(); - // Start RabbitMQ consumer - try { - const channel = await createRabbitConnection(); - await startConsumer(channel); - console.log('[API] RabbitMQ consumer started'); - } catch (err: any) { - console.warn('[API] RabbitMQ not available, events will not be consumed:', err.message); + // Load JWT public keys from Vault if available + const vaultToken = getVaultToken(); + if (vaultToken && env.vault.addr) { + await loadJwtKeysFromVault( + env.vault.addr, + vaultToken, + env.vault.mount, + env.vault.jwtKidPath, + env.vault.jwtKidsPrefix, + ); + } else { + logger.warn('JWT keys not loaded: Vault not available'); } + const db = knex(knexConfig); + + logger.info('Running migrations...'); + await db.migrate.latest(); + logger.info('Migrations complete'); + + await db.destroy(); + app.listen(env.port, () => { - console.log(`[API] Server running on port ${env.port}`); + logger.info(`Server running on port ${env.port}`); }); } -main().catch(console.error); +main().catch((err) => { + logger.error(`Failed to start: ${err.message}`); + process.exit(1); +}); diff --git a/apps/api/src/lib/logger.ts b/apps/api/src/lib/logger.ts new file mode 100644 index 0000000..1b011ea --- /dev/null +++ b/apps/api/src/lib/logger.ts @@ -0,0 +1,38 @@ +import { generateUlid } from '../utils/ulid'; +import { getTraceId } from './trace-store'; + +const instanceId = generateUlid(); + +function getCallerInfo(): { file: string; line: number } { + const stack = new Error().stack; + if (!stack) return { file: 'unknown', line: 0 }; + + const lines = stack.split('\n'); + // Skip: Error, logger method, actual caller + const callerLine = lines[3] || ''; + const match = callerLine.match(/\((.+):(\d+):\d+\)/) || callerLine.match(/at (.+):(\d+):\d+/); + if (match) return { file: match[1], line: parseInt(match[2]) }; + return { file: 'unknown', line: 0 }; +} + +function log(level: string, message: string): void { + const caller = getCallerInfo(); + const entry = { + timestamp: new Date().toISOString(), + level, + instance_id: instanceId, + file: caller.file, + line: caller.line, + trace_id: getTraceId(), + message, + }; + process.stdout.write(JSON.stringify(entry) + '\n'); +} + +export const logger = { + instanceId, + info: (msg: string) => log('INFO', msg), + warn: (msg: string) => log('WARN', msg), + error: (msg: string) => log('ERROR', msg), + debug: (msg: string) => log('DEBUG', msg), +}; diff --git a/apps/api/src/lib/trace-store.ts b/apps/api/src/lib/trace-store.ts new file mode 100644 index 0000000..6beeeef --- /dev/null +++ b/apps/api/src/lib/trace-store.ts @@ -0,0 +1,7 @@ +import { AsyncLocalStorage } from 'node:async_hooks'; + +export const traceStore = new AsyncLocalStorage(); + +export function getTraceId(): string { + return traceStore.getStore() || 'N/A'; +} diff --git a/apps/api/src/middleware/auth.ts b/apps/api/src/middleware/auth.ts new file mode 100644 index 0000000..d3d4241 --- /dev/null +++ b/apps/api/src/middleware/auth.ts @@ -0,0 +1,41 @@ +import { Request, Response, NextFunction } from 'express'; +import { verifyAccessToken, AuthContext } from '../services/jwt.service'; +import { logger } from '../lib/logger'; + +declare global { + namespace Express { + interface Request { + auth?: AuthContext; + } + } +} + +function extractToken(req: Request): string | null { + const cookie = req.cookies?.access_token; + if (cookie) return cookie; + + const auth = req.headers.authorization; + if (auth) { + const [scheme, token] = auth.split(' '); + if (scheme?.toLowerCase() === 'bearer' && token) return token; + } + + return null; +} + +export async function authMiddleware(req: Request, res: Response, next: NextFunction): Promise { + const token = extractToken(req); + + if (!token) { + res.status(401).json({ success: false, error: 'Not authenticated' }); + return; + } + + try { + req.auth = await verifyAccessToken(token); + next(); + } catch (err: any) { + logger.warn(`Auth failed: ${err.message}`); + res.status(err.status || 401).json({ success: false, error: err.message || 'Invalid token' }); + } +} diff --git a/apps/api/src/middleware/bitok-auth.ts b/apps/api/src/middleware/bitok-auth.ts deleted file mode 100644 index bf80210..0000000 --- a/apps/api/src/middleware/bitok-auth.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { Request, Response, NextFunction } from 'express'; -import { jwtVerify, decodeProtectedHeader } from 'jose'; -import { getSigningKey } from '../services/jwks.service'; -import { env } from '../config/env'; - -declare global { - namespace Express { - interface Request { - user?: { bitokUserId: string; email?: string }; - } - } -} - -export async function bitokAuth(req: Request, res: Response, next: NextFunction): Promise { - const header = req.headers.authorization; - if (!header?.startsWith('Bearer ')) { - res.status(401).json({ success: false, error: 'No token provided' }); - return; - } - - try { - const token = header.slice(7); - - // Decode header to get kid - const protectedHeader = decodeProtectedHeader(token); - if (protectedHeader.alg !== 'RS256') { - res.status(401).json({ success: false, error: 'Invalid token algorithm' }); - return; - } - - if (!protectedHeader.kid) { - res.status(401).json({ success: false, error: 'Token missing kid' }); - return; - } - - // Get the signing key for this kid - const key = await getSigningKey(protectedHeader.kid); - - // Verify the token - const { payload } = await jwtVerify(token, key, { - issuer: env.bitokIssuer, - audience: env.bitokAudience, - algorithms: ['RS256'], - }); - - if (!payload.sub) { - res.status(401).json({ success: false, error: 'Token missing subject' }); - return; - } - - req.user = { - bitokUserId: payload.sub, - email: payload.email as string | undefined, - }; - - next(); - } catch { - res.status(401).json({ success: false, error: 'Invalid or expired token' }); - } -} diff --git a/apps/api/src/middleware/error-handler.ts b/apps/api/src/middleware/error-handler.ts index 46f9b5f..245a246 100644 --- a/apps/api/src/middleware/error-handler.ts +++ b/apps/api/src/middleware/error-handler.ts @@ -1,6 +1,7 @@ import { Request, Response, NextFunction } from 'express'; +import { logger } from '../lib/logger'; export function errorHandler(err: Error, _req: Request, res: Response, _next: NextFunction): void { - console.error('[ERROR]', err.message); + logger.error(err.message); res.status(500).json({ success: false, error: 'Internal server error' }); } diff --git a/apps/api/src/middleware/rate-limit.ts b/apps/api/src/middleware/rate-limit.ts deleted file mode 100644 index 1a94929..0000000 --- a/apps/api/src/middleware/rate-limit.ts +++ /dev/null @@ -1,25 +0,0 @@ -import rateLimit from 'express-rate-limit'; - -export const loginLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, - max: 20, - message: { success: false, error: 'Too many login attempts, try again later' }, - standardHeaders: true, - legacyHeaders: false, -}); - -export const registerLimiter = rateLimit({ - windowMs: 60 * 60 * 1000, - max: 3, - message: { success: false, error: 'Too many registration attempts, try again later' }, - standardHeaders: true, - legacyHeaders: false, -}); - -export const seedPhraseLimiter = rateLimit({ - windowMs: 15 * 60 * 1000, - max: 3, - message: { success: false, error: 'Too many attempts. Try again in 15 minutes.' }, - standardHeaders: true, - legacyHeaders: false, -}); diff --git a/apps/api/src/middleware/trace.ts b/apps/api/src/middleware/trace.ts new file mode 100644 index 0000000..7b0075a --- /dev/null +++ b/apps/api/src/middleware/trace.ts @@ -0,0 +1,15 @@ +import { Request, Response, NextFunction } from 'express'; +import { generateUlid } from '../utils/ulid'; +import { traceStore } from '../lib/trace-store'; + +export function traceMiddleware(req: Request, res: Response, next: NextFunction): void { + const traceId = req.headers['x-trace-id'] as string + || req.headers['x-request-id'] as string + || generateUlid(); + + res.setHeader('X-Trace-ID', traceId); + + traceStore.run(traceId, () => { + next(); + }); +} diff --git a/apps/api/src/models/session.model.ts b/apps/api/src/models/session.model.ts new file mode 100644 index 0000000..033940a --- /dev/null +++ b/apps/api/src/models/session.model.ts @@ -0,0 +1,66 @@ +import { db } from '../config/database'; +import { generateUlid } from '../utils/ulid'; + +export interface SessionRow { + id: string; + sid: string; + user_id: string; + device_id: string | null; + user_agent: string | null; + first_ip: string | null; + last_ip: string | null; + last_seen_at: Date | null; + revoked_at: Date | null; + refresh_jti_hash: string | null; + refresh_expires_at: Date | null; + created_at: Date; + updated_at: Date; +} + +export const SessionModel = { + async findBySid(sid: string): Promise { + return db('sessions').where({ sid }).whereNull('revoked_at').first(); + }, + + async findByUserId(userId: string): Promise { + return db('sessions').where({ user_id: userId }).whereNull('revoked_at'); + }, + + async create(data: { + sid: string; + user_id: string; + device_id?: string; + user_agent?: string; + first_ip?: string; + refresh_jti_hash?: string; + refresh_expires_at?: Date; + }): Promise { + const [session] = await db('sessions') + .insert({ + id: generateUlid(), + ...data, + last_ip: data.first_ip || null, + }) + .returning('*'); + return session; + }, + + async revoke(sid: string): Promise { + await db('sessions') + .where({ sid }) + .update({ revoked_at: db.fn.now(), updated_at: db.fn.now() }); + }, + + async revokeAllForUser(userId: string): Promise { + await db('sessions') + .where({ user_id: userId }) + .whereNull('revoked_at') + .update({ revoked_at: db.fn.now(), updated_at: db.fn.now() }); + }, + + async updateLastSeen(sid: string, ip: string): Promise { + await db('sessions') + .where({ sid }) + .update({ last_seen_at: db.fn.now(), last_ip: ip, updated_at: db.fn.now() }); + }, +}; diff --git a/apps/api/src/models/user.model.ts b/apps/api/src/models/user.model.ts index 37b47d7..db0863f 100644 --- a/apps/api/src/models/user.model.ts +++ b/apps/api/src/models/user.model.ts @@ -3,71 +3,47 @@ import { generateUlid } from '../utils/ulid'; export interface UserRow { id: string; - bitok_user_id: string; - email: string | null; - encrypted_vault: string; - vault_salt: string; - mnemonic_shown: boolean; + email: string; + password_hash: string; + last_name: string | null; + first_name: string | null; + middle_name: string | null; + birth_date: string | null; + crypto_wallet: string | null; + phone: string | null; + bik: string | null; + account_number: string | null; + card_number: string | null; + inn: string | null; kyc_verified: boolean; - kyc_level: string | null; - deleted: boolean; + kyc_verified_at: Date | null; + is_deleted: boolean; created_at: Date; updated_at: Date; } export const UserModel = { + async findByEmail(email: string): Promise { + return db('users').where({ email, is_deleted: false }).first(); + }, + async findById(id: string): Promise { - return db('users').where({ id }).first(); + return db('users').where({ id, is_deleted: false }).first(); }, - async findByBitokUserId(bitokUserId: string): Promise { - return db('users').where({ bitok_user_id: bitokUserId }).first(); - }, - - async createFromBitok(data: { - bitokUserId: string; - email?: string | null; - encryptedVault: string; - vaultSalt: string; + async create(data: { + email: string; + password_hash: string; }): Promise { - const [user] = await db('users') - .insert({ - id: generateUlid(), - bitok_user_id: data.bitokUserId, - email: data.email || null, - encrypted_vault: data.encryptedVault, - vault_salt: data.vaultSalt, - }) - .returning('*'); + const [user] = await db('users').insert({ id: generateUlid(), ...data }).returning('*'); return user; }, - async setMnemonicShown(id: string): Promise { - await db('users').where({ id }).update({ mnemonic_shown: true, updated_at: db.fn.now() }); - }, - - async updateVault(id: string, encrypted_vault: string, vault_salt: string): Promise { - await db('users') + async update(id: string, data: Partial>): Promise { + const [user] = await db('users') .where({ id }) - .update({ encrypted_vault, vault_salt, updated_at: db.fn.now() }); - }, - - async updateKyc(bitokUserId: string, kycVerified: boolean, kycLevel: string | null): Promise { - await db('users') - .where({ bitok_user_id: bitokUserId }) - .update({ - kyc_verified: kycVerified, - kyc_level: kycLevel, - updated_at: db.fn.now(), - }); - }, - - async softDelete(bitokUserId: string): Promise { - await db('users') - .where({ bitok_user_id: bitokUserId }) - .update({ - deleted: true, - updated_at: db.fn.now(), - }); + .update({ ...data, updated_at: db.fn.now() }) + .returning('*'); + return user; }, }; diff --git a/apps/api/src/routes/vault.routes.ts b/apps/api/src/routes/vault.routes.ts deleted file mode 100644 index 7a65c81..0000000 --- a/apps/api/src/routes/vault.routes.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Router } from 'express'; -import { VaultController } from '../controllers/vault.controller'; -import { bitokAuth } from '../middleware/bitok-auth'; - -const router = Router(); - -router.get('/', bitokAuth, VaultController.getVault); - -export default router; diff --git a/apps/api/src/routes/wallet-setup.routes.ts b/apps/api/src/routes/wallet-setup.routes.ts deleted file mode 100644 index a9ecd83..0000000 --- a/apps/api/src/routes/wallet-setup.routes.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Router } from 'express'; -import { z } from 'zod'; -import { WalletSetupController } from '../controllers/wallet-setup.controller'; -import { validate } from '../middleware/validate'; -import { bitokAuth } from '../middleware/bitok-auth'; - -const setupSchema = z.object({ - encryptedVault: z.string().min(1), - vaultSalt: z.string().min(1), - wallets: z.array( - z.object({ - chain: z.enum(['ETH', 'BTC', 'SOL', 'TRX', 'BSC']), - address: z.string().min(1), - derivationPath: z.string().min(1), - }) - ).min(4).max(5), -}); - -const router = Router(); - -router.post('/setup', bitokAuth, validate(setupSchema), WalletSetupController.setup); -router.get('/unlock', bitokAuth, WalletSetupController.unlock); -router.post('/confirm-mnemonic', bitokAuth, WalletSetupController.confirmMnemonic); - -export default router; diff --git a/apps/api/src/routes/wallet.routes.ts b/apps/api/src/routes/wallet.routes.ts index 86a200d..2482258 100644 --- a/apps/api/src/routes/wallet.routes.ts +++ b/apps/api/src/routes/wallet.routes.ts @@ -1,9 +1,8 @@ import { Router } from 'express'; import { WalletController } from '../controllers/wallet.controller'; -import { bitokAuth } from '../middleware/bitok-auth'; const router = Router(); -router.get('/', bitokAuth, WalletController.getWallets); +router.get('/', WalletController.getWallets); export default router; diff --git a/apps/api/src/services/jwks.service.ts b/apps/api/src/services/jwks.service.ts deleted file mode 100644 index cecb258..0000000 --- a/apps/api/src/services/jwks.service.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { importJWK, type JWK, type CryptoKey } from 'jose'; -import { env } from '../config/env'; - -interface CachedKey { - key: CryptoKey | Uint8Array; - fetchedAt: number; -} - -const KEY_TTL_MS = 60 * 60 * 1000; // 1 hour -const keyCache = new Map(); - -async function fetchJwks(): Promise<{ keys: JWK[] }> { - const res = await fetch(env.bitokJwksUrl); - if (!res.ok) { - throw new Error(`Failed to fetch JWKS: ${res.status} ${res.statusText}`); - } - return res.json() as Promise<{ keys: JWK[] }>; -} - -async function refreshKeys(): Promise { - const jwks = await fetchJwks(); - - for (const jwk of jwks.keys) { - if (!jwk.kid) continue; - const key = await importJWK(jwk, 'RS256'); - keyCache.set(jwk.kid, { key, fetchedAt: Date.now() }); - } -} - -export async function getSigningKey(kid: string): Promise { - const cached = keyCache.get(kid); - - if (cached && Date.now() - cached.fetchedAt < KEY_TTL_MS) { - return cached.key; - } - - // Unknown kid or expired -- force refresh - await refreshKeys(); - - const refreshed = keyCache.get(kid); - if (!refreshed) { - throw new Error(`No key found for kid: ${kid}`); - } - - return refreshed.key; -} diff --git a/apps/api/src/services/jwt.service.ts b/apps/api/src/services/jwt.service.ts new file mode 100644 index 0000000..c440134 --- /dev/null +++ b/apps/api/src/services/jwt.service.ts @@ -0,0 +1,127 @@ +import * as jose from 'jose'; +import { env } from '../config/env'; +import { logger } from '../lib/logger'; + +export interface AccessTokenPayload { + sub: string; + type: string; + sid: string; + iat: number; + nbf: number; + exp: number; + iss?: string; + aud?: string; +} + +export interface AuthContext { + userId: string; + sid: string; + token: AccessTokenPayload; +} + +const keyMap = new Map>>(); + +export async function loadJwtKeysFromVault( + vaultAddr: string, + vaultToken: string, + mount: string, + kidPath: string, + kidsPrefix: string, +): Promise { + const { fetchVaultKV2 } = await import('../config/vault'); + + const kidData = await fetchVaultKV2(vaultAddr, vaultToken, mount, kidPath); + if (!kidData) { + logger.warn('Failed to read JWT kid config from Vault'); + return; + } + + const kids: string[] = []; + if (kidData.active) kids.push(kidData.active); + if (kidData.previous && kidData.previous !== kidData.active) kids.push(kidData.previous); + + if (kids.length === 0) { + logger.warn('No active/previous kids found in Vault'); + return; + } + + for (const kid of kids) { + const kidSecret = await fetchVaultKV2(vaultAddr, vaultToken, mount, `${kidsPrefix}/${kid}`); + if (!kidSecret?.public_key) { + logger.warn(`No public_key found for kid=${kid}`); + continue; + } + + try { + const key = await jose.importSPKI(kidSecret.public_key, env.jwt.algorithm); + keyMap.set(kid, key); + logger.info(`Loaded JWT public key for kid=${kid}`); + } catch (err: any) { + logger.error(`Failed to import public key for kid=${kid}: ${err.message}`); + } + } + + logger.info(`JWT key store loaded: ${keyMap.size} key(s)`); +} + +export async function verifyAccessToken(token: string): Promise { + let payload: jose.JWTPayload; + + try { + const header = jose.decodeProtectedHeader(token); + const kid = header.kid; + + if (!kid) { + throw Object.assign(new Error('Missing kid in token header'), { status: 401 }); + } + + const key = keyMap.get(kid); + if (!key) { + logger.warn(`Unknown kid=${kid}`); + throw Object.assign(new Error('Unknown token kid'), { status: 401 }); + } + + if (header.alg !== env.jwt.algorithm) { + throw Object.assign(new Error('Invalid token algorithm'), { status: 401 }); + } + + const verifyOptions: jose.JWTVerifyOptions = { + algorithms: [env.jwt.algorithm], + clockTolerance: 10, + }; + if (env.jwt.issuer) verifyOptions.issuer = env.jwt.issuer; + if (env.jwt.audience) verifyOptions.audience = env.jwt.audience; + + const result = await jose.jwtVerify(token, key, verifyOptions); + payload = result.payload; + } catch (err: any) { + if (err.status === 401) throw err; + if (err.code === 'ERR_JWT_EXPIRED') { + throw Object.assign(new Error('Token expired'), { status: 401 }); + } + throw Object.assign(new Error('Invalid token'), { status: 401 }); + } + + if (payload.type !== 'access') { + throw Object.assign(new Error('Invalid token type'), { status: 401 }); + } + + if (!payload.sub || !payload.sid) { + throw Object.assign(new Error('Missing token claims'), { status: 401 }); + } + + return { + userId: payload.sub, + sid: payload.sid as string, + token: { + sub: payload.sub, + type: payload.type as string, + sid: payload.sid as string, + iat: payload.iat!, + nbf: payload.nbf!, + exp: payload.exp!, + iss: payload.iss, + aud: typeof payload.aud === 'string' ? payload.aud : undefined, + }, + }; +} diff --git a/apps/api/swagger.json b/apps/api/swagger.json new file mode 100644 index 0000000..601415e --- /dev/null +++ b/apps/api/swagger.json @@ -0,0 +1,101 @@ +{ + "openapi": "3.0.0", + "info": { + "title": "CryptoWallet API", + "version": "2.0.0", + "description": "Multi-chain cryptocurrency wallet API with blockchain proxy services" + }, + "servers": [ + { "url": "/api", "description": "API" } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "Error": { + "type": "object", + "properties": { + "success": { "type": "boolean", "example": false }, + "error": { "type": "string" } + } + }, + "Wallet": { + "type": "object", + "properties": { + "chain": { "type": "string", "enum": ["ETH", "BTC", "SOL", "TRX", "BSC"] }, + "address": { "type": "string" }, + "derivationPath": { "type": "string" } + } + }, + "HealthResponse": { + "type": "object", + "properties": { + "success": { "type": "boolean", "example": true }, + "data": { + "type": "object", + "properties": { + "status": { "type": "string", "example": "ok" } + } + } + } + } + } + }, + "paths": { + "/health": { + "get": { + "summary": "Health check", + "tags": ["System"], + "responses": { + "200": { + "description": "Service is healthy", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/HealthResponse" } + } + } + } + } + } + }, + "/wallets": { + "get": { + "summary": "Get user wallets", + "tags": ["Wallets"], + "security": [{ "bearerAuth": [] }], + "responses": { + "200": { + "description": "List of wallets", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "success": { "type": "boolean", "example": true }, + "data": { + "type": "array", + "items": { "$ref": "#/components/schemas/Wallet" } + } + } + } + } + } + }, + "401": { + "description": "Not authenticated", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/Error" } + } + } + } + } + } + } + } +} diff --git a/apps/web/.gitignore b/apps/web/.gitignore deleted file mode 100644 index 5ef6a52..0000000 --- a/apps/web/.gitignore +++ /dev/null @@ -1,41 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* -.pnpm-debug.log* - -# env files (can opt-in for committing if needed) -.env* - -# vercel -.vercel - -# typescript -*.tsbuildinfo -next-env.d.ts diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile deleted file mode 100644 index 4770935..0000000 --- a/apps/web/Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -# Build stage -FROM node:20-alpine AS builder - -RUN corepack enable && corepack prepare pnpm@10.28.2 --activate - -WORKDIR /app - -# Copy everything (filtered by .dockerignore) -COPY . . - -# Install deps with hoisting -RUN echo "node-linker=hoisted" > .npmrc -RUN pnpm install --frozen-lockfile - -# Build web -ENV NEXT_PUBLIC_API_URL=http://localhost:3001 -RUN cd apps/web && ../../node_modules/.bin/next build - -# Runtime stage -FROM node:20-alpine - -WORKDIR /app - -# Copy standalone output -COPY --from=builder /app/apps/web/.next/standalone ./ -COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static -COPY --from=builder /app/apps/web/public ./apps/web/public - -EXPOSE 3000 - -ENV PORT=3000 -ENV HOSTNAME=0.0.0.0 - -CMD ["node", "apps/web/server.js"] diff --git a/apps/web/README.md b/apps/web/README.md deleted file mode 100644 index e215bc4..0000000 --- a/apps/web/README.md +++ /dev/null @@ -1,36 +0,0 @@ -This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -# or -bun dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. - -This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. diff --git a/apps/web/next.config.ts b/apps/web/next.config.ts deleted file mode 100644 index a122cf5..0000000 --- a/apps/web/next.config.ts +++ /dev/null @@ -1,28 +0,0 @@ -import path from 'path'; -import type { NextConfig } from 'next'; - -const nextConfig: NextConfig = { - output: 'standalone', - turbopack: { - // Keep Turbopack pinned to the monorepo root so dev HMR - // does not mis-detect `apps/web` as a standalone project. - root: path.resolve(__dirname, '../..'), - }, - webpack(config) { - // Enable WebAssembly for tiny-secp256k1 (used by ecpair/bitcoinjs-lib) - config.experiments = { - ...config.experiments, - asyncWebAssembly: true, - }; - - // Prevent webpack from changing the output of WASM imports - config.module.rules.push({ - test: /\.wasm$/, - type: 'webassembly/async', - }); - - return config; - }, -}; - -export default nextConfig; diff --git a/apps/web/package.json b/apps/web/package.json deleted file mode 100644 index 4da3ed5..0000000 --- a/apps/web/package.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "name": "web", - "version": "0.1.0", - "private": true, - "scripts": { - "dev": "next dev --webpack", - "build": "next build", - "start": "next start" - }, - "dependencies": { - "@scure/bip32": "^2.0.1", - "@scure/bip39": "^2.0.1", - "@solana/web3.js": "^1.98.4", - "@uniswap/router-sdk": "^2.7.1", - "@uniswap/sdk-core": "^7.12.1", - "@uniswap/universal-router-sdk": "^4.34.0", - "@uniswap/v3-sdk": "^3.29.1", - "@uniswap/v4-sdk": "^1.29.1", - "@yudiel/react-qr-scanner": "^2.5.1", - "bip32": "^5.0.1", - "bitcoinjs-lib": "^7.0.1", - "ecpair": "^3.0.1", - "ed25519-hd-key": "^1.3.0", - "ethers": "5.7.2", - "next": "16.1.6", - "qrcode.react": "^4.2.0", - "react": "19.2.3", - "react-dom": "19.2.3", - "tiny-secp256k1": "^2.2.4", - "zustand": "^5.0.11" - }, - "devDependencies": { - "@types/node": "^20", - "@types/react": "^19", - "@types/react-dom": "^19", - "typescript": "^5" - } -} diff --git a/apps/web/pnpm-lock.yaml b/apps/web/pnpm-lock.yaml deleted file mode 100644 index 8524033..0000000 --- a/apps/web/pnpm-lock.yaml +++ /dev/null @@ -1,1770 +0,0 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@scure/bip32': - specifier: ^2.0.1 - version: 2.0.1 - '@scure/bip39': - specifier: ^2.0.1 - version: 2.0.1 - '@solana/web3.js': - specifier: ^1.98.4 - version: 1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) - '@yudiel/react-qr-scanner': - specifier: ^2.5.1 - version: 2.5.1(@types/emscripten@1.41.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - bip32: - specifier: ^5.0.1 - version: 5.0.1(typescript@5.9.3) - bitcoinjs-lib: - specifier: ^7.0.1 - version: 7.0.1(typescript@5.9.3) - ecpair: - specifier: ^3.0.1 - version: 3.0.1(typescript@5.9.3) - ed25519-hd-key: - specifier: ^1.3.0 - version: 1.3.0 - ethers: - specifier: ^6.16.0 - version: 6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) - next: - specifier: 16.1.6 - version: 16.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - qrcode.react: - specifier: ^4.2.0 - version: 4.2.0(react@19.2.3) - react: - specifier: 19.2.3 - version: 19.2.3 - react-dom: - specifier: 19.2.3 - version: 19.2.3(react@19.2.3) - tiny-secp256k1: - specifier: ^2.2.4 - version: 2.2.4 - zustand: - specifier: ^5.0.11 - version: 5.0.11(@types/react@19.2.14)(react@19.2.3) - devDependencies: - '@types/node': - specifier: ^20 - version: 20.19.37 - '@types/react': - specifier: ^19 - version: 19.2.14 - '@types/react-dom': - specifier: ^19 - version: 19.2.3(@types/react@19.2.14) - typescript: - specifier: ^5 - version: 5.9.3 - -packages: - - '@adraffy/ens-normalize@1.10.1': - resolution: {integrity: sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==} - - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} - engines: {node: '>=6.9.0'} - - '@emnapi/runtime@1.8.1': - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} - - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} - - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] - - '@next/env@16.1.6': - resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==} - - '@next/swc-darwin-arm64@16.1.6': - resolution: {integrity: sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] - - '@next/swc-darwin-x64@16.1.6': - resolution: {integrity: sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-linux-arm64-gnu@16.1.6': - resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-arm64-musl@16.1.6': - resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@next/swc-linux-x64-gnu@16.1.6': - resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-x64-musl@16.1.6': - resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@next/swc-win32-arm64-msvc@16.1.6': - resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-x64-msvc@16.1.6': - resolution: {integrity: sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@noble/curves@1.2.0': - resolution: {integrity: sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==} - - '@noble/curves@1.9.7': - resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} - engines: {node: ^14.21.3 || >=16} - - '@noble/curves@2.0.1': - resolution: {integrity: sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==} - engines: {node: '>= 20.19.0'} - - '@noble/hashes@1.3.2': - resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==} - engines: {node: '>= 16'} - - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@2.0.1': - resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} - engines: {node: '>= 20.19.0'} - - '@scure/base@1.2.6': - resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} - - '@scure/base@2.0.0': - resolution: {integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==} - - '@scure/bip32@2.0.1': - resolution: {integrity: sha512-4Md1NI5BzoVP+bhyJaY3K6yMesEFzNS1sE/cP+9nuvE7p/b0kx9XbpDHHFl8dHtufcbdHRUUQdRqLIPHN/s7yA==} - - '@scure/bip39@2.0.1': - resolution: {integrity: sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg==} - - '@solana/buffer-layout@4.0.1': - resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} - engines: {node: '>=5.10'} - - '@solana/codecs-core@2.3.0': - resolution: {integrity: sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==} - engines: {node: '>=20.18.0'} - peerDependencies: - typescript: '>=5.3.3' - - '@solana/codecs-numbers@2.3.0': - resolution: {integrity: sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==} - engines: {node: '>=20.18.0'} - peerDependencies: - typescript: '>=5.3.3' - - '@solana/errors@2.3.0': - resolution: {integrity: sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==} - engines: {node: '>=20.18.0'} - hasBin: true - peerDependencies: - typescript: '>=5.3.3' - - '@solana/web3.js@1.98.4': - resolution: {integrity: sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==} - - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} - - '@types/connect@3.4.38': - resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} - - '@types/emscripten@1.41.5': - resolution: {integrity: sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==} - - '@types/node@12.20.55': - resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - - '@types/node@20.19.37': - resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} - - '@types/node@22.7.5': - resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} - - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} - - '@types/ws@7.4.7': - resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} - - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - - '@yudiel/react-qr-scanner@2.5.1': - resolution: {integrity: sha512-FWzHaCneu30mQpE80VNWx4IPtBjXFEiTzhwWunZy3afvvAy/x0aVIgYijJKEbROoaAeDfcJ/gIyUCqPBP7bMOw==} - peerDependencies: - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 - - aes-js@4.0.0-beta.5: - resolution: {integrity: sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==} - - agentkeepalive@4.6.0: - resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} - engines: {node: '>= 8.0.0'} - - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - barcode-detector@3.0.8: - resolution: {integrity: sha512-Z9jzzE8ngEDyN9EU7lWdGgV07mcnEQnrX8W9WecXDqD2v+5CcVjt9+a134a5zb+kICvpsrDx6NYA6ay4LGFs8A==} - - base-x@3.0.11: - resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==} - - base-x@5.0.1: - resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.10.0: - resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} - engines: {node: '>=6.0.0'} - hasBin: true - - bech32@2.0.0: - resolution: {integrity: sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==} - - bip174@3.0.0: - resolution: {integrity: sha512-N3vz3rqikLEu0d6yQL8GTrSkpYb35NQKWMR7Hlza0lOj6ZOlvQ3Xr7N9Y+JPebaCVoEUHdBeBSuLxcHr71r+Lw==} - engines: {node: '>=18.0.0'} - - bip32@5.0.1: - resolution: {integrity: sha512-PWlHIAgYCfVhwqNpZyeakHXuLAGyN6rEQZnhxHxKI3BoFJRVWLl26455fhRlHsmbYcV986HqtPnt33Edu5sTCw==} - engines: {node: '>=18.0.0'} - - bitcoinjs-lib@7.0.1: - resolution: {integrity: sha512-vwEmpL5Tpj0I0RBdNkcDMXePoaYSTeKY6mL6/l5esbnTs+jGdPDuLp4NY1hSh6Zk5wSgePygZ4Wx5JJao30Pww==} - engines: {node: '>=18.0.0'} - - bn.js@5.2.3: - resolution: {integrity: sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==} - - borsh@0.7.0: - resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} - - bs58@4.0.1: - resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} - - bs58@6.0.0: - resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} - - bs58check@4.0.0: - resolution: {integrity: sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==} - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - - bufferutil@4.1.0: - resolution: {integrity: sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==} - engines: {node: '>=6.14.2'} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - caniuse-lite@1.0.30001777: - resolution: {integrity: sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==} - - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - - cipher-base@1.0.7: - resolution: {integrity: sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==} - engines: {node: '>= 0.10'} - - client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - - commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} - - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - create-hash@1.2.0: - resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} - - create-hmac@1.1.7: - resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - delay@5.0.0: - resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} - engines: {node: '>=10'} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - ecpair@3.0.1: - resolution: {integrity: sha512-uz8wMFvtdr58TLrXnAesBsoMEyY8UudLOfApcyg40XfZjP+gt1xO4cuZSIkZ8hTMTQ8+ETgt7xSIV4eM7M6VNw==} - engines: {node: '>=20.0.0'} - - ed25519-hd-key@1.3.0: - resolution: {integrity: sha512-IWwAyiiuJQhgu3L8NaHb68eJxTu2pgCwxIBdgpLJdKpYZM46+AXePSVTr7fkNKaUOfOL4IrjEUaQvyVRIDP7fg==} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es6-promise@4.2.8: - resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} - - es6-promisify@5.0.0: - resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - - ethers@6.16.0: - resolution: {integrity: sha512-U1wulmetNymijEhpSEQ7Ct/P/Jw9/e7R1j5XIbPRydgV2DjLVMsULDlNksq3RQnFgKoLlZf88ijYtWEXcPa07A==} - engines: {node: '>=14.0.0'} - - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - - eyes@0.1.8: - resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} - engines: {node: '> 0.1.90'} - - fast-stable-stringify@1.0.0: - resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} - - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hash-base@3.1.2: - resolution: {integrity: sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==} - engines: {node: '>= 0.8'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - humanize-ms@1.2.1: - resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isomorphic-ws@4.0.1: - resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} - peerDependencies: - ws: '*' - - jayson@4.3.0: - resolution: {integrity: sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==} - engines: {node: '>=8'} - hasBin: true - - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - md5.js@1.3.5: - resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - next@16.1.6: - resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==} - engines: {node: '>=20.9.0'} - hasBin: true - 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-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - - node-gyp-build@4.8.4: - resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} - hasBin: true - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - qrcode.react@4.2.0: - resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - react-dom@19.2.3: - resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} - peerDependencies: - react: ^19.2.3 - - react@19.2.3: - resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} - engines: {node: '>=0.10.0'} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - ripemd160@2.0.3: - resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} - engines: {node: '>= 0.8'} - - rpc-websockets@9.3.5: - resolution: {integrity: sha512-4mAmr+AEhPYJ9TmDtxF3r3ZcbWy7W8kvZ4PoZYw/Xgp2J7WixjwTgiQZsoTDvch5nimmg3Ay6/0Kuh9oIvVs9A==} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - - sdp@3.2.1: - resolution: {integrity: sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==} - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - sha.js@2.4.12: - resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} - engines: {node: '>= 0.10'} - hasBin: true - - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - stream-chain@2.2.5: - resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} - - stream-json@1.9.1: - resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - styled-jsx@5.1.6: - resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - 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 - - superstruct@2.0.2: - resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} - engines: {node: '>=14.0.0'} - - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} - - text-encoding-utf-8@1.0.2: - resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} - - tiny-secp256k1@2.2.4: - resolution: {integrity: sha512-FoDTcToPqZE454Q04hH9o2EhxWsm7pOSpicyHkgTwKhdKWdsTUuqfP5MLq3g+VjAtl2vSx6JpXGdwA2qpYkI0Q==} - engines: {node: '>=14.0.0'} - - to-buffer@1.2.2: - resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} - engines: {node: '>= 0.4'} - - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - - tslib@2.7.0: - resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tweetnacl@1.0.3: - resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} - - type-fest@5.4.4: - resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==} - engines: {node: '>=20'} - - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - uint8array-tools@0.0.7: - resolution: {integrity: sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==} - engines: {node: '>=14.0.0'} - - uint8array-tools@0.0.8: - resolution: {integrity: sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==} - engines: {node: '>=14.0.0'} - - uint8array-tools@0.0.9: - resolution: {integrity: sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A==} - engines: {node: '>=14.0.0'} - - undici-types@6.19.8: - resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - - undici-types@6.21.0: - resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - - utf-8-validate@6.0.6: - resolution: {integrity: sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==} - engines: {node: '>=6.14.2'} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - - valibot@1.2.0: - resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} - peerDependencies: - typescript: '>=5' - peerDependenciesMeta: - typescript: - optional: true - - varuint-bitcoin@2.0.0: - resolution: {integrity: sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==} - - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} - - webrtc-adapter@9.0.3: - resolution: {integrity: sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==} - engines: {node: '>=6.0.0', npm: '>=3.10.0'} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} - engines: {node: '>= 0.4'} - - wif@5.0.0: - resolution: {integrity: sha512-iFzrC/9ne740qFbNjTZ2FciSRJlHIXoxqk/Y5EnE08QOXu1WjJyCCswwDTYbohAOEnlCtLaAAQBhyaLRFh2hMA==} - - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.17.1: - resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==} - 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 - - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} - 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 - - zustand@5.0.11: - resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@types/react': '>=18.0.0' - immer: '>=9.0.6' - react: '>=18.0.0' - use-sync-external-store: '>=1.2.0' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - - zxing-wasm@2.2.4: - resolution: {integrity: sha512-1gq5zs4wuNTs5umWLypzNNeuJoluFvwmvjiiT3L9z/TMlVveeJRWy7h90xyUqCe+Qq0zL0w7o5zkdDMWDr9aZA==} - peerDependencies: - '@types/emscripten': '>=1.39.6' - -snapshots: - - '@adraffy/ens-normalize@1.10.1': {} - - '@babel/runtime@7.28.6': {} - - '@emnapi/runtime@1.8.1': - dependencies: - tslib: 2.8.1 - optional: true - - '@img/colour@1.1.0': - optional: true - - '@img/sharp-darwin-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 - optional: true - - '@img/sharp-darwin-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.2.4': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-s390x@1.2.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - optional: true - - '@img/sharp-linux-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 - optional: true - - '@img/sharp-linux-arm@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 - optional: true - - '@img/sharp-linux-ppc64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 - optional: true - - '@img/sharp-linux-riscv64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 - optional: true - - '@img/sharp-linux-s390x@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 - optional: true - - '@img/sharp-linux-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - optional: true - - '@img/sharp-wasm32@0.34.5': - dependencies: - '@emnapi/runtime': 1.8.1 - optional: true - - '@img/sharp-win32-arm64@0.34.5': - optional: true - - '@img/sharp-win32-ia32@0.34.5': - optional: true - - '@img/sharp-win32-x64@0.34.5': - optional: true - - '@next/env@16.1.6': {} - - '@next/swc-darwin-arm64@16.1.6': - optional: true - - '@next/swc-darwin-x64@16.1.6': - optional: true - - '@next/swc-linux-arm64-gnu@16.1.6': - optional: true - - '@next/swc-linux-arm64-musl@16.1.6': - optional: true - - '@next/swc-linux-x64-gnu@16.1.6': - optional: true - - '@next/swc-linux-x64-musl@16.1.6': - optional: true - - '@next/swc-win32-arm64-msvc@16.1.6': - optional: true - - '@next/swc-win32-x64-msvc@16.1.6': - optional: true - - '@noble/curves@1.2.0': - dependencies: - '@noble/hashes': 1.3.2 - - '@noble/curves@1.9.7': - dependencies: - '@noble/hashes': 1.8.0 - - '@noble/curves@2.0.1': - dependencies: - '@noble/hashes': 2.0.1 - - '@noble/hashes@1.3.2': {} - - '@noble/hashes@1.8.0': {} - - '@noble/hashes@2.0.1': {} - - '@scure/base@1.2.6': {} - - '@scure/base@2.0.0': {} - - '@scure/bip32@2.0.1': - dependencies: - '@noble/curves': 2.0.1 - '@noble/hashes': 2.0.1 - '@scure/base': 2.0.0 - - '@scure/bip39@2.0.1': - dependencies: - '@noble/hashes': 2.0.1 - '@scure/base': 2.0.0 - - '@solana/buffer-layout@4.0.1': - dependencies: - buffer: 6.0.3 - - '@solana/codecs-core@2.3.0(typescript@5.9.3)': - dependencies: - '@solana/errors': 2.3.0(typescript@5.9.3) - typescript: 5.9.3 - - '@solana/codecs-numbers@2.3.0(typescript@5.9.3)': - dependencies: - '@solana/codecs-core': 2.3.0(typescript@5.9.3) - '@solana/errors': 2.3.0(typescript@5.9.3) - typescript: 5.9.3 - - '@solana/errors@2.3.0(typescript@5.9.3)': - dependencies: - chalk: 5.6.2 - commander: 14.0.3 - typescript: 5.9.3 - - '@solana/web3.js@1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': - dependencies: - '@babel/runtime': 7.28.6 - '@noble/curves': 1.9.7 - '@noble/hashes': 1.8.0 - '@solana/buffer-layout': 4.0.1 - '@solana/codecs-numbers': 2.3.0(typescript@5.9.3) - agentkeepalive: 4.6.0 - bn.js: 5.2.3 - borsh: 0.7.0 - bs58: 4.0.1 - buffer: 6.0.3 - fast-stable-stringify: 1.0.0 - jayson: 4.3.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) - node-fetch: 2.7.0 - rpc-websockets: 9.3.5 - superstruct: 2.0.2 - transitivePeerDependencies: - - bufferutil - - encoding - - typescript - - utf-8-validate - - '@swc/helpers@0.5.15': - dependencies: - tslib: 2.8.1 - - '@types/connect@3.4.38': - dependencies: - '@types/node': 20.19.37 - - '@types/emscripten@1.41.5': {} - - '@types/node@12.20.55': {} - - '@types/node@20.19.37': - dependencies: - undici-types: 6.21.0 - - '@types/node@22.7.5': - dependencies: - undici-types: 6.19.8 - - '@types/react-dom@19.2.3(@types/react@19.2.14)': - dependencies: - '@types/react': 19.2.14 - - '@types/react@19.2.14': - dependencies: - csstype: 3.2.3 - - '@types/uuid@10.0.0': {} - - '@types/ws@7.4.7': - dependencies: - '@types/node': 20.19.37 - - '@types/ws@8.18.1': - dependencies: - '@types/node': 20.19.37 - - '@yudiel/react-qr-scanner@2.5.1(@types/emscripten@1.41.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': - dependencies: - barcode-detector: 3.0.8(@types/emscripten@1.41.5) - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - webrtc-adapter: 9.0.3 - transitivePeerDependencies: - - '@types/emscripten' - - aes-js@4.0.0-beta.5: {} - - agentkeepalive@4.6.0: - dependencies: - humanize-ms: 1.2.1 - - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - barcode-detector@3.0.8(@types/emscripten@1.41.5): - dependencies: - zxing-wasm: 2.2.4(@types/emscripten@1.41.5) - transitivePeerDependencies: - - '@types/emscripten' - - base-x@3.0.11: - dependencies: - safe-buffer: 5.2.1 - - base-x@5.0.1: {} - - base64-js@1.5.1: {} - - baseline-browser-mapping@2.10.0: {} - - bech32@2.0.0: {} - - bip174@3.0.0: - dependencies: - uint8array-tools: 0.0.9 - varuint-bitcoin: 2.0.0 - - bip32@5.0.1(typescript@5.9.3): - dependencies: - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - uint8array-tools: 0.0.8 - valibot: 1.2.0(typescript@5.9.3) - wif: 5.0.0 - transitivePeerDependencies: - - typescript - - bitcoinjs-lib@7.0.1(typescript@5.9.3): - dependencies: - '@noble/hashes': 1.8.0 - bech32: 2.0.0 - bip174: 3.0.0 - bs58check: 4.0.0 - uint8array-tools: 0.0.9 - valibot: 1.2.0(typescript@5.9.3) - varuint-bitcoin: 2.0.0 - transitivePeerDependencies: - - typescript - - bn.js@5.2.3: {} - - borsh@0.7.0: - dependencies: - bn.js: 5.2.3 - bs58: 4.0.1 - text-encoding-utf-8: 1.0.2 - - bs58@4.0.1: - dependencies: - base-x: 3.0.11 - - bs58@6.0.0: - dependencies: - base-x: 5.0.1 - - bs58check@4.0.0: - dependencies: - '@noble/hashes': 1.8.0 - bs58: 6.0.0 - - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - bufferutil@4.1.0: - dependencies: - node-gyp-build: 4.8.4 - optional: true - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bind@1.0.8: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - caniuse-lite@1.0.30001777: {} - - chalk@5.6.2: {} - - cipher-base@1.0.7: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - to-buffer: 1.2.2 - - client-only@0.0.1: {} - - commander@14.0.3: {} - - commander@2.20.3: {} - - core-util-is@1.0.3: {} - - create-hash@1.2.0: - dependencies: - cipher-base: 1.0.7 - inherits: 2.0.4 - md5.js: 1.3.5 - ripemd160: 2.0.3 - sha.js: 2.4.12 - - create-hmac@1.1.7: - dependencies: - cipher-base: 1.0.7 - create-hash: 1.2.0 - inherits: 2.0.4 - ripemd160: 2.0.3 - safe-buffer: 5.2.1 - sha.js: 2.4.12 - - csstype@3.2.3: {} - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - delay@5.0.0: {} - - detect-libc@2.1.2: - optional: true - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - ecpair@3.0.1(typescript@5.9.3): - dependencies: - uint8array-tools: 0.0.8 - valibot: 1.2.0(typescript@5.9.3) - wif: 5.0.0 - transitivePeerDependencies: - - typescript - - ed25519-hd-key@1.3.0: - dependencies: - create-hmac: 1.1.7 - tweetnacl: 1.0.3 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - es6-promise@4.2.8: {} - - es6-promisify@5.0.0: - dependencies: - es6-promise: 4.2.8 - - ethers@6.16.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): - dependencies: - '@adraffy/ens-normalize': 1.10.1 - '@noble/curves': 1.2.0 - '@noble/hashes': 1.3.2 - '@types/node': 22.7.5 - aes-js: 4.0.0-beta.5 - tslib: 2.7.0 - ws: 8.17.1(bufferutil@4.1.0)(utf-8-validate@6.0.6) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - eventemitter3@5.0.4: {} - - eyes@0.1.8: {} - - fast-stable-stringify@1.0.0: {} - - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - - function-bind@1.1.2: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - gopd@1.2.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hash-base@3.1.2: - dependencies: - inherits: 2.0.4 - readable-stream: 2.3.8 - safe-buffer: 5.2.1 - to-buffer: 1.2.2 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - humanize-ms@1.2.1: - dependencies: - ms: 2.1.3 - - ieee754@1.2.1: {} - - inherits@2.0.4: {} - - is-callable@1.2.7: {} - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.20 - - isarray@1.0.0: {} - - isarray@2.0.5: {} - - isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6)): - dependencies: - ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6) - - jayson@4.3.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): - dependencies: - '@types/connect': 3.4.38 - '@types/node': 12.20.55 - '@types/ws': 7.4.7 - commander: 2.20.3 - delay: 5.0.0 - es6-promisify: 5.0.0 - eyes: 0.1.8 - isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - json-stringify-safe: 5.0.1 - stream-json: 1.9.1 - uuid: 8.3.2 - ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6) - transitivePeerDependencies: - - bufferutil - - utf-8-validate - - json-stringify-safe@5.0.1: {} - - math-intrinsics@1.1.0: {} - - md5.js@1.3.5: - dependencies: - hash-base: 3.1.2 - inherits: 2.0.4 - safe-buffer: 5.2.1 - - ms@2.1.3: {} - - nanoid@3.3.11: {} - - next@16.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3): - dependencies: - '@next/env': 16.1.6 - '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.0 - caniuse-lite: 1.0.30001777 - postcss: 8.4.31 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(react@19.2.3) - optionalDependencies: - '@next/swc-darwin-arm64': 16.1.6 - '@next/swc-darwin-x64': 16.1.6 - '@next/swc-linux-arm64-gnu': 16.1.6 - '@next/swc-linux-arm64-musl': 16.1.6 - '@next/swc-linux-x64-gnu': 16.1.6 - '@next/swc-linux-x64-musl': 16.1.6 - '@next/swc-win32-arm64-msvc': 16.1.6 - '@next/swc-win32-x64-msvc': 16.1.6 - sharp: 0.34.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - node-gyp-build@4.8.4: - optional: true - - picocolors@1.1.1: {} - - possible-typed-array-names@1.1.0: {} - - postcss@8.4.31: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - process-nextick-args@2.0.1: {} - - qrcode.react@4.2.0(react@19.2.3): - dependencies: - react: 19.2.3 - - react-dom@19.2.3(react@19.2.3): - dependencies: - react: 19.2.3 - scheduler: 0.27.0 - - react@19.2.3: {} - - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - ripemd160@2.0.3: - dependencies: - hash-base: 3.1.2 - inherits: 2.0.4 - - rpc-websockets@9.3.5: - dependencies: - '@swc/helpers': 0.5.15 - '@types/uuid': 10.0.0 - '@types/ws': 8.18.1 - buffer: 6.0.3 - eventemitter3: 5.0.4 - uuid: 11.1.0 - ws: 8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) - optionalDependencies: - bufferutil: 4.1.0 - utf-8-validate: 6.0.6 - - safe-buffer@5.1.2: {} - - safe-buffer@5.2.1: {} - - scheduler@0.27.0: {} - - sdp@3.2.1: {} - - semver@7.7.4: - optional: true - - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - - sha.js@2.4.12: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - to-buffer: 1.2.2 - - sharp@0.34.5: - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.7.4 - 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 - optional: true - - source-map-js@1.2.1: {} - - stream-chain@2.2.5: {} - - stream-json@1.9.1: - dependencies: - stream-chain: 2.2.5 - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - styled-jsx@5.1.6(react@19.2.3): - dependencies: - client-only: 0.0.1 - react: 19.2.3 - - superstruct@2.0.2: {} - - tagged-tag@1.0.0: {} - - text-encoding-utf-8@1.0.2: {} - - tiny-secp256k1@2.2.4: - dependencies: - uint8array-tools: 0.0.7 - - to-buffer@1.2.2: - dependencies: - isarray: 2.0.5 - safe-buffer: 5.2.1 - typed-array-buffer: 1.0.3 - - tr46@0.0.3: {} - - tslib@2.7.0: {} - - tslib@2.8.1: {} - - tweetnacl@1.0.3: {} - - type-fest@5.4.4: - dependencies: - tagged-tag: 1.0.0 - - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - - typescript@5.9.3: {} - - uint8array-tools@0.0.7: {} - - uint8array-tools@0.0.8: {} - - uint8array-tools@0.0.9: {} - - undici-types@6.19.8: {} - - undici-types@6.21.0: {} - - utf-8-validate@6.0.6: - dependencies: - node-gyp-build: 4.8.4 - optional: true - - util-deprecate@1.0.2: {} - - uuid@11.1.0: {} - - uuid@8.3.2: {} - - valibot@1.2.0(typescript@5.9.3): - optionalDependencies: - typescript: 5.9.3 - - varuint-bitcoin@2.0.0: - dependencies: - uint8array-tools: 0.0.8 - - webidl-conversions@3.0.1: {} - - webrtc-adapter@9.0.3: - dependencies: - sdp: 3.2.1 - - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - - which-typed-array@1.1.20: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - - wif@5.0.0: - dependencies: - bs58check: 4.0.0 - - ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6): - optionalDependencies: - bufferutil: 4.1.0 - utf-8-validate: 6.0.6 - - ws@8.17.1(bufferutil@4.1.0)(utf-8-validate@6.0.6): - optionalDependencies: - bufferutil: 4.1.0 - utf-8-validate: 6.0.6 - - ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): - optionalDependencies: - bufferutil: 4.1.0 - utf-8-validate: 6.0.6 - - zustand@5.0.11(@types/react@19.2.14)(react@19.2.3): - optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.3 - - zxing-wasm@2.2.4(@types/emscripten@1.41.5): - dependencies: - '@types/emscripten': 1.41.5 - type-fest: 5.4.4 diff --git a/apps/web/pnpm-workspace.yaml b/apps/web/pnpm-workspace.yaml deleted file mode 100644 index 581a9d5..0000000 --- a/apps/web/pnpm-workspace.yaml +++ /dev/null @@ -1,3 +0,0 @@ -ignoredBuiltDependencies: - - sharp - - unrs-resolver diff --git a/apps/web/public/file.svg b/apps/web/public/file.svg deleted file mode 100644 index 004145c..0000000 --- a/apps/web/public/file.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/web/public/globe.svg b/apps/web/public/globe.svg deleted file mode 100644 index 567f17b..0000000 --- a/apps/web/public/globe.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/web/public/next.svg b/apps/web/public/next.svg deleted file mode 100644 index 5174b28..0000000 --- a/apps/web/public/next.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/web/public/vercel.svg b/apps/web/public/vercel.svg deleted file mode 100644 index 7705396..0000000 --- a/apps/web/public/vercel.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/web/public/window.svg b/apps/web/public/window.svg deleted file mode 100644 index b2b2a44..0000000 --- a/apps/web/public/window.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/apps/web/src/app/bridge/page.tsx b/apps/web/src/app/bridge/page.tsx deleted file mode 100644 index 3439a0b..0000000 --- a/apps/web/src/app/bridge/page.tsx +++ /dev/null @@ -1,330 +0,0 @@ -'use client'; - -import { useEffect, useMemo, useState } from 'react'; -import Link from 'next/link'; -import { useRouter } from 'next/navigation'; -import { - BRIDGE_CHAINS, - BRIDGE_CHAIN_OPTIONS, - getDestinationChainOptions, - getTokenOptions, - getDefaultToken, - type BridgeChainKey, -} from '@/lib/bridge/constants'; -import { useBridge } from '@/hooks/useBridge'; -import { useGasPrice } from '@/hooks/useGasPrice'; -import { useGasSettings, type GasMode } from '@/hooks/useGasSettings'; -import { useAuthStore } from '@/store/auth-store'; - -const GAS_MODE_LABELS: Record = { - slow: 'Slow', - normal: 'Normal', - fast: 'Fast', - custom: 'Custom', -}; - -const GAS_MODES: GasMode[] = ['slow', 'normal', 'fast', 'custom']; - -export default function BridgePage() { - const router = useRouter(); - const user = useAuthStore((state) => state.user); - const { data: gasPriceData, loading: gasLoading } = useGasPrice(); - const gas = useGasSettings(gasPriceData); - const { - status, quote, bridgeStatus, requestId, txHashes, error, - sourceChain, setSourceChain, sourceWallet, - fetchQuote, submitBridge, resetBridge, - } = useBridge(); - - const [sourceToken, setSourceToken] = useState(() => getDefaultToken('ETH')); - const [destChain, setDestChain] = useState('SOL'); - const [destToken, setDestToken] = useState(() => getDefaultToken('SOL')); - const [amount, setAmount] = useState(''); - const [confirmed, setConfirmed] = useState(false); - - useEffect(() => { - if (!user) router.push('/login'); - }, [router, user]); - - const destChainOptions = useMemo(() => getDestinationChainOptions(sourceChain), [sourceChain]); - const sourceTokenOptions = useMemo(() => getTokenOptions(sourceChain), [sourceChain]); - const destTokenOptions = useMemo(() => getTokenOptions(destChain), [destChain]); - - const handleSourceChainChange = (newChain: BridgeChainKey) => { - setSourceChain(newChain); - setSourceToken(getDefaultToken(newChain)); - // If dest chain is same as new source, switch dest - const newDestOptions = getDestinationChainOptions(newChain); - if (!newDestOptions.includes(destChain)) { - setDestChain(newDestOptions[0]); - setDestToken(getDefaultToken(newDestOptions[0])); - } - handleReset(); - }; - - const handleDestChainChange = (newChain: BridgeChainKey) => { - setDestChain(newChain); - setDestToken(getDefaultToken(newChain)); - handleReset(); - }; - - if (!user) return null; - - const canQuote = - Number(amount) > 0 && - status !== 'quoting' && - status !== 'executing' && - status !== 'monitoring'; - const canBridge = !!quote && confirmed && status !== 'executing' && status !== 'monitoring'; - const isEvmSource = sourceChain === 'ETH' || sourceChain === 'BSC'; - const showGasControls = sourceChain === 'ETH'; // BSC uses fixed gas price - - const handleQuote = async () => { - setConfirmed(false); - await fetchQuote({ sourceChain, sourceToken, destChain, destToken, amount }); - }; - - const handleBridge = async () => { - await submitBridge( - { sourceChain, sourceToken, destChain, destToken, amount }, - isEvmSource ? gas.effectiveMaxFee : null, - isEvmSource ? gas.effectivePriorityFee : null, - ); - }; - - const handleReset = () => { - setConfirmed(false); - resetBridge(); - }; - - const tierGwei = (mode: GasMode): string => { - if (mode === 'custom') return ''; - if (!gasPriceData) return '...'; - const v = gasPriceData[mode].maxFeePerGas; - if (v >= 1) return v.toFixed(2); - const s = v.toFixed(4); - return s.replace(/0+$/, '').replace(/\.$/, ''); - }; - - const sourceExplorerBase = BRIDGE_CHAINS[sourceChain].explorerTxBaseUrl; - const destExplorerBase = BRIDGE_CHAINS[destChain].explorerTxBaseUrl; - - return ( -
-
-

Bridge

- - Back to Dashboard - -
- -
- {/* Source Chain */} -
- - -
- - {/* Source Token */} -
- - -
- - {/* Destination Chain */} -
- - -
- - {/* Destination Token */} -
- - -
- - {/* Amount */} -
- - { setAmount(e.target.value); handleReset(); }} - type="number" - min="0" - step="any" - style={inputStyle} - /> -
- - {/* Gas Speed — only for ETH source */} - {showGasControls ? ( -
- -
- {GAS_MODES.map((mode) => ( - - ))} -
- {gas.gasMode === 'custom' && ( - gas.setCustomGwei(e.target.value)} - type="number" - min="0" - step="0.01" - placeholder="Enter gwei" - style={{ ...inputStyle, marginTop: 6 }} - /> - )} -

- Effective: {gas.displayGwei} -

-
- ) : sourceChain === 'BSC' ? ( -
- -

Fixed: 0.055 gwei (BSC)

-
- ) : ( -
- -

Auto (managed by Relay)

-
- )} - - -
- - {/* Quote Review */} - {quote && ( -
-

Review

-

Expected output: {quote.outputAmountFormatted} {quote.outputSymbol}

-

Minimum output: {quote.minimumAmountFormatted}

-

Estimated fee: {quote.feeSummary}

-

Estimated time: {quote.timeEstimateSeconds ? `${quote.timeEstimateSeconds}s` : 'Unavailable'}

- {showGasControls && ( -

Gas: {gas.displayGwei} ({GAS_MODE_LABELS[gas.gasMode]})

- )} - {sourceChain === 'BSC' && ( -

Gas: 0.055 gwei (BSC fixed)

- )} - - - - -
- )} - - {/* Status */} - {(requestId || txHashes.length > 0 || bridgeStatus) && ( -
-

Status

- {requestId &&

Request ID: {requestId}

} - {bridgeStatus &&

Relay status: {bridgeStatus.status}

} - {txHashes.map((hash) => ( -

- Origin tx:{' '} - - {hash} - -

- ))} - {(bridgeStatus?.txHashes ?? []).map((hash) => ( -

- Destination tx:{' '} - - {hash} - -

- ))} -
- )} - - {(error || status === 'error') && ( -

- {error ?? 'Bridge failed'} -

- )} -
- ); -} - -const fieldGroupStyle: React.CSSProperties = { - display: 'flex', - flexDirection: 'column', - gap: 6, - marginBottom: 12, -}; - -const inputStyle: React.CSSProperties = { - width: '100%', - padding: 8, -}; - -const navButtonStyle: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - justifyContent: 'center', - padding: '6px 12px', - border: '1px solid #ccc', - borderRadius: 4, -}; diff --git a/apps/web/src/app/dashboard/page.tsx b/apps/web/src/app/dashboard/page.tsx deleted file mode 100644 index d4b3fff..0000000 --- a/apps/web/src/app/dashboard/page.tsx +++ /dev/null @@ -1,166 +0,0 @@ -'use client'; - -import { useEffect } from 'react'; -import Link from 'next/link'; -import { useRouter } from 'next/navigation'; -import { useBalances } from '@/hooks/useBalances'; -import type { ChainBalance } from '@/lib/balances/types'; -import { useAuthStore } from '@/store/auth-store'; - -export default function DashboardPage() { - const router = useRouter(); - const { user, wallets, logout } = useAuthStore(); - const { portfolio, loading, refreshing, error, refresh } = useBalances(); - - useEffect(() => { - if (!user) { - router.push('/login'); - } - }, [user, router]); - - if (!user) return null; - - return ( -
-
-

Dashboard

-
- {user.email} - - Send - - - Receive - - - Swap - - - Bridge - - - Settings - - - -
-
- -
-

- Total Portfolio USD -

-

{formatUsd(portfolio?.totalUsd ?? null)}

-

- {portfolio?.updatedAt - ? `Updated ${new Date(portfolio.updatedAt).toLocaleTimeString()}` - : loading - ? 'Loading balances...' - : 'Balances will appear after the first refresh.'} -

-
- - {error && ( -

- {error} -

- )} - - {portfolio?.priceError && ( -

- USD pricing is partially unavailable: {portfolio.priceError} -

- )} - -

Your Wallets

- {wallets.map((w) => { - const chainBalance = getChainBalance(w.chain, portfolio?.chains); - - return ( -
-
-

{w.chain}

- {formatUsd(chainBalance?.totalUsd ?? null)} -
-

- Address: {w.address} -

- - {chainBalance?.error && chainBalance.error !== '__transient__' && ( -

- {chainBalance.error} -

- )} - -
- {chainBalance?.tokens.length ? ( - chainBalance.tokens.map((token) => ( -
- {token.symbol} - {formatTokenAmount(token.balanceFormatted)} - {formatUsd(token.valueUsd)} -
- )) - ) : ( -

- {loading ? 'Loading balances...' : 'No balances loaded yet.'} -

- )} -
-
- ); - })} -
- ); -} - -function getChainBalance(chain: string, chains?: ChainBalance[]): ChainBalance | undefined { - return chains?.find((item) => item.chain === chain); -} - -function formatUsd(value: number | null): string { - if (typeof value !== 'number') { - return 'Unavailable'; - } - - return new Intl.NumberFormat('en-US', { - style: 'currency', - currency: 'USD', - maximumFractionDigits: 2, - }).format(value); -} - -function formatTokenAmount(value: string): string { - const numericValue = Number(value); - - if (!Number.isFinite(numericValue)) { - return value; - } - - return new Intl.NumberFormat('en-US', { - minimumFractionDigits: 0, - maximumFractionDigits: 6, - }).format(numericValue); -} - -const navButtonStyle: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - justifyContent: 'center', - padding: '6px 12px', - border: '1px solid #ccc', - borderRadius: 4, -}; diff --git a/apps/web/src/app/favicon.ico b/apps/web/src/app/favicon.ico deleted file mode 100644 index 718d6fe..0000000 Binary files a/apps/web/src/app/favicon.ico and /dev/null differ diff --git a/apps/web/src/app/globals.css b/apps/web/src/app/globals.css deleted file mode 100644 index e3734be..0000000 --- a/apps/web/src/app/globals.css +++ /dev/null @@ -1,42 +0,0 @@ -:root { - --background: #ffffff; - --foreground: #171717; -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } -} - -html, -body { - max-width: 100vw; - overflow-x: hidden; -} - -body { - color: var(--foreground); - background: var(--background); - font-family: Arial, Helvetica, sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -* { - box-sizing: border-box; - padding: 0; - margin: 0; -} - -a { - color: inherit; - text-decoration: none; -} - -@media (prefers-color-scheme: dark) { - html { - color-scheme: dark; - } -} diff --git a/apps/web/src/app/layout.tsx b/apps/web/src/app/layout.tsx deleted file mode 100644 index 4e4e439..0000000 --- a/apps/web/src/app/layout.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import type { Metadata } from "next"; - -export const metadata: Metadata = { - title: "Crypto Wallet", -}; - -export default function RootLayout({ - children, -}: Readonly<{ - children: React.ReactNode; -}>) { - return ( - - {children} - - ); -} diff --git a/apps/web/src/app/login/page.tsx b/apps/web/src/app/login/page.tsx deleted file mode 100644 index 1ffc81d..0000000 --- a/apps/web/src/app/login/page.tsx +++ /dev/null @@ -1,125 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { useAuthStore } from '@/store/auth-store'; - -type Step = 'email' | 'code'; - -export default function LoginPage() { - const router = useRouter(); - const { loginStart, loginComplete, loading, error, clearError } = useAuthStore(); - const [step, setStep] = useState('email'); - const [email, setEmail] = useState(''); - const [code, setCode] = useState(''); - const [password, setPassword] = useState(''); - - const handleEmailSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - clearError(); - await loginStart(email); - - const state = useAuthStore.getState(); - if (!state.error) { - setStep('code'); - } - }; - - const handleCodeSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - clearError(); - await loginComplete(email, password, code); - - const state = useAuthStore.getState(); - if (state.user) { - if (!state.mnemonicShown) { - router.push('/mnemonic'); - } else { - router.push('/dashboard'); - } - } - }; - - return ( -
-

Login

- - {step === 'email' && ( -
-
-
- setEmail(e.target.value)} - required - style={{ width: '100%', padding: 8 }} - placeholder="you@example.com" - /> -
- {error &&

{error}

} - -
- )} - - {step === 'code' && ( -
-

- A verification code was sent to {email}. -

-
-
- setCode(e.target.value.replace(/\D/g, '').slice(0, 6))} - required - minLength={6} - maxLength={6} - style={{ width: '100%', padding: 8, letterSpacing: 4, fontSize: 18, textAlign: 'center' }} - placeholder="000000" - inputMode="numeric" - autoFocus - /> -
-
-
- setPassword(e.target.value)} - required - minLength={8} - style={{ width: '100%', padding: 8 }} - /> -
- {error &&

{error}

} - - -
- )} - -

- No account? Register -

-
- ); -} diff --git a/apps/web/src/app/mnemonic/page.tsx b/apps/web/src/app/mnemonic/page.tsx deleted file mode 100644 index 28c4962..0000000 --- a/apps/web/src/app/mnemonic/page.tsx +++ /dev/null @@ -1,148 +0,0 @@ -'use client'; - -import { useEffect, useMemo, useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { useAuthStore } from '@/store/auth-store'; - -export default function MnemonicPage() { - const router = useRouter(); - const { mnemonic, wallets, confirmMnemonic } = useAuthStore(); - const [step, setStep] = useState<'show' | 'verify' | 'keys'>('show'); - const [answers, setAnswers] = useState>({}); - const [verifyError, setVerifyError] = useState(''); - - const words = useMemo(() => mnemonic?.split(' ') || [], [mnemonic]); - - useEffect(() => { - if (!mnemonic) { - router.push('/dashboard'); - } - }, [mnemonic, router]); - - const quizIndices = useMemo(() => { - if (words.length < 3) return []; - const indices: number[] = []; - while (indices.length < 3) { - const idx = Math.floor(Math.random() * words.length); - if (!indices.includes(idx)) indices.push(idx); - } - return indices.sort((a, b) => a - b); - }, [words.length]); - - if (!mnemonic) return null; - - const handleVerify = () => { - setVerifyError(''); - for (const idx of quizIndices) { - if (answers[idx]?.trim().toLowerCase() !== words[idx]) { - setVerifyError(`Wrong word for position #${idx + 1}. Try again.`); - return; - } - } - setStep('keys'); - }; - - const handleConfirm = async () => { - await confirmMnemonic(); - router.push('/dashboard'); - }; - - const copyToClipboard = async (text: string) => { - try { - await navigator.clipboard.writeText(text); - setTimeout(() => { - try { - void navigator.clipboard.writeText(''); - } catch { - // Document may have lost focus; ignore - } - }, 60000); - } catch { - // Fallback for older browsers or denied permission - } - }; - - if (step === 'show') { - return ( -
-

Save Your Mnemonic Phrase

-

- Write these words down and store them safely. You will NOT be able to see them again! -

-
- {words.map((word, i) => ( -
- {i + 1}. {word} -
- ))} -
- - -
- ); - } - - if (step === 'verify') { - return ( -
-

Verify Mnemonic

-

Enter the following words from your mnemonic to confirm you saved it.

- {quizIndices.map((idx) => ( -
-
- setAnswers({ ...answers, [idx]: e.target.value })} - style={{ width: '100%', padding: 8 }} - /> -
- ))} - {verifyError &&

{verifyError}

} - - -
- ); - } - - return ( -
-

Your Private Keys

-

- Save these private keys. They will NOT be shown again! -

- {wallets.map((w) => ( -
-

{w.chain}

-

Address: {w.address}

-

- Private Key: {w.privateKey} -

- -
- ))} -
- -
- -
- ); -} diff --git a/apps/web/src/app/page.module.css b/apps/web/src/app/page.module.css deleted file mode 100644 index 59dea42..0000000 --- a/apps/web/src/app/page.module.css +++ /dev/null @@ -1,141 +0,0 @@ -.page { - --background: #fafafa; - --foreground: #fff; - - --text-primary: #000; - --text-secondary: #666; - - --button-primary-hover: #383838; - --button-secondary-hover: #f2f2f2; - --button-secondary-border: #ebebeb; - - display: flex; - min-height: 100vh; - align-items: center; - justify-content: center; - font-family: var(--font-geist-sans); - background-color: var(--background); -} - -.main { - display: flex; - min-height: 100vh; - width: 100%; - max-width: 800px; - flex-direction: column; - align-items: flex-start; - justify-content: space-between; - background-color: var(--foreground); - padding: 120px 60px; -} - -.intro { - display: flex; - flex-direction: column; - align-items: flex-start; - text-align: left; - gap: 24px; -} - -.intro h1 { - max-width: 320px; - font-size: 40px; - font-weight: 600; - line-height: 48px; - letter-spacing: -2.4px; - text-wrap: balance; - color: var(--text-primary); -} - -.intro p { - max-width: 440px; - font-size: 18px; - line-height: 32px; - text-wrap: balance; - color: var(--text-secondary); -} - -.intro a { - font-weight: 500; - color: var(--text-primary); -} - -.ctas { - display: flex; - flex-direction: row; - width: 100%; - max-width: 440px; - gap: 16px; - font-size: 14px; -} - -.ctas a { - display: flex; - justify-content: center; - align-items: center; - height: 40px; - padding: 0 16px; - border-radius: 128px; - border: 1px solid transparent; - transition: 0.2s; - cursor: pointer; - width: fit-content; - font-weight: 500; -} - -a.primary { - background: var(--text-primary); - color: var(--background); - gap: 8px; -} - -a.secondary { - border-color: var(--button-secondary-border); -} - -/* Enable hover only on non-touch devices */ -@media (hover: hover) and (pointer: fine) { - a.primary:hover { - background: var(--button-primary-hover); - border-color: transparent; - } - - a.secondary:hover { - background: var(--button-secondary-hover); - border-color: transparent; - } -} - -@media (max-width: 600px) { - .main { - padding: 48px 24px; - } - - .intro { - gap: 16px; - } - - .intro h1 { - font-size: 32px; - line-height: 40px; - letter-spacing: -1.92px; - } -} - -@media (prefers-color-scheme: dark) { - .logo { - filter: invert(); - } - - .page { - --background: #000; - --foreground: #000; - - --text-primary: #ededed; - --text-secondary: #999; - - --button-primary-hover: #ccc; - --button-secondary-hover: #1a1a1a; - --button-secondary-border: #1a1a1a; - } -} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx deleted file mode 100644 index 1413e02..0000000 --- a/apps/web/src/app/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { redirect } from 'next/navigation'; - -export default function Home() { - redirect('/login'); -} diff --git a/apps/web/src/app/receive/page.tsx b/apps/web/src/app/receive/page.tsx deleted file mode 100644 index 8d128f6..0000000 --- a/apps/web/src/app/receive/page.tsx +++ /dev/null @@ -1,228 +0,0 @@ -'use client'; - -import { useEffect, useMemo, useState } from 'react'; -import Link from 'next/link'; -import { useRouter } from 'next/navigation'; -import { QRCodeSVG } from 'qrcode.react'; -import { useAuthStore } from '@/store/auth-store'; -import { - SEND_CHAIN_OPTIONS, - SEND_CHAINS, - getTokenOptions, - getDefaultToken, - type SendChain, -} from '@/lib/send/constants'; -import { generateReceiveUri } from '@/lib/qr/generate'; - -export default function ReceivePage() { - const router = useRouter(); - const user = useAuthStore((state) => state.user); - const wallets = useAuthStore((state) => state.wallets); - - const [chain, setChain] = useState('ETH'); - const [token, setToken] = useState(getDefaultToken('ETH')); - const [amount, setAmount] = useState(''); - const [copied, setCopied] = useState(false); - - useEffect(() => { - if (!user) router.push('/login'); - }, [user, router]); - - // Reset token when chain changes - useEffect(() => { - setToken(getDefaultToken(chain)); - setAmount(''); - }, [chain]); - - const wallet = useMemo( - () => wallets.find((w) => w.chain === SEND_CHAINS[chain].walletChain), - [wallets, chain], - ); - - const address = wallet?.address ?? ''; - - // Ensure token is valid for the current chain (guards against stale state during chain switch) - const effectiveToken = useMemo(() => { - const options = getTokenOptions(chain); - return options.includes(token) ? token : getDefaultToken(chain); - }, [chain, token]); - - const qrUri = useMemo(() => { - if (!address) return ''; - return generateReceiveUri({ - chain, - token: effectiveToken, - address, - amount: amount.trim() || undefined, - }); - }, [chain, effectiveToken, address, amount]); - - const handleCopy = async () => { - if (!address) return; - try { - await navigator.clipboard.writeText(address); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } catch { - // Fallback - const textArea = document.createElement('textarea'); - textArea.value = address; - document.body.appendChild(textArea); - textArea.select(); - document.execCommand('copy'); - document.body.removeChild(textArea); - setCopied(true); - setTimeout(() => setCopied(false), 2000); - } - }; - - if (!user) return null; - - const tokenOptions = getTokenOptions(chain); - - return ( -
-
-

Receive

-
- Send - Dashboard -
-
- - {/* Chain selector */} -
- - -
- - {/* Token selector */} -
- - -
- - {/* Amount (optional) */} -
- - { - const v = e.target.value; - if (/^\d*\.?\d*$/.test(v)) setAmount(v); - }} - style={inputStyle} - /> -
- - {/* QR Code */} - {address && ( -
-
- -
-
- )} - - {/* Address display */} -
- -
- {address || 'No wallet found for this chain'} -
-
- - {/* Copy button */} - - - {/* URI preview */} - {qrUri && ( -
- QR URI: {qrUri} -
- )} -
- ); -} - -const labelStyle: React.CSSProperties = { - display: 'block', - marginBottom: 4, - fontSize: 13, - fontWeight: 600, -}; - -const selectStyle: React.CSSProperties = { - width: '100%', - padding: '8px 12px', - border: '1px solid #ccc', - borderRadius: 4, - fontSize: 14, -}; - -const inputStyle: React.CSSProperties = { - width: '100%', - padding: '8px 12px', - border: '1px solid #ccc', - borderRadius: 4, - fontSize: 14, - boxSizing: 'border-box', -}; - -const navButtonStyle: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - justifyContent: 'center', - padding: '6px 12px', - border: '1px solid #ccc', - borderRadius: 4, - textDecoration: 'none', - color: 'inherit', -}; diff --git a/apps/web/src/app/register/page.tsx b/apps/web/src/app/register/page.tsx deleted file mode 100644 index 8f18779..0000000 --- a/apps/web/src/app/register/page.tsx +++ /dev/null @@ -1,144 +0,0 @@ -'use client'; - -import { useState } from 'react'; -import { useRouter } from 'next/navigation'; -import { useAuthStore } from '@/store/auth-store'; - -type Step = 'email' | 'code'; - -export default function RegisterPage() { - const router = useRouter(); - const { registerStart, registerComplete, loading, error, clearError } = useAuthStore(); - const [step, setStep] = useState('email'); - const [email, setEmail] = useState(''); - const [code, setCode] = useState(''); - const [password, setPassword] = useState(''); - const [confirmPassword, setConfirmPassword] = useState(''); - const [localError, setLocalError] = useState(null); - - const handleEmailSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - clearError(); - setLocalError(null); - await registerStart(email); - - const state = useAuthStore.getState(); - if (!state.error) { - setStep('code'); - } - }; - - const handleCodeSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - clearError(); - setLocalError(null); - - if (password !== confirmPassword) { - setLocalError('Passwords do not match'); - return; - } - - await registerComplete(email, password, code); - - const state = useAuthStore.getState(); - if (state.user) { - router.push('/mnemonic'); - } - }; - - const displayError = localError || error; - - return ( -
-

Register

- - {step === 'email' && ( -
-
-
- setEmail(e.target.value)} - required - style={{ width: '100%', padding: 8 }} - placeholder="you@example.com" - /> -
- {displayError &&

{displayError}

} - -
- )} - - {step === 'code' && ( -
-

- A verification code was sent to {email}. -

-
-
- setCode(e.target.value.replace(/\D/g, '').slice(0, 6))} - required - minLength={6} - maxLength={6} - style={{ width: '100%', padding: 8, letterSpacing: 4, fontSize: 18, textAlign: 'center' }} - placeholder="000000" - inputMode="numeric" - autoFocus - /> -
-
-
- setPassword(e.target.value)} - required - minLength={8} - style={{ width: '100%', padding: 8 }} - /> -
-
-
- setConfirmPassword(e.target.value)} - required - minLength={8} - style={{ width: '100%', padding: 8 }} - /> -
- {displayError &&

{displayError}

} - - -
- )} - -

- Already have an account? Login -

-
- ); -} diff --git a/apps/web/src/app/send/page.tsx b/apps/web/src/app/send/page.tsx deleted file mode 100644 index ba2f388..0000000 --- a/apps/web/src/app/send/page.tsx +++ /dev/null @@ -1,564 +0,0 @@ -'use client'; - -import { useCallback, useEffect, useMemo, useState } from 'react'; -import Link from 'next/link'; -import { useRouter } from 'next/navigation'; -import { Scanner } from '@yudiel/react-qr-scanner'; -import { useAuthStore } from '@/store/auth-store'; -import { useBalances } from '@/hooks/useBalances'; -import { useGasPrice } from '@/hooks/useGasPrice'; -import { useGasSettings, type GasMode } from '@/hooks/useGasSettings'; -import { - SEND_CHAIN_OPTIONS, - SEND_CHAINS, - getTokenOptions, - getDefaultToken, - type SendChain, -} from '@/lib/send/constants'; -import { validateAddress } from '@/lib/send/validate'; -import { parseQrUri } from '@/lib/qr/parse'; -import { executeSend, type SendResult } from '@/lib/send/execute'; -import type { ChainBalance } from '@/lib/balances/types'; - -const GAS_MODE_LABELS: Record = { - slow: 'Slow', - normal: 'Normal', - fast: 'Fast', - custom: 'Custom', -}; -const GAS_MODES: GasMode[] = ['slow', 'normal', 'fast', 'custom']; - -type SendStatus = 'idle' | 'review' | 'sending' | 'success' | 'error'; - -export default function SendPage() { - const router = useRouter(); - const user = useAuthStore((state) => state.user); - const wallets = useAuthStore((state) => state.wallets); - const { portfolio } = useBalances(); - const { data: gasPriceData } = useGasPrice(); - const gas = useGasSettings(gasPriceData); - - const [chain, setChain] = useState('ETH'); - const [token, setToken] = useState(getDefaultToken('ETH')); - const [recipient, setRecipient] = useState(''); - const [amount, setAmount] = useState(''); - const [confirmed, setConfirmed] = useState(false); - const [status, setStatus] = useState('idle'); - const [error, setError] = useState(null); - const [result, setResult] = useState(null); - const [scannerOpen, setScannerOpen] = useState(false); - - useEffect(() => { - if (!user) router.push('/login'); - }, [user, router]); - - // Reset token on chain change - useEffect(() => { - setToken(getDefaultToken(chain)); - setRecipient(''); - setAmount(''); - setConfirmed(false); - setStatus('idle'); - setError(null); - setResult(null); - }, [chain]); - - const wallet = useMemo( - () => wallets.find((w) => w.chain === SEND_CHAINS[chain].walletChain), - [wallets, chain], - ); - - const fromAddress = wallet?.address ?? ''; - - // Get available balance for the selected token - const availableBalance = useMemo(() => { - if (!portfolio?.chains) return null; - const chainBalance: ChainBalance | undefined = portfolio.chains.find( - (c) => c.chain === SEND_CHAINS[chain].walletChain, - ); - if (!chainBalance) return null; - const tokenBalance = chainBalance.tokens.find((t) => t.symbol === token); - return tokenBalance?.balanceFormatted ?? null; - }, [portfolio, chain, token]); - - // Validate address - const addressValidation = useMemo(() => { - if (!recipient.trim()) return null; - return validateAddress(chain, recipient); - }, [chain, recipient]); - - // Handle QR scan - const handleScan = useCallback((results: Array<{ rawValue: string }>) => { - if (!results.length) return; - const raw = results[0].rawValue; - if (!raw) return; - - const parsed = parseQrUri(raw); - setScannerOpen(false); - - if (parsed.chain) { - setChain(parsed.chain); - // Wait for chain useEffect, then set token/recipient/amount - setTimeout(() => { - if (parsed.token) setToken(parsed.token); - if (parsed.address) setRecipient(parsed.address); - if (parsed.amount) setAmount(parsed.amount); - }, 50); - } else if (parsed.address) { - setRecipient(parsed.address); - if (parsed.amount) setAmount(parsed.amount); - } - }, []); - - const handleReview = () => { - setError(null); - - if (!recipient.trim()) { - setError('Recipient address is required'); - return; - } - - const validation = validateAddress(chain, recipient); - if (!validation.valid) { - setError(validation.error || 'Invalid address'); - return; - } - - if (!amount || Number(amount) <= 0) { - setError('Enter a valid amount'); - return; - } - - setStatus('review'); - }; - - const handleSend = async () => { - if (!wallet) { - setError('Wallet not found for this chain'); - return; - } - - setStatus('sending'); - setError(null); - - try { - const sendResult = await executeSend({ - chain, - token, - toAddress: recipient.trim(), - amount, - privateKey: wallet.privateKey, - fromAddress, - maxFeeGwei: chain === 'ETH' ? gas.effectiveMaxFee : null, - priorityFeeGwei: chain === 'ETH' ? gas.effectivePriorityFee : null, - }); - - setResult(sendResult); - setStatus('success'); - } catch (err: any) { - setError(err.message || 'Transaction failed'); - setStatus('error'); - } - }; - - const handleReset = () => { - setRecipient(''); - setAmount(''); - setConfirmed(false); - setStatus('idle'); - setError(null); - setResult(null); - }; - - const handleMax = () => { - if (availableBalance) { - setAmount(availableBalance); - } - }; - - if (!user) return null; - - const tokenOptions = getTokenOptions(chain); - - return ( -
-
-

Send

-
- Receive - Dashboard -
-
- - {/* QR Scanner Modal */} - {scannerOpen && ( -
-
-
-

Scan QR Code

- -
-
- { - console.error('QR scanner error:', err); - setScannerOpen(false); - }} - formats={['qr_code']} - styles={{ container: { width: '100%' } }} - /> -
-

- Point your camera at a QR code to auto-fill send details -

-
-
- )} - - {/* Success state */} - {status === 'success' && result && ( -
-

Transaction Sent!

-

- TX Hash: {result.hash} -

- - View on Explorer - -
- -
-
- )} - - {/* Form (hidden during success) */} - {status !== 'success' && ( - <> - {/* Chain selector */} -
- - -
- - {/* Token selector */} -
- - - {availableBalance !== null && ( -

- Available: {availableBalance} {token} -

- )} -
- - {/* Recipient address */} -
- -
- setRecipient(e.target.value)} - style={{ ...inputStyle, flex: 1 }} - disabled={status === 'sending'} - /> - -
- {addressValidation && !addressValidation.valid && ( -

{addressValidation.error}

- )} - {addressValidation?.valid && ( -

Valid address

- )} -
- - {/* Amount */} -
- -
- { - const v = e.target.value; - if (/^\d*\.?\d*$/.test(v)) setAmount(v); - }} - style={{ ...inputStyle, flex: 1 }} - disabled={status === 'sending'} - /> - {availableBalance !== null && ( - - )} -
-
- - {/* Gas settings (ETH only) */} - {chain === 'ETH' && ( -
- -
- {GAS_MODES.map((mode) => ( - - ))} -
- {gas.gasMode === 'custom' && ( - gas.setCustomGwei(e.target.value)} - style={inputStyle} - /> - )} -

- Estimated gas: {gas.displayGwei} -

-
- )} - - {/* Fee info for non-ETH chains */} - {chain !== 'ETH' && ( -
-

- {chain === 'SOL' && 'Fee: Auto (~0.000005 SOL)'} - {chain === 'TRX' && 'Fee: Auto (Energy/Bandwidth)'} - {chain === 'BTC' && 'Fee: Auto (market rate sat/vB)'} -

-
- )} - -

- Platform fee: 0.7% per transaction -

- - {/* Review section */} - {status === 'review' && ( -
-

Review Transaction

-
- From: - {fromAddress} -
-
- To: - {recipient} -
-
- Amount: - {amount} {token} -
-
- Network: - {SEND_CHAINS[chain].label} -
- -
- -
- -
- - -
-
- )} - - {/* Sending state */} - {status === 'sending' && ( -
-

Sending transaction...

-

Please wait, do not close this page.

-
- )} - - {/* Error display */} - {error && ( -

{error}

- )} - - {/* Action buttons */} - {(status === 'idle' || status === 'error') && ( - - )} - - )} -
- ); -} - -// ─── Styles ─── - -const labelStyle: React.CSSProperties = { - display: 'block', - marginBottom: 4, - fontSize: 13, - fontWeight: 600, -}; - -const selectStyle: React.CSSProperties = { - width: '100%', - padding: '8px 12px', - border: '1px solid #ccc', - borderRadius: 4, - fontSize: 14, -}; - -const inputStyle: React.CSSProperties = { - width: '100%', - padding: '8px 12px', - border: '1px solid #ccc', - borderRadius: 4, - fontSize: 14, - boxSizing: 'border-box', -}; - -const navButtonStyle: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - justifyContent: 'center', - padding: '6px 12px', - border: '1px solid #ccc', - borderRadius: 4, - textDecoration: 'none', - color: 'inherit', - cursor: 'pointer', - background: '#fff', -}; - -const primaryButtonStyle: React.CSSProperties = { - width: '100%', - padding: '10px 16px', - border: '1px solid #333', - borderRadius: 4, - cursor: 'pointer', - background: '#333', - color: '#fff', - fontSize: 14, - fontWeight: 600, -}; - -const reviewRowStyle: React.CSSProperties = { - display: 'flex', - justifyContent: 'space-between', - alignItems: 'flex-start', - gap: 12, - padding: '6px 0', - borderBottom: '1px solid #f0f0f0', -}; - -const overlayStyle: React.CSSProperties = { - position: 'fixed', - top: 0, - left: 0, - right: 0, - bottom: 0, - background: 'rgba(0,0,0,0.6)', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - zIndex: 1000, -}; - -const modalStyle: React.CSSProperties = { - background: '#fff', - borderRadius: 12, - padding: 20, - maxWidth: 440, - width: '90%', -}; diff --git a/apps/web/src/app/settings/page.tsx b/apps/web/src/app/settings/page.tsx deleted file mode 100644 index 0c41b29..0000000 --- a/apps/web/src/app/settings/page.tsx +++ /dev/null @@ -1,94 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; -import { useRouter } from 'next/navigation'; -import Link from 'next/link'; -import { useAuthStore } from '@/store/auth-store'; -import { SeedPhraseModal } from '@/components/SeedPhraseModal'; - -export default function SettingsPage() { - const router = useRouter(); - const { user } = useAuthStore(); - const [showSeedModal, setShowSeedModal] = useState(false); - - useEffect(() => { - if (!user) { - router.push('/login'); - } - }, [user, router]); - - if (!user) return null; - - return ( -
-
-

Settings

- - Dashboard - -
- - {/* Security Section */} -
-

Security

- -
-
-

Seed Phrase

-

- View your 12-word recovery phrase. Requires password verification. -

-
- -
-
- - {/* Account Section */} -
-

Account

- -
-

Email

-

{user.email}

-
-
- - setShowSeedModal(false)} - /> -
- ); -} - -// ── Styles ── - -const navButtonStyle: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - justifyContent: 'center', - padding: '6px 12px', - border: '1px solid #ccc', - borderRadius: 4, - textDecoration: 'none', - color: 'inherit', - cursor: 'pointer', - background: '#fff', -}; - -const primaryButtonStyle: React.CSSProperties = { - padding: '8px 16px', - border: '1px solid #333', - borderRadius: 4, - cursor: 'pointer', - background: '#333', - color: '#fff', - fontSize: 14, - fontWeight: 600, - whiteSpace: 'nowrap', -}; diff --git a/apps/web/src/app/swap/page.tsx b/apps/web/src/app/swap/page.tsx deleted file mode 100644 index c66fdb4..0000000 --- a/apps/web/src/app/swap/page.tsx +++ /dev/null @@ -1,333 +0,0 @@ -'use client'; - -import { useEffect, useMemo, useState } from 'react'; -import Link from 'next/link'; -import { useRouter } from 'next/navigation'; -import { - type SwapChain, - SWAP_TOKEN_OPTIONS_BY_CHAIN, - CHAIN_DEFAULT_TOKENS, - getSlippageBpsForChain, - getExplorerTxUrl, -} from '@/lib/swap/constants'; -import { useSwap, type MultiChainSwapRequest } from '@/hooks/useSwap'; -import { useGasPrice } from '@/hooks/useGasPrice'; -import { useGasSettings, type GasMode } from '@/hooks/useGasSettings'; -import { useAuthStore } from '@/store/auth-store'; - -const GAS_MODE_LABELS: Record = { - slow: 'Slow', - normal: 'Normal', - fast: 'Fast', - custom: 'Custom', -}; - -const GAS_MODES: GasMode[] = ['slow', 'normal', 'fast', 'custom']; -const CHAINS: SwapChain[] = ['ETH', 'SOL', 'TRX', 'BSC']; - -export default function SwapPage() { - const router = useRouter(); - const user = useAuthStore((state) => state.user); - const { data: gasPriceData, loading: gasLoading } = useGasPrice(); - const gas = useGasSettings(gasPriceData); - const { - status, - quote, - error, - txHash, - approvalHashes, - liveEstimate, - chain, - setChain, - fetchQuote, - submitSwap, - resetSwap, - estimateOutput, - } = useSwap(); - - const tokenOptions = SWAP_TOKEN_OPTIONS_BY_CHAIN[chain]; - const defaults = CHAIN_DEFAULT_TOKENS[chain]; - const [fromSymbol, setFromSymbol] = useState(defaults.from); - const [toSymbol, setToSymbol] = useState(defaults.to); - const [amount, setAmount] = useState(''); - const [confirmed, setConfirmed] = useState(false); - - const slippageBps = useMemo(() => getSlippageBpsForChain(chain, fromSymbol, toSymbol), [chain, fromSymbol, toSymbol]); - const slippagePercent = (slippageBps / 100).toFixed(2); - - const request = useMemo( - () => ({ - chain, - fromSymbol, - toSymbol, - amount, - slippageBps, - }), - [chain, amount, fromSymbol, slippageBps, toSymbol] - ); - - useEffect(() => { - if (!user) { - router.push('/login'); - } - }, [router, user]); - - useEffect(() => { - estimateOutput(request); - }, [estimateOutput, request]); - - if (!user) { - return null; - } - - const canQuote = fromSymbol !== toSymbol && Number(amount) > 0 && request.slippageBps > 0; - const canSwap = !!quote && confirmed && status !== 'approving' && status !== 'swapping' && status !== 'quoting'; - - const handleChainChange = (newChain: SwapChain) => { - setChain(newChain); - const newDefaults = CHAIN_DEFAULT_TOKENS[newChain]; - setFromSymbol(newDefaults.from); - setToSymbol(newDefaults.to); - setAmount(''); - setConfirmed(false); - resetSwap(); - }; - - const handleQuote = async () => { - setConfirmed(false); - await fetchQuote(request); - }; - - const handleSwap = async () => { - await submitSwap(request, gas.effectiveMaxFee, gas.effectivePriorityFee); - }; - - const handleFieldReset = () => { - setConfirmed(false); - resetSwap(); - }; - - const tierGwei = (mode: GasMode): string => { - if (mode === 'custom') return ''; - if (!gasPriceData) return '...'; - const v = gasPriceData[mode].maxFeePerGas; - if (v >= 1) return v.toFixed(2); - const s = v.toFixed(4); - return s.replace(/0+$/, '').replace(/\.$/, ''); - }; - - return ( -
-
-

Swap

- - Back to Dashboard - -
- -
- {/* Chain selector */} -
- {CHAINS.map((c) => ( - - ))} -
- -
- - -
- -
- - -
- -
- - { setAmount(event.target.value); handleFieldReset(); }} type="number" min="0" step="any" style={inputStyle} /> - {liveEstimate && fromSymbol !== toSymbol && ( -

- {liveEstimate.loading ? '...' : `~${liveEstimate.amountOut} ${toSymbol}`} -

- )} -
- - {/* Gas speed — only for ETH */} - {chain === 'ETH' && ( -
- -
- {GAS_MODES.map((mode) => ( - - ))} -
- {gas.gasMode === 'custom' && ( - gas.setCustomGwei(event.target.value)} - type="number" - min="0" - step="0.01" - placeholder="Enter gwei" - style={{ ...inputStyle, marginTop: 6 }} - /> - )} -

- Effective: {gas.displayGwei} -

-
- )} - - {/* Fee info for non-ETH */} - {chain === 'SOL' && ( -

- Fee: Auto (Jupiter Priority) -

- )} - {chain === 'TRX' && ( -

- Fee: Auto (Energy/Bandwidth) -

- )} - {chain === 'BSC' && ( -

- Fee: 0.055 gwei (BSC fixed) -

- )} - -

- Slippage: {slippagePercent}% (auto) -

-

- Platform fee: 0.7% per swap -

- - {fromSymbol === toSymbol && ( -

From and To tokens must be different.

- )} - - -
- - {quote && ( -
-

Review

-

Expected output: {quote.amountOutFormatted} {toSymbol}

-

Minimum output after slippage: {quote.minimumAmountOutFormatted} {toSymbol}

- {'executionPrice' in quote &&

Execution price: {quote.executionPrice}

} - {'priceImpact' in quote &&

Price impact: {quote.priceImpact}%

} - {'routeSymbols' in quote &&

Route: {quote.routeSymbols.join(' -> ')}

} - {'routeFees' in quote &&

Pool fees: {quote.routeFees.join(' / ')}

} - {'routeLabels' in quote && (quote as any).routeLabels?.length > 0 && ( -

Route: {(quote as any).routeLabels.join(' → ')}

- )} - {chain === 'ETH' &&

Gas: {gas.displayGwei} ({GAS_MODE_LABELS[gas.gasMode]})

} -

Slippage: {slippagePercent}%

- - - - -
- )} - - {(approvalHashes.length > 0 || txHash) && ( -
-

Transaction Status

- {approvalHashes.map((hash) => ( -

- Approval tx:{' '} - - {hash.slice(0, 16)}... - -

- ))} - {txHash && ( -

- Swap tx:{' '} - - {txHash.slice(0, 16)}... - -

- )} -
- )} - - {(error || status === 'error') && ( -

- {error ?? 'Swap failed'} -

- )} -
- ); -} - -const fieldGroupStyle: React.CSSProperties = { - display: 'flex', - flexDirection: 'column', - gap: 6, - marginBottom: 12, -}; - -const inputStyle: React.CSSProperties = { - width: '100%', - padding: 8, -}; - -const navButtonStyle: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - justifyContent: 'center', - padding: '6px 12px', - border: '1px solid #ccc', - borderRadius: 4, -}; diff --git a/apps/web/src/components/SeedPhraseModal.tsx b/apps/web/src/components/SeedPhraseModal.tsx deleted file mode 100644 index 538537c..0000000 --- a/apps/web/src/components/SeedPhraseModal.tsx +++ /dev/null @@ -1,283 +0,0 @@ -'use client'; - -import { useState, useEffect, useCallback, useRef } from 'react'; -import { walletApi } from '@/lib/api'; -import { decryptVault } from '@/lib/crypto/vault'; - -interface SeedPhraseModalProps { - isOpen: boolean; - onClose: () => void; -} - -const AUTO_HIDE_SECONDS = 60; -const CLIPBOARD_CLEAR_SECONDS = 30; - -export function SeedPhraseModal({ isOpen, onClose }: SeedPhraseModalProps) { - const [password, setPassword] = useState(''); - const [mnemonic, setMnemonic] = useState(null); - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [countdown, setCountdown] = useState(AUTO_HIDE_SECONDS); - const [revealed, setRevealed] = useState(false); - const timerRef = useRef(null); - const clipboardTimerRef = useRef(null); - - const clearSensitiveData = useCallback(() => { - setPassword(''); - setMnemonic(null); - setError(null); - setRevealed(false); - setCountdown(AUTO_HIDE_SECONDS); - if (timerRef.current) { - clearInterval(timerRef.current); - timerRef.current = null; - } - if (clipboardTimerRef.current) { - clearTimeout(clipboardTimerRef.current); - clipboardTimerRef.current = null; - } - }, []); - - const handleClose = useCallback(() => { - clearSensitiveData(); - onClose(); - }, [clearSensitiveData, onClose]); - - // Auto-hide countdown - useEffect(() => { - if (!mnemonic) return; - - setCountdown(AUTO_HIDE_SECONDS); - timerRef.current = setInterval(() => { - setCountdown((prev) => { - if (prev <= 1) { - handleClose(); - return 0; - } - return prev - 1; - }); - }, 1000); - - return () => { - if (timerRef.current) { - clearInterval(timerRef.current); - timerRef.current = null; - } - }; - }, [mnemonic, handleClose]); - - // Cleanup on unmount - useEffect(() => { - return () => { - clearSensitiveData(); - }; - }, [clearSensitiveData]); - - if (!isOpen) return null; - - const handleVerify = async () => { - setError(null); - setLoading(true); - - try { - // Get vault data from backend - const result = await walletApi.unlock(); - - // Attempt client-side decryption with password only - const decrypted = await decryptVault( - result.encryptedVault, - result.vaultSalt, - password, - ); - - setMnemonic(decrypted); - } catch (err: any) { - const msg = err?.message || ''; - if (msg.includes('Too many attempts')) { - setError('Too many attempts. Try again in 15 minutes.'); - } else { - setError('Wrong password'); - } - } finally { - setLoading(false); - } - }; - - const handleCopy = async () => { - if (!mnemonic) return; - try { - await navigator.clipboard.writeText(mnemonic); - - clipboardTimerRef.current = setTimeout(() => { - try { - void navigator.clipboard.writeText(''); - } catch { - // Ignore - } - }, CLIPBOARD_CLEAR_SECONDS * 1000); - } catch { - // Clipboard API not available - } - }; - - const words = mnemonic?.split(' ') || []; - - return ( -
-
e.stopPropagation()}> -
-

- {mnemonic ? 'Seed Phrase' : 'Verify Identity'} -

- -
- - {!mnemonic ? ( -
-

- Enter your password to view your seed phrase. -

- -
- - setPassword(e.target.value)} - style={inputStyle} - placeholder="Enter password" - autoComplete="off" - /> -
- - {error && ( -

{error}

- )} - - -
- ) : ( -
-
-

- Auto-hide in {countdown}s -

-
- - -
-
- -
- {words.map((word, i) => ( -
- {i + 1}.{' '} - - {word} - -
- ))} -
- -

- Clipboard will be cleared in {CLIPBOARD_CLEAR_SECONDS}s after copying. -

-
- )} -
-
- ); -} - -// -- Styles -- - -const overlayStyle: React.CSSProperties = { - position: 'fixed', - top: 0, - left: 0, - right: 0, - bottom: 0, - background: 'rgba(0,0,0,0.6)', - display: 'flex', - justifyContent: 'center', - alignItems: 'center', - zIndex: 1000, -}; - -const modalStyle: React.CSSProperties = { - background: '#fff', - borderRadius: 12, - padding: 20, - maxWidth: 480, - width: '90%', -}; - -const inputStyle: React.CSSProperties = { - width: '100%', - padding: '8px 12px', - border: '1px solid #ccc', - borderRadius: 4, - fontSize: 14, - boxSizing: 'border-box', -}; - -const navButtonStyle: React.CSSProperties = { - display: 'inline-flex', - alignItems: 'center', - justifyContent: 'center', - padding: '6px 12px', - border: '1px solid #ccc', - borderRadius: 4, - cursor: 'pointer', - background: '#fff', - fontSize: 13, -}; - -const primaryButtonStyle: React.CSSProperties = { - padding: '10px 16px', - border: '1px solid #333', - borderRadius: 4, - cursor: 'pointer', - background: '#333', - color: '#fff', - fontSize: 14, - fontWeight: 600, -}; diff --git a/apps/web/src/hooks/useBalances.ts b/apps/web/src/hooks/useBalances.ts deleted file mode 100644 index 5709ad2..0000000 --- a/apps/web/src/hooks/useBalances.ts +++ /dev/null @@ -1,52 +0,0 @@ -'use client'; - -import { useCallback, useEffect } from 'react'; -import { useAuthStore } from '@/store/auth-store'; -import { useBalanceStore } from '@/store/balance-store'; - -const BALANCE_REFRESH_INTERVAL_MS = 30_000; - -export function useBalances() { - const user = useAuthStore((state) => state.user); - const wallets = useAuthStore((state) => state.wallets); - const portfolio = useBalanceStore((state) => state.portfolio); - const loading = useBalanceStore((state) => state.loading); - const refreshing = useBalanceStore((state) => state.refreshing); - const error = useBalanceStore((state) => state.error); - const fetchBalances = useBalanceStore((state) => state.fetchBalances); - const clearBalances = useBalanceStore((state) => state.clearBalances); - - const refresh = useCallback(async () => { - if (!user || !wallets.length) { - clearBalances(); - return; - } - - await fetchBalances(wallets); - }, [clearBalances, fetchBalances, user, wallets]); - - useEffect(() => { - if (!user || !wallets.length) { - clearBalances(); - return; - } - - void fetchBalances(wallets); - - const intervalId = window.setInterval(() => { - void fetchBalances(wallets); - }, BALANCE_REFRESH_INTERVAL_MS); - - return () => { - window.clearInterval(intervalId); - }; - }, [clearBalances, fetchBalances, user, wallets]); - - return { - portfolio, - loading, - refreshing, - error, - refresh, - }; -} diff --git a/apps/web/src/hooks/useBridge.ts b/apps/web/src/hooks/useBridge.ts deleted file mode 100644 index be1355d..0000000 --- a/apps/web/src/hooks/useBridge.ts +++ /dev/null @@ -1,191 +0,0 @@ -'use client'; - -import { useEffect, useMemo, useRef, useState } from 'react'; -import { executeBridge, type ExecuteBridgeResult } from '@/lib/bridge/execute'; -import { getBridgeQuote, type BridgeQuoteResult } from '@/lib/bridge/quote'; -import { getBridgeStatus, isBridgeTerminalStatus, type BridgeStatusResult } from '@/lib/bridge/status'; -import { - BRIDGE_CHAINS, - getTokenConfig, - type BridgeChainKey, -} from '@/lib/bridge/constants'; -import { useAuthStore } from '@/store/auth-store'; - -export type BridgeStatus = 'idle' | 'quoting' | 'quoted' | 'executing' | 'monitoring' | 'success' | 'error'; - -const BRIDGE_POLL_INTERVAL_MS = 1_000; - -export interface BridgeRequestParams { - sourceChain: BridgeChainKey; - sourceToken: string; - destChain: BridgeChainKey; - destToken: string; - amount: string; -} - -export function useBridge() { - const wallets = useAuthStore((state) => state.wallets); - - const [status, setStatus] = useState('idle'); - const [quote, setQuote] = useState(null); - const [bridgeStatus, setBridgeStatus] = useState(null); - const [requestId, setRequestId] = useState(null); - const [txHashes, setTxHashes] = useState([]); - const [error, setError] = useState(null); - const [sourceChain, setSourceChain] = useState('ETH'); - const pollTimeoutRef = useRef(null); - - useEffect(() => { - return () => { - if (pollTimeoutRef.current) { - window.clearTimeout(pollTimeoutRef.current); - } - }; - }, []); - - const sourceWallet = useMemo( - () => wallets.find((w) => w.chain === BRIDGE_CHAINS[sourceChain].walletChain) ?? null, - [wallets, sourceChain], - ); - - function getWalletAddress(chainKey: BridgeChainKey): string | null { - const chain = BRIDGE_CHAINS[chainKey]; - const wallet = wallets.find((w) => w.chain === chain.walletChain); - return wallet?.address ?? null; - } - - const fetchQuote = async (request: BridgeRequestParams) => { - const userAddress = getWalletAddress(request.sourceChain); - if (!userAddress) { - throw new Error(`${request.sourceChain} wallet is not available`); - } - - const recipientAddress = getWalletAddress(request.destChain); - if (!recipientAddress) { - throw new Error(`${request.destChain} wallet is not available`); - } - - setStatus('quoting'); - setError(null); - setBridgeStatus(null); - setRequestId(null); - setTxHashes([]); - - try { - const nextQuote = await getBridgeQuote({ - ...request, - userAddress, - recipientAddress, - }); - setQuote(nextQuote); - setStatus('quoted'); - return nextQuote; - } catch (nextError) { - setQuote(null); - setStatus('error'); - setError(getErrorMessage(nextError)); - throw nextError; - } - }; - - const submitBridge = async ( - request: BridgeRequestParams, - maxFeeGwei?: string | null, - priorityFeeGwei?: string | null, - ) => { - if (!sourceWallet?.privateKey) { - throw new Error(`${request.sourceChain} private key is not available`); - } - - if (!quote) { - throw new Error('Get a bridge quote before executing'); - } - - setStatus('executing'); - setError(null); - - try { - const tokenConfig = getTokenConfig(request.sourceChain, request.sourceToken); - const execution = await executeBridge({ - sourceChain: request.sourceChain, - sourceToken: request.sourceToken, - originalAmount: request.amount, - sourceTokenDecimals: tokenConfig.decimals, - sourceTokenAddress: tokenConfig.address, - privateKey: sourceWallet.privateKey, - quote: quote.quote, - maxFeeGwei, - priorityFeeGwei, - }); - - setTxHashes(execution.txHashes); - if (!execution.requestId) { - throw new Error('Relay request ID was not returned'); - } - - setRequestId(execution.requestId); - setStatus('monitoring'); - await pollBridgeStatus(execution.requestId); - return execution; - } catch (nextError) { - setStatus('error'); - setError(getErrorMessage(nextError)); - throw nextError; - } - }; - - const resetBridge = () => { - if (pollTimeoutRef.current) { - window.clearTimeout(pollTimeoutRef.current); - pollTimeoutRef.current = null; - } - - setStatus('idle'); - setQuote(null); - setBridgeStatus(null); - setRequestId(null); - setTxHashes([]); - setError(null); - }; - - const pollBridgeStatus = async (nextRequestId: string): Promise => { - const nextStatus = await getBridgeStatus(nextRequestId); - setBridgeStatus(nextStatus); - - if (isBridgeTerminalStatus(nextStatus.status)) { - if (nextStatus.status === 'success') { - setStatus('success'); - } else { - setStatus('error'); - setError(nextStatus.details || `Bridge finished with status: ${nextStatus.status}`); - } - return nextStatus; - } - - await new Promise((resolve) => { - pollTimeoutRef.current = window.setTimeout(() => resolve(), BRIDGE_POLL_INTERVAL_MS); - }); - - return pollBridgeStatus(nextRequestId); - }; - - return { - status, - quote, - bridgeStatus, - requestId, - txHashes, - error, - sourceChain, - setSourceChain, - sourceWallet, - fetchQuote, - submitBridge, - resetBridge, - }; -} - -function getErrorMessage(error: unknown): string { - if (error instanceof Error) return error.message; - return 'Bridge request failed'; -} diff --git a/apps/web/src/hooks/useGasPrice.ts b/apps/web/src/hooks/useGasPrice.ts deleted file mode 100644 index 2394f4d..0000000 --- a/apps/web/src/hooks/useGasPrice.ts +++ /dev/null @@ -1,36 +0,0 @@ -'use client'; - -import { useEffect, useState } from 'react'; -import { fetchGasPrices, type GasPriceData } from '@/lib/gas-price'; - -const GAS_REFRESH_INTERVAL_MS = 30_000; - -export function useGasPrice() { - const [data, setData] = useState(null); - const [loading, setLoading] = useState(true); - - useEffect(() => { - let cancelled = false; - - const refresh = async () => { - try { - const next = await fetchGasPrices(); - if (!cancelled) setData(next); - } catch { - /* keep last known data */ - } finally { - if (!cancelled) setLoading(false); - } - }; - - void refresh(); - const intervalId = setInterval(refresh, GAS_REFRESH_INTERVAL_MS); - - return () => { - cancelled = true; - clearInterval(intervalId); - }; - }, []); - - return { data, loading }; -} diff --git a/apps/web/src/hooks/useGasSettings.ts b/apps/web/src/hooks/useGasSettings.ts deleted file mode 100644 index 28ed036..0000000 --- a/apps/web/src/hooks/useGasSettings.ts +++ /dev/null @@ -1,64 +0,0 @@ -'use client'; - -import { useMemo, useState } from 'react'; -import type { GasPriceData } from '@/lib/gas-price'; - -export type GasMode = 'slow' | 'normal' | 'fast' | 'custom'; - -export interface GasSettings { - gasMode: GasMode; - setGasMode: (mode: GasMode) => void; - customGwei: string; - setCustomGwei: (value: string) => void; - effectiveMaxFee: string | null; - effectivePriorityFee: string | null; - displayGwei: string; -} - -function fmt(n: number): string { - if (n >= 1) return n.toFixed(2); - const s = n.toFixed(6); - return s.replace(/0+$/, '').replace(/\.$/, ''); -} - -export function useGasSettings(gasPriceData: GasPriceData | null): GasSettings { - const [gasMode, setGasMode] = useState('normal'); - const [customGwei, setCustomGwei] = useState(''); - - const { effectiveMaxFee, effectivePriorityFee, displayGwei } = useMemo(() => { - if (gasMode === 'custom') { - const v = customGwei.trim(); - if (!v || Number(v) <= 0) { - return { effectiveMaxFee: null, effectivePriorityFee: null, displayGwei: '-' }; - } - const base = Number(v); - const priority = Math.max(0.01, base * 0.1); - return { - effectiveMaxFee: v, - effectivePriorityFee: fmt(priority), - displayGwei: `${v} gwei`, - }; - } - - if (!gasPriceData) { - return { effectiveMaxFee: null, effectivePriorityFee: null, displayGwei: '...' }; - } - - const tier = gasPriceData[gasMode]; - return { - effectiveMaxFee: fmt(tier.maxFeePerGas), - effectivePriorityFee: fmt(tier.maxPriorityFeePerGas), - displayGwei: `${fmt(tier.maxFeePerGas)} gwei`, - }; - }, [gasMode, customGwei, gasPriceData]); - - return { - gasMode, - setGasMode, - customGwei, - setCustomGwei, - effectiveMaxFee, - effectivePriorityFee, - displayGwei, - }; -} diff --git a/apps/web/src/hooks/useSwap.ts b/apps/web/src/hooks/useSwap.ts deleted file mode 100644 index 68f6109..0000000 --- a/apps/web/src/hooks/useSwap.ts +++ /dev/null @@ -1,274 +0,0 @@ -'use client'; - -import { useCallback, useMemo, useRef, useState } from 'react'; -import { ensureSwapApproval } from '@/lib/swap/approve'; -import { executeSwap } from '@/lib/swap/execute'; -import { getSwapQuote, type SwapQuoteResult } from '@/lib/swap/quote'; -import { getSolSwapQuote, type SolSwapQuoteResult } from '@/lib/swap/sol/quote'; -import { executeSolSwap } from '@/lib/swap/sol/execute'; -import { getTrxSwapQuote, type TrxSwapQuoteResult } from '@/lib/swap/trx/quote'; -import { executeTrxSwap } from '@/lib/swap/trx/execute'; -import { getBscSwapQuote, type BscSwapQuoteResult } from '@/lib/swap/bsc/quote'; -import { executeBscSwap } from '@/lib/swap/bsc/execute'; -import { mapSwapError } from '@/lib/swap/errors'; -import type { SwapQuoteRequest } from '@/lib/swap/constants'; -import { type SwapChain, getSlippageBpsForChain } from '@/lib/swap/constants'; -import { useAuthStore } from '@/store/auth-store'; - -const ESTIMATE_DEBOUNCE_MS = 500; - -export type SwapStatus = 'idle' | 'quoting' | 'quoted' | 'approving' | 'swapping' | 'success' | 'error'; - -export interface LiveEstimate { - amountOut: string; - loading: boolean; -} - -export type AnyQuoteResult = SwapQuoteResult | SolSwapQuoteResult | TrxSwapQuoteResult | BscSwapQuoteResult; - -export interface MultiChainSwapRequest { - chain: SwapChain; - fromSymbol: string; - toSymbol: string; - amount: string; - slippageBps: number; -} - -export function useSwap() { - const wallets = useAuthStore((state) => state.wallets); - const [status, setStatus] = useState('idle'); - const [quote, setQuote] = useState(null); - const [error, setError] = useState(null); - const [txHash, setTxHash] = useState(null); - const [approvalHashes, setApprovalHashes] = useState([]); - const [liveEstimate, setLiveEstimate] = useState(null); - const [chain, setChain] = useState('ETH'); - const estimateTimeoutRef = useRef | null>(null); - - const currentWallet = useMemo( - () => wallets.find((w) => w.chain === chain) ?? null, - [wallets, chain] - ); - - const fetchQuote = async (request: MultiChainSwapRequest) => { - setStatus('quoting'); - setError(null); - setTxHash(null); - setApprovalHashes([]); - - try { - const nextQuote = await fetchQuoteForChain(request); - setQuote(nextQuote); - setStatus('quoted'); - return nextQuote; - } catch (nextError) { - setQuote(null); - setStatus('error'); - setError(mapSwapError(request.chain, nextError)); - throw nextError; - } - }; - - const submitSwap = async ( - request: MultiChainSwapRequest, - maxFeeGwei?: string | null, - priorityFeeGwei?: string | null, - ) => { - if (!currentWallet?.privateKey) { - const walletError = new Error(`${request.chain} private key is not available`); - setStatus('error'); - setError(walletError.message); - throw walletError; - } - - if (!quote) { - const quoteError = new Error('Get a quote before swapping'); - setStatus('error'); - setError(quoteError.message); - throw quoteError; - } - - setError(null); - setApprovalHashes([]); - - try { - let result: { hash: string }; - - switch (request.chain) { - case 'ETH': { - // Existing ETH swap logic - if (request.fromSymbol !== 'ETH') { - setStatus('approving'); - const approvalResult = await ensureSwapApproval({ - privateKey: currentWallet.privateKey, - tokenSymbol: request.fromSymbol as any, - amount: request.amount, - maxFeeGwei, - priorityFeeGwei, - }); - setApprovalHashes(approvalResult.approvalHashes); - } - - setStatus('swapping'); - const ethQuote = quote as SwapQuoteResult; - result = await executeSwap({ - privateKey: currentWallet.privateKey, - request: request as SwapQuoteRequest, - quote: ethQuote, - maxFeeGwei, - priorityFeeGwei, - }); - break; - } - - case 'SOL': { - setStatus('swapping'); - const solQuote = quote as SolSwapQuoteResult; - result = await executeSolSwap({ - privateKeyHex: currentWallet.privateKey, - userPublicKey: currentWallet.address, - quoteResponse: solQuote.quoteResponse, - }); - break; - } - - case 'TRX': { - setStatus('swapping'); - const trxQuote = quote as TrxSwapQuoteResult; - const trxResult = await executeTrxSwap({ - privateKeyHex: currentWallet.privateKey, - from: request.fromSymbol, - to: request.toSymbol, - amount: trxQuote.amountInRaw, - amountOutMin: trxQuote.minimumAmountOutRaw, - userAddress: currentWallet.address, - }); - setApprovalHashes(trxResult.approvalHashes); - result = trxResult; - break; - } - - case 'BSC': { - setStatus('swapping'); - const bscQuote = quote as BscSwapQuoteResult; - const bscResult = await executeBscSwap({ - privateKeyHex: currentWallet.privateKey, - from: request.fromSymbol, - to: request.toSymbol, - amount: bscQuote.amountIn, - amountOutMin: bscQuote.minimumAmountOutRaw, - userAddress: currentWallet.address, - }); - setApprovalHashes(bscResult.approvalHashes); - result = bscResult; - break; - } - } - - setTxHash(result.hash); - setStatus('success'); - return result; - } catch (nextError) { - setStatus('error'); - setError(mapSwapError(request.chain, nextError)); - throw nextError; - } - }; - - const estimateOutput = useCallback((request: MultiChainSwapRequest) => { - if ( - request.fromSymbol === request.toSymbol || - !request.amount || - Number(request.amount) <= 0 || - request.slippageBps <= 0 - ) { - setLiveEstimate(null); - return; - } - - if (estimateTimeoutRef.current) { - clearTimeout(estimateTimeoutRef.current); - estimateTimeoutRef.current = null; - } - - setLiveEstimate({ amountOut: '', loading: true }); - - estimateTimeoutRef.current = setTimeout(async () => { - estimateTimeoutRef.current = null; - try { - const result = await fetchQuoteForChain(request); - setLiveEstimate({ - amountOut: result.amountOutFormatted, - loading: false, - }); - } catch { - setLiveEstimate(null); - } - }, ESTIMATE_DEBOUNCE_MS); - }, []); - - const resetSwap = useCallback(() => { - if (estimateTimeoutRef.current) { - clearTimeout(estimateTimeoutRef.current); - estimateTimeoutRef.current = null; - } - setStatus('idle'); - setQuote(null); - setError(null); - setTxHash(null); - setApprovalHashes([]); - setLiveEstimate(null); - }, []); - - return { - status, - quote, - error, - txHash, - approvalHashes, - liveEstimate, - chain, - setChain, - currentWallet, - fetchQuote, - submitSwap, - resetSwap, - estimateOutput, - }; -} - -async function fetchQuoteForChain(request: MultiChainSwapRequest): Promise { - switch (request.chain) { - case 'ETH': - return getSwapQuote({ - fromSymbol: request.fromSymbol as any, - toSymbol: request.toSymbol as any, - amount: request.amount, - slippageBps: request.slippageBps, - }); - - case 'SOL': - return getSolSwapQuote({ - fromSymbol: request.fromSymbol, - toSymbol: request.toSymbol, - amount: request.amount, - slippageBps: request.slippageBps, - }); - - case 'TRX': - return getTrxSwapQuote({ - fromSymbol: request.fromSymbol, - toSymbol: request.toSymbol, - amount: request.amount, - slippageBps: request.slippageBps, - }); - - case 'BSC': - return getBscSwapQuote({ - fromSymbol: request.fromSymbol, - toSymbol: request.toSymbol, - amount: request.amount, - slippageBps: request.slippageBps, - }); - } -} diff --git a/apps/web/src/lib/api.ts b/apps/web/src/lib/api.ts deleted file mode 100644 index de53839..0000000 --- a/apps/web/src/lib/api.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { webEnv } from './env'; - -const API_URL = webEnv.apiUrl; -const BITOK_BASE = process.env.NEXT_PUBLIC_BITOK_URL || 'http://localhost:8000'; - -let accessToken: string | null = null; - -export function setAccessToken(token: string | null) { - accessToken = token; -} - -export function getAccessToken() { - return accessToken; -} - -// ── BITOK auth calls (httpOnly cookies + access_token in body) ── - -async function bitokRequest(path: string, options: RequestInit = {}): Promise { - const headers: Record = { - 'Content-Type': 'application/json', - ...(options.headers as Record), - }; - - const res = await fetch(`${BITOK_BASE}${path}`, { - ...options, - headers, - credentials: 'include', - }); - - if (!res.ok) { - const body = await res.json().catch(() => ({})); - throw new Error(body.detail || body.error || `Request failed (${res.status})`); - } - - return res.json(); -} - -export const bitokAuth = { - registrationStart: (email: string) => - bitokRequest<{ success: boolean }>('/v1/auth/registration/start', { - method: 'POST', - body: JSON.stringify({ email }), - }), - - registrationComplete: (email: string, password: string, code: string) => - bitokRequest<{ id: string; email: string; access_token: string }>('/v1/auth/registration/complete', { - method: 'POST', - body: JSON.stringify({ email, password, code }), - }), - - loginStart: (email: string) => - bitokRequest<{ success: boolean }>('/v1/auth/login/start', { - method: 'POST', - body: JSON.stringify({ email }), - }), - - loginComplete: (email: string, password: string, code: string) => - bitokRequest<{ id: string; email: string; access_token: string }>('/v1/auth/login/complete', { - method: 'POST', - body: JSON.stringify({ email, password, code }), - }), - - refresh: () => - bitokRequest<{ result: boolean; access_token: string }>('/v1/jwt/refresh', { method: 'POST' }), - - logout: () => - bitokRequest<{ ok: boolean }>('/v1/auth/logout', { method: 'POST' }), -}; - -// ── Wallet API calls (uses Bearer token from BITOK) ── - -async function walletRequest(path: string, options: RequestInit = {}): Promise { - const headers: Record = { - 'Content-Type': 'application/json', - ...(options.headers as Record), - }; - - if (accessToken) { - headers['Authorization'] = `Bearer ${accessToken}`; - } - - const res = await fetch(`${API_URL}${path}`, { - ...options, - headers, - credentials: 'include', - }); - - const data = await res.json(); - - if (!data.success) { - throw new Error(data.error || 'Request failed'); - } - - return data.data; -} - -export interface WalletSetupPayload { - encryptedVault: string; - vaultSalt: string; - wallets: { chain: string; address: string; derivationPath: string }[]; -} - -export interface WalletUnlockResponse { - encryptedVault: string; - vaultSalt: string; - wallets: { chain: string; address: string; derivationPath: string }[]; - mnemonicShown: boolean; -} - -export const walletApi = { - setup: (data: WalletSetupPayload) => - walletRequest('/api/wallet/setup', { - method: 'POST', - body: JSON.stringify(data), - }), - - unlock: () => - walletRequest('/api/wallet/unlock'), - - getWallets: () => walletRequest('/api/wallets'), - - confirmMnemonic: () => - walletRequest('/api/wallet/confirm-mnemonic', { method: 'POST' }), -}; - -// ── Legacy api object (keep for components that still reference it) ── - -export const api = { - getWallets: () => walletRequest('/api/wallets'), - getVault: () => walletRequest('/api/vault'), - confirmMnemonic: () => - walletRequest('/api/wallet/confirm-mnemonic', { method: 'POST' }), -}; diff --git a/apps/web/src/lib/balances/bsc-balances.ts b/apps/web/src/lib/balances/bsc-balances.ts deleted file mode 100644 index 2451c79..0000000 --- a/apps/web/src/lib/balances/bsc-balances.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { ethers } from 'ethers'; -import { webEnv } from '@/lib/env'; -import type { ChainBalance, TokenBalance, TokenDefinition } from './types'; - -const BSC_CHAIN_ID = 56; - -const BSC_BALANCE_TIMEOUT_MS = 6_000; -const BSC_RPC_HEALTHCHECK_TIMEOUT_MS = 4_000; -const BEP20_ABI = ['function balanceOf(address owner) view returns (uint256)']; - -const BSC_RPC_CANDIDATES = dedupeUrls([ - webEnv.bscRpcUrl, - 'https://bsc-dataseed1.defibit.io', - 'https://bsc-dataseed1.ninicoin.io', - 'https://bsc-dataseed.binance.org', -]); - -const BSC_TOKENS: TokenDefinition[] = [ - { - chain: 'BSC', - symbol: 'BNB', - decimals: 18, - contractAddress: 'native', - coinGeckoId: 'binancecoin', - isNative: true, - }, - { - chain: 'BSC', - symbol: 'USDT', - decimals: 18, - contractAddress: '0x55d398326f99059fF775485246999027B3197955', - coinGeckoId: 'tether', - isNative: false, - }, - { - chain: 'BSC', - symbol: 'DOGE', - decimals: 8, - contractAddress: '0xbA2aE424d960c26247Dd6c32edC70B295c744C43', - coinGeckoId: 'dogecoin', - isNative: false, - }, -]; - -export async function fetchBscBalances(address: string): Promise { - let provider: ethers.providers.StaticJsonRpcProvider; - try { - provider = await getHealthyBscProvider(); - } catch (error) { - return { - chain: 'BSC', - address, - tokens: BSC_TOKENS.map(createEmptyTokenBalance), - totalUsd: null, - error: getErrorMessage(error), - }; - } - - const settled = await Promise.allSettled( - BSC_TOKENS.map(async (token) => readBscTokenBalance(provider, address, token)) - ); - - const tokens: TokenBalance[] = []; - const errors: Array<{ symbol: string; message: string }> = []; - - settled.forEach((result, index) => { - const token = BSC_TOKENS[index]; - - if (result.status === 'fulfilled') { - tokens.push(result.value); - return; - } - - tokens.push(createEmptyTokenBalance(token)); - errors.push({ symbol: token.symbol, message: getErrorMessage(result.reason) }); - }); - - const uniqueMessages = [...new Set(errors.map((item) => item.message))]; - const error = - errors.length === BSC_TOKENS.length && uniqueMessages.length === 1 - ? uniqueMessages[0] - : errors.length - ? errors.map((item) => `${item.symbol}: ${item.message}`).join(' | ') - : null; - - return { - chain: 'BSC', - address, - tokens, - totalUsd: null, - error, - }; -} - -async function getHealthyBscProvider(): Promise { - let lastError: unknown = new Error('No BSC RPC endpoints configured'); - for (const rpcUrl of BSC_RPC_CANDIDATES) { - const provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl, BSC_CHAIN_ID); - try { - await withTimeout( - provider.getBlockNumber(), - BSC_RPC_HEALTHCHECK_TIMEOUT_MS, - `BSC RPC health-check timed out for ${rpcUrl}` - ); - return provider; - } catch (error) { - lastError = error; - } - } - throw lastError; -} - -async function readBscTokenBalance(provider: ethers.providers.StaticJsonRpcProvider, address: string, token: TokenDefinition): Promise { - if (token.isNative) { - const balance = await withTimeout( - provider.getBalance(address), - BSC_BALANCE_TIMEOUT_MS, - 'BNB balance request timed out' - ); - - return { - ...token, - balanceRaw: balance.toString(), - balanceFormatted: ethers.utils.formatEther(balance), - priceUsd: null, - valueUsd: null, - }; - } - - const contract = new ethers.Contract(token.contractAddress, BEP20_ABI, provider); - const balance = (await withTimeout( - contract.balanceOf(address), - BSC_BALANCE_TIMEOUT_MS, - `${token.symbol} balance request timed out` - )) as ethers.BigNumber; - - return { - ...token, - balanceRaw: balance.toString(), - balanceFormatted: ethers.utils.formatUnits(balance, token.decimals), - priceUsd: null, - valueUsd: null, - }; -} - -function createEmptyTokenBalance(token: TokenDefinition): TokenBalance { - return { - ...token, - balanceRaw: '0', - balanceFormatted: '0', - priceUsd: null, - valueUsd: null, - }; -} - -function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs); - - promise - .then((value) => { - clearTimeout(timeoutId); - resolve(value); - }) - .catch((error) => { - clearTimeout(timeoutId); - reject(error); - }); - }); -} - -function getErrorMessage(error: unknown): string { - if (error instanceof Error && error.message) { - if (error.message.includes('Failed to fetch')) { - return 'BSC RPC is temporarily unavailable'; - } - return error.message; - } - - return 'Unable to load token balance'; -} - -function dedupeUrls(urls: string[]): string[] { - return [...new Set(urls.map((url) => url.trim()).filter(Boolean))]; -} diff --git a/apps/web/src/lib/balances/btc-balances.ts b/apps/web/src/lib/balances/btc-balances.ts deleted file mode 100644 index 4885e1a..0000000 --- a/apps/web/src/lib/balances/btc-balances.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { webEnv } from '@/lib/env'; -import type { ChainBalance, TokenDefinition } from './types'; - -const BTC_BALANCE_TIMEOUT_MS = 12_000; - -const BTC_TOKEN: TokenDefinition = { - chain: 'BTC', - symbol: 'BTC', - decimals: 8, - contractAddress: 'native', - coinGeckoId: 'bitcoin', - isNative: true, -}; - -interface BlockstreamAddressResponse { - chain_stats?: { - funded_txo_sum?: number; - spent_txo_sum?: number; - }; -} - -export async function fetchBtcBalances(address: string): Promise { - try { - const response = await fetchJsonWithTimeout( - `${webEnv.btcApiUrl}/address/${address}`, - BTC_BALANCE_TIMEOUT_MS - ); - - const funded = response.chain_stats?.funded_txo_sum ?? 0; - const spent = response.chain_stats?.spent_txo_sum ?? 0; - const sats = Math.max(funded - spent, 0); - - return { - chain: 'BTC', - address, - tokens: [ - { - ...BTC_TOKEN, - balanceRaw: sats.toString(), - balanceFormatted: formatFixedBalance(sats, BTC_TOKEN.decimals), - priceUsd: null, - valueUsd: null, - }, - ], - totalUsd: null, - error: null, - }; - } catch (error) { - return { - chain: 'BTC', - address, - tokens: [ - { - ...BTC_TOKEN, - balanceRaw: '0', - balanceFormatted: '0', - priceUsd: null, - valueUsd: null, - }, - ], - totalUsd: null, - error: getErrorMessage(error), - }; - } -} - -async function fetchJsonWithTimeout(url: string, timeoutMs: number): Promise { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeoutMs); - - try { - const response = await fetch(url, { - signal: controller.signal, - cache: 'no-store', - }); - - if (!response.ok) { - throw new Error(`BTC API returned ${response.status}`); - } - - return (await response.json()) as T; - } finally { - clearTimeout(timeoutId); - } -} - -function formatFixedBalance(rawValue: number, decimals: number): string { - if (rawValue === 0) { - return '0'; - } - - return (rawValue / 10 ** decimals).toString(); -} - -function getErrorMessage(error: unknown): string { - if (error instanceof Error) { - return error.message; - } - - return 'Unable to load BTC balance'; -} diff --git a/apps/web/src/lib/balances/eth-balances.ts b/apps/web/src/lib/balances/eth-balances.ts deleted file mode 100644 index 7724861..0000000 --- a/apps/web/src/lib/balances/eth-balances.ts +++ /dev/null @@ -1,257 +0,0 @@ -import { ethers } from 'ethers'; -import { webEnv } from '@/lib/env'; -import type { ChainBalance, TokenBalance, TokenDefinition } from './types'; - -const ETH_CHAIN_ID = 1; - -const ETH_BALANCE_TIMEOUT_MS = 6_000; -const ETH_RPC_HEALTHCHECK_TIMEOUT_MS = 4_000; -const ERC20_ABI = ['function balanceOf(address owner) view returns (uint256)']; - -const ETH_RPC_CANDIDATES = dedupeUrls([ - webEnv.ethRpcUrl, - 'https://ethereum-rpc.publicnode.com', - 'https://rpc.ankr.com/eth', - 'https://eth.llamarpc.com', -]); - -const ETH_TOKENS: TokenDefinition[] = [ - { - chain: 'ETH', - symbol: 'ETH', - decimals: 18, - contractAddress: 'native', - coinGeckoId: 'ethereum', - isNative: true, - }, - { - chain: 'ETH', - symbol: 'USDT', - decimals: 6, - contractAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', - coinGeckoId: 'tether', - isNative: false, - }, - { - chain: 'ETH', - symbol: 'USDC', - decimals: 6, - contractAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - coinGeckoId: 'usd-coin', - isNative: false, - }, - { - chain: 'ETH', - symbol: 'XAUT', - decimals: 6, - contractAddress: '0x68749665FF8D2d112Fa859AA293F07A622782F38', - coinGeckoId: 'tether-gold', - isNative: false, - }, - { - chain: 'ETH', - symbol: 'UNI', - decimals: 18, - contractAddress: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984', - coinGeckoId: 'uniswap', - isNative: false, - }, - { - chain: 'ETH', - symbol: 'PEPE', - decimals: 18, - contractAddress: '0x6982508145454Ce325dDbE47a25d4ec3d2311933', - coinGeckoId: 'pepe', - isNative: false, - }, - { - chain: 'ETH', - symbol: 'stETH', - decimals: 18, - contractAddress: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', - coinGeckoId: 'staked-ether', - isNative: false, - }, - { - chain: 'ETH', - symbol: 'SHIB', - decimals: 18, - contractAddress: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE', - coinGeckoId: 'shiba-inu', - isNative: false, - }, - { - chain: 'ETH', - symbol: 'LINK', - decimals: 18, - contractAddress: '0x514910771AF9Ca656af840dff83E8264EcF986CA', - coinGeckoId: 'chainlink', - isNative: false, - }, - { - chain: 'ETH', - symbol: 'POL', - decimals: 18, - contractAddress: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6', - coinGeckoId: 'polygon-ecosystem-token', - isNative: false, - }, - { - chain: 'ETH', - symbol: 'WLFI', - decimals: 18, - contractAddress: '0x66f85E3865D0cFDC009acf6280a8621f12e46CCf', - coinGeckoId: 'world-liberty-financial', - isNative: false, - }, - { - chain: 'ETH', - symbol: 'AAVE', - decimals: 18, - contractAddress: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', - coinGeckoId: 'aave', - isNative: false, - }, -]; - -export async function fetchEthBalances(address: string): Promise { - let provider: ethers.providers.StaticJsonRpcProvider; - try { - provider = await getHealthyEthProvider(); - } catch (error) { - return { - chain: 'ETH', - address, - tokens: ETH_TOKENS.map(createEmptyTokenBalance), - totalUsd: null, - error: getErrorMessage(error), - }; - } - - const settled = await Promise.allSettled( - ETH_TOKENS.map(async (token) => readEthTokenBalance(provider, address, token)) - ); - - const tokens: TokenBalance[] = []; - const errors: Array<{ symbol: string; message: string }> = []; - - settled.forEach((result, index) => { - const token = ETH_TOKENS[index]; - - if (result.status === 'fulfilled') { - tokens.push(result.value); - return; - } - - tokens.push(createEmptyTokenBalance(token)); - errors.push({ symbol: token.symbol, message: getErrorMessage(result.reason) }); - }); - - const uniqueMessages = [...new Set(errors.map((item) => item.message))]; - const error = - errors.length === ETH_TOKENS.length && uniqueMessages.length === 1 - ? uniqueMessages[0] - : errors.length - ? errors.map((item) => `${item.symbol}: ${item.message}`).join(' | ') - : null; - - return { - chain: 'ETH', - address, - tokens, - totalUsd: null, - error, - }; -} - -async function getHealthyEthProvider(): Promise { - let lastError: unknown = new Error('No Ethereum RPC endpoints configured'); - for (const rpcUrl of ETH_RPC_CANDIDATES) { - const provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl, ETH_CHAIN_ID); - try { - await withTimeout( - provider.getBlockNumber(), - ETH_RPC_HEALTHCHECK_TIMEOUT_MS, - `ETH RPC health-check timed out for ${rpcUrl}` - ); - return provider; - } catch (error) { - lastError = error; - } - } - throw lastError; -} - -async function readEthTokenBalance(provider: ethers.providers.StaticJsonRpcProvider, address: string, token: TokenDefinition): Promise { - if (token.isNative) { - const balance = await withTimeout( - provider.getBalance(address), - ETH_BALANCE_TIMEOUT_MS, - 'ETH balance request timed out' - ); - - return { - ...token, - balanceRaw: balance.toString(), - balanceFormatted: ethers.utils.formatEther(balance), - priceUsd: null, - valueUsd: null, - }; - } - - const contract = new ethers.Contract(token.contractAddress, ERC20_ABI, provider); - const balance = (await withTimeout( - contract.balanceOf(address), - ETH_BALANCE_TIMEOUT_MS, - `${token.symbol} balance request timed out` - )) as ethers.BigNumber; - - return { - ...token, - balanceRaw: balance.toString(), - balanceFormatted: ethers.utils.formatUnits(balance, token.decimals), - priceUsd: null, - valueUsd: null, - }; -} - -function createEmptyTokenBalance(token: TokenDefinition): TokenBalance { - return { - ...token, - balanceRaw: '0', - balanceFormatted: '0', - priceUsd: null, - valueUsd: null, - }; -} - -function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs); - - promise - .then((value) => { - clearTimeout(timeoutId); - resolve(value); - }) - .catch((error) => { - clearTimeout(timeoutId); - reject(error); - }); - }); -} - -function getErrorMessage(error: unknown): string { - if (error instanceof Error && error.message) { - if (error.message.includes('Failed to fetch')) { - return 'Ethereum RPC is temporarily unavailable'; - } - return error.message; - } - - return 'Unable to load token balance'; -} - -function dedupeUrls(urls: string[]): string[] { - return [...new Set(urls.map((url) => url.trim()).filter(Boolean))]; -} diff --git a/apps/web/src/lib/balances/index.ts b/apps/web/src/lib/balances/index.ts deleted file mode 100644 index 17d9df8..0000000 --- a/apps/web/src/lib/balances/index.ts +++ /dev/null @@ -1,120 +0,0 @@ -import type { DerivedWallet } from '@/lib/crypto/derive-keys'; -import { fetchBtcBalances } from './btc-balances'; -import { fetchEthBalances } from './eth-balances'; -import { fetchUsdPrices } from './prices'; -import { fetchSolBalances } from './sol-balances'; -import { fetchTrxBalances } from './trx-balances'; -import { fetchBscBalances } from './bsc-balances'; -import type { BalanceChain, ChainBalance, PortfolioBalance, TokenBalance } from './types'; - -const SUPPORTED_CHAINS: BalanceChain[] = ['ETH', 'BTC', 'SOL', 'TRX', 'BSC']; - -const balanceFetchers: Record Promise> = { - ETH: fetchEthBalances, - BTC: fetchBtcBalances, - SOL: fetchSolBalances, - TRX: fetchTrxBalances, - BSC: fetchBscBalances, -}; - -export async function fetchAllBalances(wallets: DerivedWallet[]): Promise { - const settled = await Promise.allSettled( - SUPPORTED_CHAINS.map(async (chain) => { - const wallet = wallets.find((item) => item.chain === chain); - - if (!wallet) { - return createMissingChainBalance(chain); - } - - return balanceFetchers[chain](wallet.address); - }) - ); - - const rawChains = settled.map((result, index) => { - if (result.status === 'fulfilled') { - return result.value; - } - - return createMissingChainBalance(SUPPORTED_CHAINS[index], getErrorMessage(result.reason)); - }); - - let prices: Record = {}; - let priceError: string | null = null; - - try { - const coinIds = rawChains.flatMap((chain) => chain.tokens.map((token) => token.coinGeckoId)); - prices = await fetchUsdPrices(coinIds); - } catch (error) { - priceError = getErrorMessage(error); - } - - const chains = rawChains.map((chain) => enrichChain(chain, prices)); - - return { - chains, - totalUsd: sumNullable(chains.map((chain) => chain.totalUsd)), - errors: chains.reduce((acc, chain) => { - if (chain.error && chain.error !== '__transient__') { - acc[chain.chain] = chain.error; - } - return acc; - }, {}), - priceError, - updatedAt: new Date().toISOString(), - }; -} - -function enrichChain(chain: ChainBalance, prices: Record): ChainBalance { - const tokens = chain.tokens.map((token) => enrichToken(token, prices)); - - return { - ...chain, - tokens, - totalUsd: sumNullable(tokens.map((token) => token.valueUsd)), - }; -} - -function enrichToken(token: TokenBalance, prices: Record): TokenBalance { - const priceUsd = prices[token.coinGeckoId]; - - if (typeof priceUsd !== 'number') { - return token; - } - - const balance = Number(token.balanceFormatted); - const valueUsd = Number.isFinite(balance) ? balance * priceUsd : null; - - return { - ...token, - priceUsd, - valueUsd, - }; -} - -function createMissingChainBalance(chain: BalanceChain, error = 'Wallet not available'): ChainBalance { - return { - chain, - address: '', - tokens: [], - totalUsd: null, - error, - }; -} - -function sumNullable(values: Array): number | null { - const filtered = values.filter((value): value is number => typeof value === 'number'); - - if (!filtered.length) { - return null; - } - - return filtered.reduce((total, value) => total + value, 0); -} - -function getErrorMessage(error: unknown): string { - if (error instanceof Error) { - return error.message; - } - - return 'Unable to load balances'; -} diff --git a/apps/web/src/lib/balances/prices.ts b/apps/web/src/lib/balances/prices.ts deleted file mode 100644 index 83cfcab..0000000 --- a/apps/web/src/lib/balances/prices.ts +++ /dev/null @@ -1,70 +0,0 @@ -const PRICE_CACHE_TTL_MS = 60_000; -const PRICE_REQUEST_TIMEOUT_MS = 10_000; - -let cachedPrices: Record | null = null; -let cachedAt = 0; - -interface CoinGeckoPriceResponse { - [coinId: string]: { - usd?: number; - }; -} - -export async function fetchUsdPrices(coinIds: string[]): Promise> { - const uniqueCoinIds = Array.from(new Set(coinIds.filter(Boolean))); - - if (!uniqueCoinIds.length) { - return {}; - } - - if ( - cachedPrices && - Date.now() - cachedAt < PRICE_CACHE_TTL_MS && - uniqueCoinIds.every((coinId) => coinId in cachedPrices!) - ) { - return uniqueCoinIds.reduce>((acc, coinId) => { - acc[coinId] = cachedPrices![coinId]; - return acc; - }, {}); - } - - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), PRICE_REQUEST_TIMEOUT_MS); - - try { - const url = new URL('https://api.coingecko.com/api/v3/simple/price'); - url.searchParams.set('ids', uniqueCoinIds.join(',')); - url.searchParams.set('vs_currencies', 'usd'); - - const response = await fetch(url.toString(), { - signal: controller.signal, - cache: 'no-store', - headers: { - Accept: 'application/json', - }, - }); - - if (!response.ok) { - throw new Error(`CoinGecko returned ${response.status}`); - } - - const payload = (await response.json()) as CoinGeckoPriceResponse; - const prices = uniqueCoinIds.reduce>((acc, coinId) => { - const price = payload[coinId]?.usd; - if (typeof price === 'number') { - acc[coinId] = price; - } - return acc; - }, {}); - - cachedPrices = { - ...(cachedPrices ?? {}), - ...prices, - }; - cachedAt = Date.now(); - - return prices; - } finally { - clearTimeout(timeoutId); - } -} diff --git a/apps/web/src/lib/balances/sol-balances.ts b/apps/web/src/lib/balances/sol-balances.ts deleted file mode 100644 index 258cb9b..0000000 --- a/apps/web/src/lib/balances/sol-balances.ts +++ /dev/null @@ -1,325 +0,0 @@ -import { Connection, LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'; -import { webEnv } from '@/lib/env'; -import type { ChainBalance, TokenBalance, TokenDefinition } from './types'; - -const SOL_BALANCE_TIMEOUT_MS = 6_000; -const SOL_RPC_CANDIDATES = dedupeUrls([ - webEnv.solRpcUrl, - 'https://solana.publicnode.com', - 'https://api.mainnet-beta.solana.com', -]); - -const SOL_TOKENS: TokenDefinition[] = [ - { - chain: 'SOL', - symbol: 'SOL', - decimals: 9, - contractAddress: 'native', - coinGeckoId: 'solana', - isNative: true, - }, - { - chain: 'SOL', - symbol: 'USDT', - decimals: 6, - contractAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', - coinGeckoId: 'tether', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'USDC', - decimals: 6, - contractAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', - coinGeckoId: 'usd-coin', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'PUMP', - decimals: 6, - contractAddress: 'pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn', - coinGeckoId: 'pump', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'JUP', - decimals: 6, - contractAddress: 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN', - coinGeckoId: 'jupiter-exchange-solana', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'WIF', - decimals: 6, - contractAddress: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm', - coinGeckoId: 'dogwifcoin', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'POPCAT', - decimals: 9, - contractAddress: '7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr', - coinGeckoId: 'popcat', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'TRUMP', - decimals: 6, - contractAddress: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', - coinGeckoId: 'official-trump', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'PYTH', - decimals: 6, - contractAddress: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3', - coinGeckoId: 'pyth-network', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'JTO', - decimals: 9, - contractAddress: 'jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL', - coinGeckoId: 'jito-governance-token', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'W', - decimals: 6, - contractAddress: '85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ', - coinGeckoId: 'wormhole', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'BONK', - decimals: 5, - contractAddress: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', - coinGeckoId: 'bonk', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'ORCA', - decimals: 6, - contractAddress: 'orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE', - coinGeckoId: 'orca', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'PENGU', - decimals: 6, - contractAddress: '2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv', - coinGeckoId: 'pudgy-penguins', - isNative: false, - }, - { - chain: 'SOL', - symbol: 'RAY', - decimals: 6, - contractAddress: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', - coinGeckoId: 'raydium', - isNative: false, - }, -]; - -export async function fetchSolBalances(address: string): Promise { - const owner = new PublicKey(address); - - const settled = await Promise.allSettled( - SOL_TOKENS.map(async (token) => readSolTokenWithFallback(owner, token)) - ); - - const tokens: TokenBalance[] = []; - const errors: Array<{ symbol: string; message: string }> = []; - - settled.forEach((result, index) => { - const token = SOL_TOKENS[index]; - - if (result.status === 'fulfilled') { - tokens.push(result.value); - return; - } - - tokens.push(createEmptyTokenBalance(token)); - errors.push({ symbol: token.symbol, message: getErrorMessage(result.reason) }); - }); - - // Separate transient (rate-limit, access restricted) from permanent errors - const permanentErrors = errors.filter((e) => !isTransientError(e.message)); - const transientErrors = errors.filter((e) => isTransientError(e.message)); - - const uniqueMessages = [...new Set(permanentErrors.map((item) => item.message))]; - let error: string | null = - permanentErrors.length === SOL_TOKENS.length && uniqueMessages.length === 1 - ? uniqueMessages[0] - : permanentErrors.length - ? permanentErrors.map((item) => `${item.symbol}: ${item.message}`).join(' | ') - : null; - - // If some/all errors were transient, mark chain so store keeps previous balances - // '__transient__' is an internal marker — not displayed in UI - if (!error && transientErrors.length > 0) { - error = '__transient__'; - } - - return { - chain: 'SOL', - address, - tokens, - totalUsd: null, - error, - }; -} - -async function readSolTokenWithFallback( - owner: PublicKey, - token: TokenDefinition -): Promise { - let lastError: unknown; - - for (const rpcUrl of SOL_RPC_CANDIDATES) { - try { - const connection = new Connection(rpcUrl, 'confirmed'); - return await readSolTokenBalance(connection, owner, token); - } catch (error) { - lastError = error; - if (isMintNotFoundError(error)) { - return createEmptyTokenBalance(token); - } - } - } - - throw lastError; -} - -async function readSolTokenBalance( - connection: Connection, - owner: PublicKey, - token: TokenDefinition -): Promise { - if (token.isNative) { - const lamports = await withTimeout( - connection.getBalance(owner), - SOL_BALANCE_TIMEOUT_MS, - 'SOL balance request timed out' - ); - - return { - ...token, - balanceRaw: lamports.toString(), - balanceFormatted: (lamports / LAMPORTS_PER_SOL).toString(), - priceUsd: null, - valueUsd: null, - }; - } - - const mint = new PublicKey(token.contractAddress); - const response = await withTimeout( - connection.getParsedTokenAccountsByOwner(owner, { mint }), - SOL_BALANCE_TIMEOUT_MS, - `${token.symbol} balance request timed out` - ); - - const amountRaw = response.value.reduce((sum, account) => { - const parsed = account.account.data.parsed; - const amount = parsed.info.tokenAmount.amount; - return sum + BigInt(amount); - }, 0n); - - return { - ...token, - balanceRaw: amountRaw.toString(), - balanceFormatted: formatBigIntBalance(amountRaw, token.decimals), - priceUsd: null, - valueUsd: null, - }; -} - -function isMintNotFoundError(error: unknown): boolean { - if (error instanceof Error) { - const msg = error.message; - return msg.includes('could not find mint') || msg.includes('Invalid param'); - } - return false; -} - -function createEmptyTokenBalance(token: TokenDefinition): TokenBalance { - return { - ...token, - balanceRaw: '0', - balanceFormatted: '0', - priceUsd: null, - valueUsd: null, - }; -} - -function formatBigIntBalance(rawValue: bigint, decimals: number): string { - if (rawValue === 0n) { - return '0'; - } - - const divisor = 10n ** BigInt(decimals); - const whole = rawValue / divisor; - const fraction = rawValue % divisor; - - if (fraction === 0n) { - return whole.toString(); - } - - return `${whole}.${fraction.toString().padStart(decimals, '0').replace(/0+$/, '')}`; -} - -function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs); - - promise - .then((value) => { - clearTimeout(timeoutId); - resolve(value); - }) - .catch((error) => { - clearTimeout(timeoutId); - reject(error); - }); - }); -} - -function isTransientError(msg: string): boolean { - return msg.includes('access restricted') || - msg.includes('temporarily unavailable') || - msg.includes('timed out') || - msg.includes('rate') || - msg.includes('429') || - msg.includes('403'); -} - -function getErrorMessage(error: unknown): string { - if (error instanceof Error) { - const msg = error.message; - if (msg.includes('403') || msg.includes('API key is not allowed')) { - return 'Solana RPC access restricted'; - } - if (msg.includes('Failed to fetch')) { - return 'Solana RPC is temporarily unavailable'; - } - return msg; - } - - return 'Unable to load SOL balance'; -} - - -function dedupeUrls(urls: string[]): string[] { - return [...new Set(urls.map((url) => url.trim()).filter(Boolean))]; -} diff --git a/apps/web/src/lib/balances/trx-balances.ts b/apps/web/src/lib/balances/trx-balances.ts deleted file mode 100644 index 70a4ab4..0000000 --- a/apps/web/src/lib/balances/trx-balances.ts +++ /dev/null @@ -1,133 +0,0 @@ -import { webEnv } from '@/lib/env'; -import type { ChainBalance, TokenDefinition } from './types'; - -const TRX_BALANCE_TIMEOUT_MS = 12_000; - -const TRX_TOKENS: TokenDefinition[] = [ - { - chain: 'TRX', - symbol: 'TRX', - decimals: 6, - contractAddress: 'native', - coinGeckoId: 'tron', - isNative: true, - }, - { - chain: 'TRX', - symbol: 'USDT', - decimals: 6, - contractAddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', - coinGeckoId: 'tether', - isNative: false, - }, -]; - -interface TronGridAccountResponse { - data?: Array<{ - balance?: number; - trc20?: Array>; - }>; -} - -export async function fetchTrxBalances(address: string): Promise { - try { - const response = await fetchJsonWithTimeout( - `${webEnv.apiUrl}/api/tron/account/${address}`, - TRX_BALANCE_TIMEOUT_MS - ); - - const account = response.data?.[0]; - const nativeRaw = account?.balance ?? 0; - const trc20Balances = account?.trc20 ?? []; - const usdtRaw = trc20Balances.reduce((current, entry) => { - const next = entry[TRX_TOKENS[1].contractAddress]; - return next ?? current; - }, '0'); - - return { - chain: 'TRX', - address, - tokens: [ - { - ...TRX_TOKENS[0], - balanceRaw: nativeRaw.toString(), - balanceFormatted: formatBalance(nativeRaw.toString(), TRX_TOKENS[0].decimals), - priceUsd: null, - valueUsd: null, - }, - { - ...TRX_TOKENS[1], - balanceRaw: usdtRaw, - balanceFormatted: formatBalance(usdtRaw, TRX_TOKENS[1].decimals), - priceUsd: null, - valueUsd: null, - }, - ], - totalUsd: null, - error: null, - }; - } catch (error) { - console.warn(`[TRX] balance fetch failed:`, error); - - return { - chain: 'TRX', - address, - tokens: TRX_TOKENS.map((token) => ({ - ...token, - balanceRaw: '0', - balanceFormatted: '0', - priceUsd: null, - valueUsd: null, - })), - totalUsd: null, - error: getErrorMessage(error), - }; - } -} - -async function fetchJsonWithTimeout(url: string, timeoutMs: number): Promise { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), timeoutMs); - - try { - const response = await fetch(url, { - signal: controller.signal, - cache: 'no-store', - headers: { Accept: 'application/json' }, - }); - - if (!response.ok) { - throw new Error(`TRON API returned ${response.status}`); - } - - return (await response.json()) as T; - } finally { - clearTimeout(timeoutId); - } -} - -function formatBalance(rawValue: string, decimals: number): string { - const bigintValue = BigInt(rawValue || '0'); - - if (bigintValue === 0n) { - return '0'; - } - - const divisor = 10n ** BigInt(decimals); - const whole = bigintValue / divisor; - const fraction = bigintValue % divisor; - - if (fraction === 0n) { - return whole.toString(); - } - - return `${whole}.${fraction.toString().padStart(decimals, '0').replace(/0+$/, '')}`; -} - -function getErrorMessage(error: unknown): string { - if (error instanceof Error) { - return error.message; - } - - return 'Unable to load TRX balance'; -} diff --git a/apps/web/src/lib/balances/types.ts b/apps/web/src/lib/balances/types.ts deleted file mode 100644 index 1f0fec9..0000000 --- a/apps/web/src/lib/balances/types.ts +++ /dev/null @@ -1,33 +0,0 @@ -export type BalanceChain = 'ETH' | 'BTC' | 'SOL' | 'TRX' | 'BSC'; - -export interface TokenDefinition { - chain: BalanceChain; - symbol: string; - decimals: number; - contractAddress: string | 'native'; - coinGeckoId: string; - isNative: boolean; -} - -export interface TokenBalance extends TokenDefinition { - balanceRaw: string; - balanceFormatted: string; - priceUsd: number | null; - valueUsd: number | null; -} - -export interface ChainBalance { - chain: BalanceChain; - address: string; - tokens: TokenBalance[]; - totalUsd: number | null; - error: string | null; -} - -export interface PortfolioBalance { - chains: ChainBalance[]; - totalUsd: number | null; - errors: Partial>; - priceError: string | null; - updatedAt: string; -} diff --git a/apps/web/src/lib/bridge/constants.ts b/apps/web/src/lib/bridge/constants.ts deleted file mode 100644 index fc12ad4..0000000 --- a/apps/web/src/lib/bridge/constants.ts +++ /dev/null @@ -1,112 +0,0 @@ -export const RELAY_PROXY_BASE_URL = '/api/relay'; -export const RELAY_REQUEST_TIMEOUT_MS = 15_000; - -// ── Bridge platform fee (0.7%) ── -export const BRIDGE_FEE_BPS = 70; // 0.7% -export const BRIDGE_FEE_RECIPIENT_EVM = '0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718'; -export const BRIDGE_FEE_RECIPIENT_SOL = 'Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ'; -export const BRIDGE_FEE_RECIPIENT_TRX = 'TYTfrem65362TFyQSARTheeYza1GQA37Ug'; - -// ─── Chain types ─── - -export type BridgeChainKey = 'ETH' | 'BSC' | 'SOL' | 'TRX'; - -export interface BridgeCurrencyConfig { - symbol: string; - address: string; - decimals: number; -} - -export interface BridgeChainConfig { - key: BridgeChainKey; - label: string; - chainId: number; - walletChain: 'ETH' | 'BSC' | 'SOL' | 'TRX'; - explorerTxBaseUrl: string; - tokens: Record; -} - -export const BRIDGE_CHAINS: Record = { - ETH: { - key: 'ETH', - label: 'Ethereum', - chainId: 1, - walletChain: 'ETH', - explorerTxBaseUrl: 'https://etherscan.io/tx/', - tokens: { - ETH: { symbol: 'ETH', address: '0x0000000000000000000000000000000000000000', decimals: 18 }, - USDT: { symbol: 'USDT', address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', decimals: 6 }, - USDC: { symbol: 'USDC', address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', decimals: 6 }, - }, - }, - SOL: { - key: 'SOL', - label: 'Solana', - chainId: 792703809, - walletChain: 'SOL', - explorerTxBaseUrl: 'https://solscan.io/tx/', - tokens: { - SOL: { symbol: 'SOL', address: '11111111111111111111111111111111', decimals: 9 }, - USDT: { symbol: 'USDT', address: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', decimals: 6 }, - USDC: { symbol: 'USDC', address: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', decimals: 6 }, - }, - }, - BSC: { - key: 'BSC', - label: 'BNB Smart Chain', - chainId: 56, - walletChain: 'BSC', - explorerTxBaseUrl: 'https://bscscan.com/tx/', - tokens: { - BNB: { symbol: 'BNB', address: '0x0000000000000000000000000000000000000000', decimals: 18 }, - USDT: { symbol: 'USDT', address: '0x55d398326f99059fF775485246999027B3197955', decimals: 18 }, - }, - }, - TRX: { - key: 'TRX', - label: 'TRON', - chainId: 728126428, - walletChain: 'TRX', - explorerTxBaseUrl: 'https://tronscan.org/#/transaction/', - tokens: { - USDT: { symbol: 'USDT', address: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', decimals: 6 }, - }, - }, -}; - -export const BRIDGE_CHAIN_OPTIONS: BridgeChainKey[] = ['ETH', 'BSC', 'SOL', 'TRX']; - -// ─── Helpers ─── - -export function getDestinationChainOptions(sourceChain: BridgeChainKey): BridgeChainKey[] { - return BRIDGE_CHAIN_OPTIONS.filter((c) => c !== sourceChain); -} - -export function getTokenOptions(chainKey: BridgeChainKey): string[] { - return Object.keys(BRIDGE_CHAINS[chainKey].tokens); -} - -export function getDefaultToken(chainKey: BridgeChainKey): string { - const tokens = getTokenOptions(chainKey); - return tokens[0]; -} - -export function getTokenConfig(chainKey: BridgeChainKey, tokenSymbol: string): BridgeCurrencyConfig { - const token = BRIDGE_CHAINS[chainKey].tokens[tokenSymbol]; - if (!token) { - throw new Error(`Token ${tokenSymbol} not found on ${BRIDGE_CHAINS[chainKey].label}`); - } - return token; -} - -// ─── Request type ─── - -export interface BridgeQuoteRequest { - sourceChain: BridgeChainKey; - sourceToken: string; - destChain: BridgeChainKey; - destToken: string; - amount: string; - userAddress: string; - recipientAddress: string; -} diff --git a/apps/web/src/lib/bridge/execute.ts b/apps/web/src/lib/bridge/execute.ts deleted file mode 100644 index a3ab852..0000000 --- a/apps/web/src/lib/bridge/execute.ts +++ /dev/null @@ -1,482 +0,0 @@ -import { ethers } from 'ethers'; -import { - Connection, - Keypair, - PublicKey, - TransactionInstruction, - TransactionMessage, - VersionedTransaction, - AddressLookupTableAccount, -} from '@solana/web3.js'; -import { createEthProvider } from '@/lib/eth-provider'; -import { webEnv } from '@/lib/env'; -import { BSC_GAS_PRICE } from '@/lib/crypto/bsc-constants'; -import { - BRIDGE_CHAINS, - BRIDGE_FEE_BPS, - BRIDGE_FEE_RECIPIENT_EVM, - BRIDGE_FEE_RECIPIENT_SOL, - BRIDGE_FEE_RECIPIENT_TRX, - RELAY_PROXY_BASE_URL, - RELAY_REQUEST_TIMEOUT_MS, - type BridgeChainKey, -} from './constants'; -import type { RelayQuoteResponse, RelayStep } from './quote'; - -const provider = createEthProvider(); - -// TYTfrem65362TFyQSARTheeYza1GQA37Ug → hex (20 bytes, no 0x prefix) -const BRIDGE_FEE_RECIPIENT_TRX_HEX = 'f6b4d4e650fc67982894f37ba97ab2496781ddb6'; - -interface ExecuteBridgeParams { - sourceChain: BridgeChainKey; - sourceToken: string; - originalAmount: string; - sourceTokenDecimals: number; - sourceTokenAddress: string; - privateKey: string; - quote: RelayQuoteResponse; - maxFeeGwei?: string | null; - priorityFeeGwei?: string | null; -} - -export interface ExecuteBridgeResult { - requestId: string | null; - txHashes: string[]; -} - -export async function executeBridge(params: ExecuteBridgeParams): Promise { - switch (params.sourceChain) { - case 'ETH': - return executeEvmBridge(params, provider); - case 'BSC': - return executeEvmBridge(params, new ethers.providers.StaticJsonRpcProvider(webEnv.bscRpcUrl, 56)); - case 'SOL': - return executeSolBridge(params); - case 'TRX': - return executeTrxBridge(params); - } -} - -// ─── EVM origin (existing logic) ─── - -async function executeEvmBridge( - params: ExecuteBridgeParams, - evmProvider: ethers.providers.Provider, -): Promise { - const wallet = new ethers.Wallet(params.privateKey, evmProvider); - const isBsc = params.sourceChain === 'BSC'; - const txHashes: string[] = []; - let requestId = params.quote.steps.find((s) => s.requestId)?.requestId ?? null; - - // ── Send 0.7% platform fee before bridge ── - await sendEvmBridgeFee(wallet, params, isBsc); - - for (const step of params.quote.steps) { - if (!step.items?.length) continue; - - for (const item of step.items) { - if (step.kind === 'signature') { - await executeSignatureStep(wallet, step, item.data); - } else { - const hash = await executeEvmTransactionStep(wallet, item.data, params.maxFeeGwei, params.priorityFeeGwei, isBsc); - txHashes.push(hash); - } - requestId = requestId ?? step.requestId ?? extractRequestId(item.check?.endpoint); - } - } - - return { requestId, txHashes }; -} - -async function executeEvmTransactionStep( - wallet: ethers.Wallet, - data: Record, - maxFeeGwei?: string | null, - priorityFeeGwei?: string | null, - isBsc?: boolean, -): Promise { - const gasOverrides = isBsc - ? { gasPrice: BSC_GAS_PRICE } - : maxFeeGwei?.trim() - ? { - maxFeePerGas: ethers.utils.parseUnits(maxFeeGwei, 'gwei'), - maxPriorityFeePerGas: ethers.utils.parseUnits(priorityFeeGwei?.trim() || '0.01', 'gwei'), - } - : { - ...(data.maxFeePerGas ? { maxFeePerGas: ethers.BigNumber.from(data.maxFeePerGas) } : {}), - ...(data.maxPriorityFeePerGas ? { maxPriorityFeePerGas: ethers.BigNumber.from(data.maxPriorityFeePerGas) } : {}), - }; - - const response = await wallet.sendTransaction({ - to: data.to, - data: data.data, - value: data.value ? ethers.BigNumber.from(data.value) : ethers.constants.Zero, - gasLimit: data.gas ? ethers.BigNumber.from(data.gas) : undefined, - ...gasOverrides, - }); - - const receipt = await response.wait(); - if (!receipt || receipt.status !== 1) { - throw new Error('Bridge transaction reverted'); - } - - return response.hash; -} - -// ─── Bridge Fee Helpers ─── - -async function sendEvmBridgeFee(wallet: ethers.Wallet, params: ExecuteBridgeParams, isBsc?: boolean): Promise { - const fullAmountRaw = ethers.utils.parseUnits(params.originalAmount, params.sourceTokenDecimals); - const feeAmount = fullAmountRaw.mul(BRIDGE_FEE_BPS).div(10000); - if (feeAmount.isZero()) return; - - const gasOverrides = isBsc ? { gasPrice: BSC_GAS_PRICE } : {}; - const isNative = params.sourceTokenAddress === '0x0000000000000000000000000000000000000000'; - - if (isNative) { - const tx = await wallet.sendTransaction({ - to: BRIDGE_FEE_RECIPIENT_EVM, - value: feeAmount, - ...gasOverrides, - }); - await tx.wait(); - } else { - const tokenContract = new ethers.Contract( - params.sourceTokenAddress, - ['function transfer(address to, uint256 amount) returns (bool)'], - wallet, - ); - const tx = await tokenContract.transfer(BRIDGE_FEE_RECIPIENT_EVM, feeAmount, gasOverrides); - await tx.wait(); - } -} - -async function sendSolBridgeFee( - connection: Connection, - keypair: Keypair, - params: ExecuteBridgeParams, -): Promise { - const { SystemProgram } = await import('@solana/web3.js'); - - const fullAmountRaw = BigInt( - Math.round(Number(params.originalAmount) * 10 ** params.sourceTokenDecimals), - ); - const feeAmount = (fullAmountRaw * BigInt(BRIDGE_FEE_BPS)) / 10000n; - if (feeAmount === 0n) return; - - const feeRecipient = new PublicKey(BRIDGE_FEE_RECIPIENT_SOL); - const isNative = params.sourceTokenAddress === '11111111111111111111111111111111'; - - // Bridge fee only supports native SOL transfers - // (SOL bridge primarily uses SOL, USDT, USDC — SPL fee handled off-chain if needed) - if (!isNative) return; - - const instruction = SystemProgram.transfer({ - fromPubkey: keypair.publicKey, - toPubkey: feeRecipient, - lamports: feeAmount, - }); - - const latestBlockhash = await connection.getLatestBlockhash('confirmed'); - const messageV0 = new TransactionMessage({ - payerKey: keypair.publicKey, - recentBlockhash: latestBlockhash.blockhash, - instructions: [instruction], - }).compileToV0Message(); - - const tx = new VersionedTransaction(messageV0); - tx.sign([keypair]); - - const sig = await connection.sendRawTransaction(tx.serialize(), { skipPreflight: false }); - await connection.confirmTransaction( - { signature: sig, blockhash: latestBlockhash.blockhash, lastValidBlockHeight: latestBlockhash.lastValidBlockHeight }, - 'confirmed', - ); -} - -async function sendTrxBridgeFee( - signingKey: ethers.utils.SigningKey, - apiUrl: string, - params: ExecuteBridgeParams, -): Promise { - const decimals = params.sourceTokenDecimals; - const fullAmountRaw = BigInt( - Math.round(Number(params.originalAmount) * 10 ** decimals), - ); - const feeAmount = (fullAmountRaw * BigInt(BRIDGE_FEE_BPS)) / 10000n; - if (feeAmount === 0n) return; - - // TRX bridge only supports USDT (TRC-20) — build a TRC20 transfer via API - // Use the tron proxy to build a transfer tx, sign it, and broadcast - const buildResp = await fetch(`${apiUrl}/api/tron/triggersmartcontract`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - owner_address: params.quote.steps[0]?.items?.[0]?.data?.parameter?.owner_address - ?? ethers.utils.computeAddress(signingKey.publicKey).toLowerCase(), - contract_address: params.sourceTokenAddress, - function_selector: 'transfer(address,uint256)', - parameter: - BRIDGE_FEE_RECIPIENT_TRX_HEX.padStart(64, '0') + - feeAmount.toString(16).padStart(64, '0'), - call_value: 0, - fee_limit: 100000000, - visible: false, - }), - }); - - if (!buildResp.ok) return; // Fee transfer is best-effort; don't block bridge - - const buildResult = await buildResp.json(); - const tx = buildResult.transaction; - if (!tx?.txID) return; - - // Sign and broadcast - const digest = ethers.utils.arrayify('0x' + tx.txID); - const signature = signingKey.signDigest(digest); - const sigHex = ethers.utils.joinSignature(signature).slice(2); - - const broadcastResp = await fetch(`${apiUrl}/api/tron/broadcasttransaction`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ ...tx, signature: [sigHex] }), - }); - - // Wait for broadcast, but don't fail the bridge if fee transfer fails - await broadcastResp.json(); -} - -async function executeSignatureStep(wallet: ethers.Wallet, step: RelayStep, data: Record): Promise { - const signData = data.sign; - const postData = data.post; - - if (!signData || !postData?.endpoint) { - throw new Error(`Invalid signature step payload for ${step.id}`); - } - - const signature = await signRelayPayload(wallet, signData); - const endpoint = new URL(`${webEnv.apiUrl}${RELAY_PROXY_BASE_URL}${postData.endpoint}`); - endpoint.searchParams.set('signature', signature); - - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), RELAY_REQUEST_TIMEOUT_MS); - - try { - const response = await fetch(endpoint.toString(), { - method: postData.method ?? 'POST', - signal: controller.signal, - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(postData.body ?? {}), - }); - - if (!response.ok) { - const payload = await response.text(); - throw new Error(payload || 'Relay signature submission failed'); - } - } finally { - clearTimeout(timeoutId); - } -} - -async function signRelayPayload(wallet: ethers.Wallet, signData: Record): Promise { - if (signData.signatureKind === 'eip191') { - const message = typeof signData.message === 'string' && signData.message.startsWith('0x') - ? ethers.utils.arrayify(signData.message) - : signData.message; - return wallet.signMessage(message); - } - - if (signData.signatureKind === 'eip712') { - const { EIP712Domain, ...types } = signData.types ?? {}; - return wallet._signTypedData(signData.domain ?? {}, types, signData.value ?? {}); - } - - throw new Error(`Unsupported Relay signature kind: ${signData.signatureKind}`); -} - -// ─── SOL origin ─── - -async function executeSolBridge(params: ExecuteBridgeParams): Promise { - const connection = new Connection(webEnv.solRpcUrl, 'confirmed'); - const keypair = Keypair.fromSecretKey(Buffer.from(params.privateKey, 'hex')); - const txHashes: string[] = []; - let requestId = params.quote.steps.find((s) => s.requestId)?.requestId ?? null; - - // ── Send 0.7% platform fee before bridge ── - await sendSolBridgeFee(connection, keypair, params); - - for (const step of params.quote.steps) { - if (!step.items?.length) continue; - - for (const item of step.items) { - const data = item.data; - - if (!data.instructions || !Array.isArray(data.instructions)) { - throw new Error('Expected Solana instructions in bridge step'); - } - - const hash = await executeSolTransactionStep(connection, keypair, data); - txHashes.push(hash); - requestId = requestId ?? step.requestId ?? extractRequestId(item.check?.endpoint); - } - } - - return { requestId, txHashes }; -} - -async function executeSolTransactionStep( - connection: Connection, - keypair: Keypair, - data: Record, -): Promise { - // Build instructions from Relay response - const instructions: TransactionInstruction[] = data.instructions.map((ix: any) => ({ - programId: new PublicKey(ix.programId), - keys: ix.keys.map((k: any) => ({ - pubkey: new PublicKey(k.pubkey), - isSigner: k.isSigner, - isWritable: k.isWritable, - })), - data: Buffer.from(ix.data, 'hex'), - })); - - // Load address lookup tables - const lookupTableAddresses: string[] = data.addressLookupTableAddresses ?? []; - const lookupTables: AddressLookupTableAccount[] = []; - - for (const addr of lookupTableAddresses) { - const account = await connection.getAddressLookupTable(new PublicKey(addr)); - if (account.value) { - lookupTables.push(account.value); - } - } - - // Build versioned transaction - const latestBlockhash = await connection.getLatestBlockhash('confirmed'); - const messageV0 = new TransactionMessage({ - payerKey: keypair.publicKey, - recentBlockhash: latestBlockhash.blockhash, - instructions, - }).compileToV0Message(lookupTables); - - const transaction = new VersionedTransaction(messageV0); - transaction.sign([keypair]); - - // Send and confirm - const signature = await connection.sendRawTransaction(transaction.serialize(), { - skipPreflight: false, - maxRetries: 2, - }); - - await connection.confirmTransaction( - { - signature, - blockhash: latestBlockhash.blockhash, - lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, - }, - 'confirmed', - ); - - return signature; -} - -// ─── TRX origin ─── - -async function executeTrxBridge(params: ExecuteBridgeParams): Promise { - const signingKey = new ethers.utils.SigningKey('0x' + params.privateKey); - const apiUrl = webEnv.apiUrl || 'http://localhost:3001'; - const txHashes: string[] = []; - let requestId = params.quote.steps.find((s) => s.requestId)?.requestId ?? null; - - // ── Send 0.7% platform fee before bridge ── - await sendTrxBridgeFee(signingKey, apiUrl, params); - - for (const step of params.quote.steps) { - if (!step.items?.length) continue; - - for (const item of step.items) { - const data = item.data; - - if (data.type !== 'TriggerSmartContract') { - throw new Error(`Unsupported TRX step type: ${data.type}`); - } - - const hash = await executeTrxTransactionStep(signingKey, apiUrl, data); - txHashes.push(hash); - requestId = requestId ?? step.requestId ?? extractRequestId(item.check?.endpoint); - } - - // Small delay between steps (e.g., approve → deposit) - if (step.items.length > 0 && step !== params.quote.steps[params.quote.steps.length - 1]) { - await delay(3000); - } - } - - return { requestId, txHashes }; -} - -async function executeTrxTransactionStep( - signingKey: ethers.utils.SigningKey, - apiUrl: string, - data: Record, -): Promise { - // 1. Build transaction via TronGrid triggersmartcontract - const buildResponse = await fetch(`${apiUrl}/api/tron/triggersmartcontract`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(data.parameter), - }); - - if (!buildResponse.ok) { - const body = await buildResponse.json().catch(() => ({ error: 'Failed to build TRX bridge tx' })); - throw new Error(body.error || `TRX bridge build failed (${buildResponse.status})`); - } - - const buildResult = await buildResponse.json(); - const tx = buildResult.transaction; - if (!tx?.txID) { - throw new Error('TronGrid did not return a valid transaction'); - } - - // 2. Sign txID with secp256k1 - const digest = ethers.utils.arrayify('0x' + tx.txID); - const signature = signingKey.signDigest(digest); - const sigHex = ethers.utils.joinSignature(signature).slice(2); // 65 bytes hex, no 0x - - const signedTx = { - ...tx, - signature: [sigHex], - }; - - // 3. Broadcast - const broadcastResponse = await fetch(`${apiUrl}/api/tron/broadcasttransaction`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(signedTx), - }); - - const result = await broadcastResponse.json(); - if (!result.result) { - const errorMsg = result.message || result.code || 'TRX broadcast failed'; - throw new Error(`TRX bridge broadcast error: ${errorMsg}`); - } - - return tx.txID; -} - -// ─── Utils ─── - -function extractRequestId(endpoint?: string): string | null { - if (!endpoint) return null; - try { - const url = new URL(endpoint, 'https://api.relay.link'); - return url.searchParams.get('requestId'); - } catch { - return null; - } -} - -function delay(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} diff --git a/apps/web/src/lib/bridge/quote.ts b/apps/web/src/lib/bridge/quote.ts deleted file mode 100644 index f7bba6a..0000000 --- a/apps/web/src/lib/bridge/quote.ts +++ /dev/null @@ -1,184 +0,0 @@ -import { ethers } from 'ethers'; -import { webEnv } from '@/lib/env'; -import { - BRIDGE_CHAINS, - BRIDGE_FEE_BPS, - RELAY_PROXY_BASE_URL, - RELAY_REQUEST_TIMEOUT_MS, - getTokenConfig, - type BridgeQuoteRequest, -} from './constants'; - -export interface RelayStep { - id: string; - kind: 'transaction' | 'signature'; - requestId?: string; - items: Array<{ - status: 'complete' | 'incomplete'; - data: Record; - check?: { - endpoint: string; - method: 'GET' | 'POST'; - }; - }>; -} - -export interface RelayQuoteResponse { - steps: RelayStep[]; - fees?: Record; - details?: { - timeEstimate?: number; - currencyOut?: { - currency?: { - symbol?: string; - decimals?: number; - }; - amount?: string; - amountFormatted?: string; - }; - totalImpact?: { - usd?: string; - percent?: string; - }; - slippageTolerance?: { - destination?: { - percent?: string; - }; - }; - }; -} - -export interface BridgeQuoteResult { - quote: RelayQuoteResponse; - sourceChain: string; - requestId: string | null; - outputAmountFormatted: string; - outputSymbol: string; - minimumAmountFormatted: string; - feeSummary: string; - timeEstimateSeconds: number | null; -} - -export async function getBridgeQuote(request: BridgeQuoteRequest): Promise { - if (!request.amount || Number(request.amount) <= 0) { - throw new Error('Enter a valid bridge amount'); - } - - const sourceChainConfig = BRIDGE_CHAINS[request.sourceChain]; - const destChainConfig = BRIDGE_CHAINS[request.destChain]; - const sourceTokenConfig = getTokenConfig(request.sourceChain, request.sourceToken); - const destTokenConfig = getTokenConfig(request.destChain, request.destToken); - - // Apply 0.7% platform fee — bridge only 99.3% of input - const fullAmountRaw = BigInt(parseAmountToRaw(request.amount, sourceTokenConfig.decimals)); - const feeAmount = (fullAmountRaw * BigInt(BRIDGE_FEE_BPS)) / 10000n; - const amount = (fullAmountRaw - feeAmount).toString(); - - const quote = await fetchRelayJson( - `${webEnv.apiUrl}${RELAY_PROXY_BASE_URL}/quote/v2`, - { - method: 'POST', - body: JSON.stringify({ - user: request.userAddress, - recipient: request.recipientAddress, - originChainId: sourceChainConfig.chainId, - destinationChainId: destChainConfig.chainId, - originCurrency: sourceTokenConfig.address, - destinationCurrency: destTokenConfig.address, - amount, - tradeType: 'EXACT_INPUT', - }), - }, - ); - - const requestId = quote.steps.find((step) => step.requestId)?.requestId ?? null; - const currencyOut = quote.details?.currencyOut; - - return { - quote, - sourceChain: request.sourceChain, - requestId, - outputAmountFormatted: currencyOut?.amountFormatted ?? 'Unavailable', - outputSymbol: currencyOut?.currency?.symbol ?? destTokenConfig.symbol, - minimumAmountFormatted: computeMinimumAmount(currencyOut, destTokenConfig.decimals), - feeSummary: buildFeeSummary(quote.fees), - timeEstimateSeconds: quote.details?.timeEstimate ?? null, - }; -} - -async function fetchRelayJson(url: string, options: RequestInit): Promise { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), RELAY_REQUEST_TIMEOUT_MS); - - try { - const response = await fetch(url, { - ...options, - signal: controller.signal, - headers: { - 'Content-Type': 'application/json', - ...(options.headers ?? {}), - }, - }); - - const payload = (await response.json()) as T & { message?: string }; - if (!response.ok) { - throw new Error((payload as { message?: string }).message || 'Relay quote request failed'); - } - - return payload; - } finally { - clearTimeout(timeoutId); - } -} - -function buildFeeSummary(fees: RelayQuoteResponse['fees']): string { - if (!fees) return 'Unavailable'; - - const usdTotal = Object.values(fees).reduce((total, fee) => { - const amountUsd = Number(fee.amountUsd ?? 0); - return Number.isFinite(amountUsd) ? total + amountUsd : total; - }, 0); - - if (usdTotal > 0) return `$${usdTotal.toFixed(4)}`; - - const relayerFee = fees.relayer; - if (relayerFee?.amountFormatted && relayerFee.currency?.symbol) { - return `${relayerFee.amountFormatted} ${relayerFee.currency.symbol}`; - } - - return 'Unavailable'; -} - -function computeMinimumAmount( - currencyOut: NonNullable['currencyOut'] | undefined, - decimals: number, -): string { - if (!currencyOut?.amount || !currencyOut.currency?.decimals) { - return 'Unavailable'; - } - - // Apply 2% slippage to displayed minimum - const raw = BigInt(currencyOut.amount); - const minimum = (raw * 98n) / 100n; - return formatRawUnits(minimum.toString(), currencyOut.currency.decimals); -} - -function parseAmountToRaw(amount: string, decimals: number): string { - const parts = amount.split('.'); - const whole = parts[0] || '0'; - const fraction = (parts[1] || '').padEnd(decimals, '0').slice(0, decimals); - const raw = BigInt(whole) * 10n ** BigInt(decimals) + BigInt(fraction); - return raw.toString(); -} - -function formatRawUnits(raw: string, decimals: number): string { - const value = BigInt(raw); - if (value === 0n) return '0'; - - const divisor = 10n ** BigInt(decimals); - const whole = value / divisor; - const fraction = value % divisor; - - if (fraction === 0n) return whole.toString(); - return `${whole}.${fraction.toString().padStart(decimals, '0').replace(/0+$/, '')}`; -} diff --git a/apps/web/src/lib/bridge/status.ts b/apps/web/src/lib/bridge/status.ts deleted file mode 100644 index cd84bfe..0000000 --- a/apps/web/src/lib/bridge/status.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { webEnv } from '@/lib/env'; -import { RELAY_PROXY_BASE_URL, RELAY_REQUEST_TIMEOUT_MS } from './constants'; - -export interface BridgeStatusResult { - status: 'waiting' | 'pending' | 'submitted' | 'success' | 'delayed' | 'refunded' | 'failure'; - details?: string; - inTxHashes?: string[]; - txHashes?: string[]; - updatedAt?: number; - originChainId?: number; - destinationChainId?: number; -} - -export async function getBridgeStatus(requestId: string): Promise { - const controller = new AbortController(); - const timeoutId = setTimeout(() => controller.abort(), RELAY_REQUEST_TIMEOUT_MS); - - try { - const response = await fetch( - `${webEnv.apiUrl}${RELAY_PROXY_BASE_URL}/intents/status/v3?requestId=${encodeURIComponent(requestId)}`, - { - signal: controller.signal, - cache: 'no-store', - } - ); - - const payload = (await response.json()) as BridgeStatusResult & { message?: string }; - if (!response.ok) { - throw new Error(payload.message || 'Unable to fetch bridge status'); - } - - return payload; - } finally { - clearTimeout(timeoutId); - } -} - -export function isBridgeTerminalStatus(status: BridgeStatusResult['status']): boolean { - return status === 'success' || status === 'failure' || status === 'refunded'; -} diff --git a/apps/web/src/lib/crypto/bsc-constants.ts b/apps/web/src/lib/crypto/bsc-constants.ts deleted file mode 100644 index 39a5e4e..0000000 --- a/apps/web/src/lib/crypto/bsc-constants.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { ethers } from 'ethers'; - -/** Fixed gas price for all BSC transactions (swaps & sends) */ -export const BSC_GAS_PRICE = ethers.utils.parseUnits('0.055', 'gwei'); diff --git a/apps/web/src/lib/crypto/btc.ts b/apps/web/src/lib/crypto/btc.ts deleted file mode 100644 index d0acdab..0000000 --- a/apps/web/src/lib/crypto/btc.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { HDKey } from '@scure/bip32'; -import * as bitcoin from 'bitcoinjs-lib'; - -export function deriveBtcWallet(seed: Uint8Array) { - const root = HDKey.fromMasterSeed(seed); - const child = root.derive("m/84'/0'/0'/0/0"); - - const { address } = bitcoin.payments.p2wpkh({ - pubkey: Buffer.from(child.publicKey!), - network: bitcoin.networks.bitcoin, - }); - - return { - address: address!, - privateKey: Buffer.from(child.privateKey!).toString('hex'), - }; -} diff --git a/apps/web/src/lib/crypto/derive-keys.ts b/apps/web/src/lib/crypto/derive-keys.ts deleted file mode 100644 index 64813ec..0000000 --- a/apps/web/src/lib/crypto/derive-keys.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { generateMnemonic, mnemonicToSeedBytes } from './mnemonic'; -import { deriveEthWallet } from './eth'; -import { deriveBtcWallet } from './btc'; -import { deriveSolWallet } from './sol'; -import { deriveTrxWallet } from './trx'; - -export type Chain = 'ETH' | 'BTC' | 'SOL' | 'TRX' | 'BSC'; - -const DERIVATION_PATHS: Record = { - ETH: "m/44'/60'/0'/0/0", - BTC: "m/84'/0'/0'/0/0", - SOL: "m/44'/501'/0'/0'", - TRX: "m/44'/195'/0'/0/0", - BSC: "m/44'/60'/0'/0/0", -}; - -export interface DerivedWallet { - chain: Chain; - address: string; - privateKey: string; - derivationPath: string; -} - -export interface DerivedKeys { - mnemonic: string; - wallets: DerivedWallet[]; -} - -export async function generateWallets(): Promise { - const mnemonic = generateMnemonic(); - return deriveWalletsFromMnemonic(mnemonic); -} - -export async function deriveWalletsFromMnemonic(mnemonic: string): Promise { - const seed = await mnemonicToSeedBytes(mnemonic); - - const eth = deriveEthWallet(mnemonic); - const btc = deriveBtcWallet(seed); - const sol = deriveSolWallet(seed); - const trx = deriveTrxWallet(mnemonic); - // BSC uses the same secp256k1 key as ETH (identical derivation path m/44'/60'/0'/0/0) - const bsc = deriveEthWallet(mnemonic); - - return { - mnemonic, - wallets: [ - { chain: 'ETH', address: eth.address, privateKey: eth.privateKey, derivationPath: DERIVATION_PATHS.ETH }, - { chain: 'BTC', address: btc.address, privateKey: btc.privateKey, derivationPath: DERIVATION_PATHS.BTC }, - { chain: 'SOL', address: sol.address, privateKey: sol.privateKey, derivationPath: DERIVATION_PATHS.SOL }, - { chain: 'TRX', address: trx.address, privateKey: trx.privateKey, derivationPath: DERIVATION_PATHS.TRX }, - { chain: 'BSC', address: bsc.address, privateKey: bsc.privateKey, derivationPath: DERIVATION_PATHS.BSC }, - ], - }; -} diff --git a/apps/web/src/lib/crypto/eth.ts b/apps/web/src/lib/crypto/eth.ts deleted file mode 100644 index 50598cd..0000000 --- a/apps/web/src/lib/crypto/eth.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { ethers } from 'ethers'; - -export function deriveEthWallet(mnemonicPhrase: string) { - const wallet = ethers.Wallet.fromMnemonic(mnemonicPhrase, "m/44'/60'/0'/0/0"); - return { - address: wallet.address, - privateKey: wallet.privateKey, - }; -} diff --git a/apps/web/src/lib/crypto/mnemonic.ts b/apps/web/src/lib/crypto/mnemonic.ts deleted file mode 100644 index 15668c8..0000000 --- a/apps/web/src/lib/crypto/mnemonic.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { generateMnemonic as genMnemonic, mnemonicToSeed, validateMnemonic } from '@scure/bip39'; -import { wordlist } from '@scure/bip39/wordlists/english.js'; - -export function generateMnemonic(): string { - return genMnemonic(wordlist, 128); -} - -export async function mnemonicToSeedBytes(mnemonic: string): Promise { - return mnemonicToSeed(mnemonic); -} - -export function isValidMnemonic(mnemonic: string): boolean { - return validateMnemonic(mnemonic, wordlist); -} diff --git a/apps/web/src/lib/crypto/sol.ts b/apps/web/src/lib/crypto/sol.ts deleted file mode 100644 index 6cba460..0000000 --- a/apps/web/src/lib/crypto/sol.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Keypair } from '@solana/web3.js'; -import { derivePath } from 'ed25519-hd-key'; - -export function deriveSolWallet(seed: Uint8Array) { - const path = "m/44'/501'/0'/0'"; - const derived = derivePath(path, Buffer.from(seed).toString('hex')); - const keypair = Keypair.fromSeed(derived.key); - - return { - address: keypair.publicKey.toBase58(), - privateKey: Buffer.from(keypair.secretKey).toString('hex'), - }; -} diff --git a/apps/web/src/lib/crypto/trx.ts b/apps/web/src/lib/crypto/trx.ts deleted file mode 100644 index 1bd65c7..0000000 --- a/apps/web/src/lib/crypto/trx.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { ethers } from 'ethers'; - -export function deriveTrxWallet(mnemonicPhrase: string) { - const hdNode = ethers.utils.HDNode.fromMnemonic(mnemonicPhrase).derivePath("m/44'/195'/0'/0/0"); - const ethAddress = ethers.utils.computeAddress(hdNode.publicKey); - const address = ethToTronAddress(ethAddress); - - return { - address, - privateKey: hdNode.privateKey.slice(2), - }; -} - -function ethToTronAddress(ethAddress: string): string { - const hex = '41' + ethAddress.slice(2); - return hexToBase58Check(hex); -} - -function hexToBase58Check(hex: string): string { - const bytes = hexToBytes(hex); - const hash1 = sha256Sync(bytes); - const hash2 = sha256Sync(hash1); - const checksum = hash2.slice(0, 4); - const payload = new Uint8Array(bytes.length + 4); - payload.set(bytes); - payload.set(checksum, bytes.length); - return base58Encode(payload); -} - -function hexToBytes(hex: string): Uint8Array { - const arr = new Uint8Array(hex.length / 2); - for (let i = 0; i < hex.length; i += 2) { - arr[i / 2] = parseInt(hex.substring(i, i + 2), 16); - } - return arr; -} - -function sha256Sync(data: Uint8Array): Uint8Array { - const { createHash } = require('crypto'); - return new Uint8Array(createHash('sha256').update(data).digest()); -} - -const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; - -function base58Encode(data: Uint8Array): string { - let num = BigInt('0x' + Array.from(data).map(b => b.toString(16).padStart(2, '0')).join('')); - let result = ''; - while (num > 0n) { - const mod = Number(num % 58n); - result = BASE58_ALPHABET[mod] + result; - num = num / 58n; - } - for (const byte of data) { - if (byte === 0) result = '1' + result; - else break; - } - return result; -} diff --git a/apps/web/src/lib/crypto/vault.ts b/apps/web/src/lib/crypto/vault.ts deleted file mode 100644 index b22e589..0000000 --- a/apps/web/src/lib/crypto/vault.ts +++ /dev/null @@ -1,107 +0,0 @@ -const PBKDF2_ITERATIONS = 600_000; - -export async function encryptVault( - mnemonic: string, - password: string, -): Promise<{ encryptedVault: string; vaultSalt: string }> { - const salt = crypto.getRandomValues(new Uint8Array(32)); - - const keyMaterial = await crypto.subtle.importKey( - 'raw', - new TextEncoder().encode(password), - 'PBKDF2', - false, - ['deriveKey'] - ); - - const aesKey = await crypto.subtle.deriveKey( - { name: 'PBKDF2', salt: salt as BufferSource, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' }, - keyMaterial, - { name: 'AES-GCM', length: 256 }, - false, - ['encrypt'] - ); - - const iv = crypto.getRandomValues(new Uint8Array(12)); - const ciphertext = await crypto.subtle.encrypt( - { name: 'AES-GCM', iv: iv as BufferSource }, - aesKey, - new TextEncoder().encode(mnemonic) - ); - - const blob = new Uint8Array(iv.length + ciphertext.byteLength); - blob.set(iv, 0); - blob.set(new Uint8Array(ciphertext), iv.length); - - return { - encryptedVault: uint8ToBase64(blob), - vaultSalt: uint8ToHex(salt), - }; -} - -export async function decryptVault( - encryptedVault: string, - vaultSalt: string, - password: string, -): Promise { - const salt = hexToUint8(vaultSalt); - const raw = base64ToUint8(encryptedVault); - - const iv = raw.slice(0, 12); - const ciphertextWithTag = raw.slice(12); - - const keyMaterial = await crypto.subtle.importKey( - 'raw', - new TextEncoder().encode(password), - 'PBKDF2', - false, - ['deriveKey'] - ); - - const aesKey = await crypto.subtle.deriveKey( - { name: 'PBKDF2', salt: salt as BufferSource, iterations: PBKDF2_ITERATIONS, hash: 'SHA-256' }, - keyMaterial, - { name: 'AES-GCM', length: 256 }, - false, - ['decrypt'] - ); - - const plaintext = await crypto.subtle.decrypt( - { name: 'AES-GCM', iv: iv as BufferSource }, - aesKey, - ciphertextWithTag - ); - - return new TextDecoder().decode(plaintext); -} - -function uint8ToBase64(arr: Uint8Array): string { - let binary = ''; - for (let i = 0; i < arr.length; i++) { - binary += String.fromCharCode(arr[i]); - } - return btoa(binary); -} - -function base64ToUint8(b64: string): Uint8Array { - const binary = atob(b64); - const arr = new Uint8Array(binary.length); - for (let i = 0; i < binary.length; i++) { - arr[i] = binary.charCodeAt(i); - } - return arr; -} - -function uint8ToHex(arr: Uint8Array): string { - return Array.from(arr) - .map((b) => b.toString(16).padStart(2, '0')) - .join(''); -} - -function hexToUint8(hex: string): Uint8Array { - const arr = new Uint8Array(hex.length / 2); - for (let i = 0; i < hex.length; i += 2) { - arr[i / 2] = parseInt(hex.substring(i, i + 2), 16); - } - return arr; -} diff --git a/apps/web/src/lib/env.ts b/apps/web/src/lib/env.ts deleted file mode 100644 index 067c5cb..0000000 --- a/apps/web/src/lib/env.ts +++ /dev/null @@ -1,16 +0,0 @@ -function readEnv(name: string, fallback: string): string { - const value = process.env[name]; - return value && value.trim() ? value : fallback; -} - -function readUrlEnv(name: string, fallback: string): string { - return readEnv(name, fallback).replace(/\/+$/, ''); -} - -export const webEnv = { - apiUrl: readUrlEnv('NEXT_PUBLIC_API_URL', 'http://localhost:3001'), - ethRpcUrl: readUrlEnv('NEXT_PUBLIC_ETH_RPC_URL', 'https://ethereum-rpc.publicnode.com'), - solRpcUrl: readUrlEnv('NEXT_PUBLIC_SOL_RPC_URL', 'https://solana.publicnode.com'), - btcApiUrl: readUrlEnv('NEXT_PUBLIC_BTC_API_URL', 'https://blockstream.info/api'), - bscRpcUrl: readUrlEnv('NEXT_PUBLIC_BSC_RPC_URL', 'https://bsc-dataseed.binance.org'), -} as const; diff --git a/apps/web/src/lib/eth-provider.ts b/apps/web/src/lib/eth-provider.ts deleted file mode 100644 index 8fb5a65..0000000 --- a/apps/web/src/lib/eth-provider.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ethers } from 'ethers'; -import { webEnv } from '@/lib/env'; - -const MAINNET_NETWORK = { chainId: 1, name: 'mainnet' } as const; - -const ETH_RPC_FALLBACKS = [ - 'https://ethereum-rpc.publicnode.com', - 'https://rpc.ankr.com/eth', - 'https://eth.llamarpc.com', -]; - -function getEthRpcUrls(): string[] { - return [...new Set([webEnv.ethRpcUrl, ...ETH_RPC_FALLBACKS].map((url) => url.trim()).filter(Boolean))]; -} - -export function createEthProvider(): ethers.providers.FallbackProvider { - const providerConfigs = getEthRpcUrls().map((url, index) => ({ - provider: new ethers.providers.StaticJsonRpcProvider(url, MAINNET_NETWORK), - priority: index + 1, - weight: 1, - stallTimeout: 1_200, - })); - - return new ethers.providers.FallbackProvider(providerConfigs, 1); -} diff --git a/apps/web/src/lib/gas-price.ts b/apps/web/src/lib/gas-price.ts deleted file mode 100644 index f39552d..0000000 --- a/apps/web/src/lib/gas-price.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { ethers } from 'ethers'; -import { createEthProvider } from '@/lib/eth-provider'; - -const FETCH_TIMEOUT_MS = 8_000; - -// Fixed small priority tips (gwei) — just enough to get included -const PRIORITY_FEE: Record<'slow' | 'normal' | 'fast', number> = { - slow: 0.01, - normal: 0.015, - fast: 0.03, -}; - -export interface GasTier { - maxFeePerGas: number; - maxPriorityFeePerGas: number; - confidence: number; -} - -export interface GasPriceData { - baseFeeGwei: number; - slow: GasTier; - normal: GasTier; - fast: GasTier; -} - -export async function fetchGasPrices(): Promise { - const provider = createEthProvider(); - - const feeData = await withTimeout( - provider.getFeeData(), - FETCH_TIMEOUT_MS, - 'ETH fee data request timed out', - ); - - if (!feeData.lastBaseFeePerGas) { - throw new Error('Could not get base fee from ETH RPC'); - } - - // Convert wei → gwei as float - const baseFeeGwei = parseFloat(ethers.utils.formatUnits(feeData.lastBaseFeePerGas, 'gwei')); - - function buildTier(mode: 'slow' | 'normal' | 'fast', confidence: number): GasTier { - const priority = PRIORITY_FEE[mode]; - return { - maxFeePerGas: baseFeeGwei + priority, - maxPriorityFeePerGas: priority, - confidence, - }; - } - - return { - baseFeeGwei, - slow: buildTier('slow', 70), - normal: buildTier('normal', 90), - fast: buildTier('fast', 99), - }; -} - -function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs); - promise - .then((value) => { clearTimeout(timeoutId); resolve(value); }) - .catch((error) => { clearTimeout(timeoutId); reject(error); }); - }); -} diff --git a/apps/web/src/lib/qr/generate.ts b/apps/web/src/lib/qr/generate.ts deleted file mode 100644 index a84732d..0000000 --- a/apps/web/src/lib/qr/generate.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { SEND_CHAINS, getDefaultToken, type SendChain } from '@/lib/send/constants'; - -/** Safely resolve token config, falling back to chain's native token */ -function resolveToken(chain: SendChain, token: string) { - const chainCfg = SEND_CHAINS[chain]; - return chainCfg.tokens[token] ?? chainCfg.tokens[getDefaultToken(chain)]; -} - -export interface GenerateQrParams { - chain: SendChain; - token: string; - address: string; - amount?: string; -} - -/** - * Generate a standard URI for QR code encoding. - * - * Formats: - * - ETH native: ethereum:
[?value=] - * - ETH ERC20: ethereum:/transfer?address=&uint256= - * - SOL native: solana:
[?amount=] - * - SOL SPL: solana:
?spl-token=[&amount=] - * - TRX native: tron:
[?amount=] - * - TRX TRC20: tron:
?token=[&amount=] - * - BTC: bitcoin:
[?amount=] - */ -export function generateReceiveUri(params: GenerateQrParams): string { - const { chain, token, address, amount } = params; - - switch (chain) { - case 'ETH': - return generateEthUri(address, token, amount); - case 'SOL': - return generateSolUri(address, token, amount); - case 'TRX': - return generateTrxUri(address, token, amount); - case 'BTC': - return generateBtcUri(address, amount); - case 'BSC': - return generateBscUri(address, token, amount); - } -} - -// ─── ETH (EIP-681) ─── - -function generateEthUri(address: string, token: string, amount?: string): string { - const tokenCfg = resolveToken('ETH', token); - - // Native ETH - if (!tokenCfg.contractAddress) { - if (amount && Number(amount) > 0) { - const wei = toRawUnits(amount, tokenCfg.decimals); - return `ethereum:${address}?value=${wei}`; - } - return `ethereum:${address}`; - } - - // ERC20 — ethereum:/transfer?address=&uint256= - const base = `ethereum:${tokenCfg.contractAddress}/transfer?address=${address}`; - if (amount && Number(amount) > 0) { - const raw = toRawUnits(amount, tokenCfg.decimals); - return `${base}&uint256=${raw}`; - } - return base; -} - -// ─── SOL (Solana Pay) ─── - -function generateSolUri(address: string, token: string, amount?: string): string { - const tokenCfg = resolveToken('SOL', token); - - const params = new URLSearchParams(); - - // SPL token - if (tokenCfg.contractAddress) { - params.set('spl-token', tokenCfg.contractAddress); - } - - if (amount && Number(amount) > 0) { - params.set('amount', amount); - } - - const qs = params.toString(); - return `solana:${address}${qs ? '?' + qs : ''}`; -} - -// ─── TRX ─── - -function generateTrxUri(address: string, token: string, amount?: string): string { - const tokenCfg = resolveToken('TRX', token); - - const params = new URLSearchParams(); - - if (tokenCfg.contractAddress) { - params.set('token', tokenCfg.contractAddress); - } - - if (amount && Number(amount) > 0) { - params.set('amount', amount); - } - - const qs = params.toString(); - return `tron:${address}${qs ? '?' + qs : ''}`; -} - -// ─── BTC (BIP-21) ─── - -function generateBtcUri(address: string, amount?: string): string { - if (amount && Number(amount) > 0) { - return `bitcoin:${address}?amount=${amount}`; - } - return `bitcoin:${address}`; -} - -// ─── BSC (EIP-681 with @56 chain discriminator) ─── - -function generateBscUri(address: string, token: string, amount?: string): string { - const tokenCfg = resolveToken('BSC', token); - - // Native BNB - if (!tokenCfg.contractAddress) { - if (amount && Number(amount) > 0) { - const wei = toRawUnits(amount, tokenCfg.decimals); - return `ethereum:${address}@56?value=${wei}`; - } - return `ethereum:${address}@56`; - } - - // BEP-20 — ethereum:@56/transfer?address=&uint256= - const base = `ethereum:${tokenCfg.contractAddress}@56/transfer?address=${address}`; - if (amount && Number(amount) > 0) { - const raw = toRawUnits(amount, tokenCfg.decimals); - return `${base}&uint256=${raw}`; - } - return base; -} - -// ─── Utils ─── - -function toRawUnits(amount: string, decimals: number): string { - const parts = amount.split('.'); - const whole = parts[0] || '0'; - const fraction = (parts[1] || '').padEnd(decimals, '0').slice(0, decimals); - return (BigInt(whole) * 10n ** BigInt(decimals) + BigInt(fraction)).toString(); -} diff --git a/apps/web/src/lib/qr/parse.ts b/apps/web/src/lib/qr/parse.ts deleted file mode 100644 index c7c0436..0000000 --- a/apps/web/src/lib/qr/parse.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { CONTRACT_TO_SYMBOL, type SendChain } from '@/lib/send/constants'; -import { validateAddress, detectChainFromAddress } from '@/lib/send/validate'; - -export interface ParsedQrResult { - chain: SendChain | null; - token: string; - address: string; - amount: string | null; -} - -/** - * Parse a QR URI into chain, token, address, and optional amount. - * - * Supports: - * - ethereum:
?value= - * - ethereum:/transfer?address=&uint256= - * - solana:
?amount=&spl-token= - * - tron:
?amount=&token= - * - bitcoin:
?amount= - * - Raw addresses (auto-detect chain) - */ -export function parseQrUri(uri: string): ParsedQrResult { - const trimmed = uri.trim(); - - // Detect scheme - if (trimmed.startsWith('ethereum:')) return parseEthUri(trimmed); - if (trimmed.startsWith('solana:')) return parseSolUri(trimmed); - if (trimmed.startsWith('tron:')) return parseTrxUri(trimmed); - if (trimmed.startsWith('bitcoin:')) return parseBtcUri(trimmed); - - // No scheme — try to detect chain from raw address - return parseRawAddress(trimmed); -} - -// ─── Ethereum (EIP-681) — also handles BSC via @56 chain discriminator ─── - -function parseEthUri(uri: string): ParsedQrResult { - const withoutScheme = uri.slice('ethereum:'.length); - - // Detect chain from @chainId discriminator - const isBsc = withoutScheme.includes('@56'); - const chain: SendChain = isBsc ? 'BSC' : 'ETH'; - const nativeToken = isBsc ? 'BNB' : 'ETH'; - const nativeDecimals = 18; - - // Strip @chainId from the URI for easier parsing - const cleaned = withoutScheme.replace(/@56/g, '').replace(/@1/g, ''); - - // Check for ERC20/BEP20 transfer: /transfer?address=&uint256= - const transferMatch = cleaned.match(/^(0x[0-9a-fA-F]{40})\/transfer\?(.+)$/); - if (transferMatch) { - const contract = transferMatch[1]; - const params = new URLSearchParams(transferMatch[2]); - const toAddress = params.get('address') || ''; - const rawAmount = params.get('uint256'); - - const known = CONTRACT_TO_SYMBOL[contract.toLowerCase()]; - const token = known?.symbol ?? nativeToken; - const decimals = known ? getDecimalsForSymbol(token) : nativeDecimals; - - return { - chain, - token, - address: toAddress, - amount: rawAmount ? fromRawUnits(rawAmount, decimals) : null, - }; - } - - // Native transfer:
?value= - const [addressPart, queryPart] = cleaned.split('?'); - const params = queryPart ? new URLSearchParams(queryPart) : null; - const weiValue = params?.get('value'); - - return { - chain, - token: nativeToken, - address: addressPart || '', - amount: weiValue ? fromRawUnits(weiValue, nativeDecimals) : null, - }; -} - -function getDecimalsForSymbol(symbol: string): number { - const decimalsMap: Record = { - USDT: 6, USDC: 6, XAUT: 6, DOGE: 8, - }; - return decimalsMap[symbol] ?? 18; -} - -// ─── Solana (Solana Pay) ─── - -function parseSolUri(uri: string): ParsedQrResult { - const withoutScheme = uri.slice('solana:'.length); - const [addressPart, queryPart] = withoutScheme.split('?'); - const params = queryPart ? new URLSearchParams(queryPart) : null; - - const splToken = params?.get('spl-token'); - const amount = params?.get('amount'); - - let token = 'SOL'; - if (splToken) { - const known = CONTRACT_TO_SYMBOL[splToken]; - token = known?.symbol ?? 'SOL'; - } - - return { - chain: 'SOL', - token, - address: addressPart || '', - amount: amount || null, - }; -} - -// ─── TRON ─── - -function parseTrxUri(uri: string): ParsedQrResult { - const withoutScheme = uri.slice('tron:'.length); - const [addressPart, queryPart] = withoutScheme.split('?'); - const params = queryPart ? new URLSearchParams(queryPart) : null; - - const tokenContract = params?.get('token'); - const amount = params?.get('amount'); - - let token = 'TRX'; - if (tokenContract) { - const known = CONTRACT_TO_SYMBOL[tokenContract]; - token = known?.symbol ?? 'TRX'; - } - - return { - chain: 'TRX', - token, - address: addressPart || '', - amount: amount || null, - }; -} - -// ─── Bitcoin (BIP-21) ─── - -function parseBtcUri(uri: string): ParsedQrResult { - const withoutScheme = uri.slice('bitcoin:'.length); - const [addressPart, queryPart] = withoutScheme.split('?'); - const params = queryPart ? new URLSearchParams(queryPart) : null; - - return { - chain: 'BTC', - token: 'BTC', - address: addressPart || '', - amount: params?.get('amount') || null, - }; -} - -// ─── Raw address (no scheme) ─── - -function parseRawAddress(address: string): ParsedQrResult { - const chain = detectChainFromAddress(address); - - if (chain) { - const validation = validateAddress(chain, address); - if (validation.valid) { - // Default to native token - const nativeTokens: Record = { - ETH: 'ETH', - SOL: 'SOL', - TRX: 'TRX', - BTC: 'BTC', - BSC: 'BNB', - }; - - return { - chain, - token: nativeTokens[chain], - address, - amount: null, - }; - } - } - - // Could not detect or validate - return { - chain: null, - token: '', - address, - amount: null, - }; -} - -// ─── Utils ─── - -function fromRawUnits(raw: string, decimals: number): string { - try { - const value = BigInt(raw); - if (value === 0n) return '0'; - - const divisor = 10n ** BigInt(decimals); - const whole = value / divisor; - const fraction = value % divisor; - - if (fraction === 0n) return whole.toString(); - return `${whole}.${fraction.toString().padStart(decimals, '0').replace(/0+$/, '')}`; - } catch { - return raw; - } -} diff --git a/apps/web/src/lib/send/constants.ts b/apps/web/src/lib/send/constants.ts deleted file mode 100644 index 805878a..0000000 --- a/apps/web/src/lib/send/constants.ts +++ /dev/null @@ -1,136 +0,0 @@ -export type SendChain = 'ETH' | 'SOL' | 'TRX' | 'BTC' | 'BSC'; - -export interface SendTokenConfig { - symbol: string; - contractAddress: string | null; // null = native - decimals: number; -} - -export interface SendChainConfig { - key: SendChain; - label: string; - walletChain: string; // auth-store chain key - tokens: Record; - explorerTxUrl: string; -} - -export const SEND_CHAINS: Record = { - ETH: { - key: 'ETH', - label: 'Ethereum', - walletChain: 'ETH', - tokens: { - ETH: { symbol: 'ETH', contractAddress: null, decimals: 18 }, - USDT: { symbol: 'USDT', contractAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7', decimals: 6 }, - USDC: { symbol: 'USDC', contractAddress: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', decimals: 6 }, - stETH: { symbol: 'stETH', contractAddress: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', decimals: 18 }, - SHIB: { symbol: 'SHIB', contractAddress: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE', decimals: 18 }, - LINK: { symbol: 'LINK', contractAddress: '0x514910771AF9Ca656af840dff83E8264EcF986CA', decimals: 18 }, - POL: { symbol: 'POL', contractAddress: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6', decimals: 18 }, - WLFI: { symbol: 'WLFI', contractAddress: '0x66f85E3865D0cFDC009acf6280a8621f12e46CCf', decimals: 18 }, - AAVE: { symbol: 'AAVE', contractAddress: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', decimals: 18 }, - }, - explorerTxUrl: 'https://etherscan.io/tx/', - }, - SOL: { - key: 'SOL', - label: 'Solana', - walletChain: 'SOL', - tokens: { - SOL: { symbol: 'SOL', contractAddress: null, decimals: 9 }, - USDT: { symbol: 'USDT', contractAddress: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', decimals: 6 }, - USDC: { symbol: 'USDC', contractAddress: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', decimals: 6 }, - PUMP: { symbol: 'PUMP', contractAddress: 'pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn', decimals: 6 }, - JUP: { symbol: 'JUP', contractAddress: 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN', decimals: 6 }, - WIF: { symbol: 'WIF', contractAddress: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm', decimals: 6 }, - POPCAT: { symbol: 'POPCAT', contractAddress: '7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr', decimals: 9 }, - TRUMP: { symbol: 'TRUMP', contractAddress: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', decimals: 6 }, - PYTH: { symbol: 'PYTH', contractAddress: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3', decimals: 6 }, - JTO: { symbol: 'JTO', contractAddress: 'jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL', decimals: 9 }, - W: { symbol: 'W', contractAddress: '85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ', decimals: 6 }, - BONK: { symbol: 'BONK', contractAddress: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', decimals: 5 }, - ORCA: { symbol: 'ORCA', contractAddress: 'orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE', decimals: 6 }, - PENGU: { symbol: 'PENGU', contractAddress: '2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv', decimals: 6 }, - RAY: { symbol: 'RAY', contractAddress: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', decimals: 6 }, - }, - explorerTxUrl: 'https://solscan.io/tx/', - }, - TRX: { - key: 'TRX', - label: 'TRON', - walletChain: 'TRX', - tokens: { - TRX: { symbol: 'TRX', contractAddress: null, decimals: 6 }, - USDT: { symbol: 'USDT', contractAddress: 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t', decimals: 6 }, - }, - explorerTxUrl: 'https://tronscan.org/#/transaction/', - }, - BTC: { - key: 'BTC', - label: 'Bitcoin', - walletChain: 'BTC', - tokens: { - BTC: { symbol: 'BTC', contractAddress: null, decimals: 8 }, - }, - explorerTxUrl: 'https://blockstream.info/tx/', - }, - BSC: { - key: 'BSC', - label: 'BNB Smart Chain', - walletChain: 'BSC', - tokens: { - BNB: { symbol: 'BNB', contractAddress: null, decimals: 18 }, - USDT: { symbol: 'USDT', contractAddress: '0x55d398326f99059fF775485246999027B3197955', decimals: 18 }, - DOGE: { symbol: 'DOGE', contractAddress: '0xbA2aE424d960c26247Dd6c32edC70B295c744C43', decimals: 8 }, - }, - explorerTxUrl: 'https://bscscan.com/tx/', - }, -}; - -export const SEND_CHAIN_OPTIONS: SendChain[] = ['ETH', 'SOL', 'TRX', 'BTC', 'BSC']; - -export function getTokenOptions(chain: SendChain): string[] { - return Object.keys(SEND_CHAINS[chain].tokens); -} - -export function getDefaultToken(chain: SendChain): string { - return getTokenOptions(chain)[0]; -} - -export function getTokenConfig(chain: SendChain, token: string): SendTokenConfig { - const cfg = SEND_CHAINS[chain].tokens[token]; - if (!cfg) throw new Error(`Token ${token} not found on ${chain}`); - return cfg; -} - -/** Map known contract addresses back to token symbols */ -export const CONTRACT_TO_SYMBOL: Record = { - // ETH - '0xdac17f958d2ee523a2206206994597c13d831ec7': { chain: 'ETH', symbol: 'USDT' }, - '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': { chain: 'ETH', symbol: 'USDC' }, - '0xae7ab96520de3a18e5e111b5eaab095312d7fe84': { chain: 'ETH', symbol: 'stETH' }, - '0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce': { chain: 'ETH', symbol: 'SHIB' }, - '0x514910771af9ca656af840dff83e8264ecf986ca': { chain: 'ETH', symbol: 'LINK' }, - '0x455e53cbb86018ac2b8092fdcd39d8444affc3f6': { chain: 'ETH', symbol: 'POL' }, - '0x66f85e3865d0cfdc009acf6280a8621f12e46ccf': { chain: 'ETH', symbol: 'WLFI' }, - '0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9': { chain: 'ETH', symbol: 'AAVE' }, - // SOL - 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB': { chain: 'SOL', symbol: 'USDT' }, - 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v': { chain: 'SOL', symbol: 'USDC' }, - 'pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn': { chain: 'SOL', symbol: 'PUMP' }, - 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN': { chain: 'SOL', symbol: 'JUP' }, - 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm': { chain: 'SOL', symbol: 'WIF' }, - '7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr': { chain: 'SOL', symbol: 'POPCAT' }, - '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN': { chain: 'SOL', symbol: 'TRUMP' }, - 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3': { chain: 'SOL', symbol: 'PYTH' }, - 'jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL': { chain: 'SOL', symbol: 'JTO' }, - '85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ': { chain: 'SOL', symbol: 'W' }, - 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263': { chain: 'SOL', symbol: 'BONK' }, - 'orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE': { chain: 'SOL', symbol: 'ORCA' }, - '2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv': { chain: 'SOL', symbol: 'PENGU' }, - '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R': { chain: 'SOL', symbol: 'RAY' }, - // TRX - 'TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t': { chain: 'TRX', symbol: 'USDT' }, - // BSC - '0xba2ae424d960c26247dd6c32edc70b295c744c43': { chain: 'BSC', symbol: 'DOGE' }, -}; diff --git a/apps/web/src/lib/send/execute.ts b/apps/web/src/lib/send/execute.ts deleted file mode 100644 index 26f785e..0000000 --- a/apps/web/src/lib/send/execute.ts +++ /dev/null @@ -1,622 +0,0 @@ -import { ethers } from 'ethers'; -import { - Connection, - Keypair, - PublicKey, - SystemProgram, - TransactionMessage, - VersionedTransaction, - TransactionInstruction, -} from '@solana/web3.js'; -import * as bitcoin from 'bitcoinjs-lib'; -import { ECPairFactory } from 'ecpair'; -import * as ecc from 'tiny-secp256k1'; -import { createEthProvider } from '@/lib/eth-provider'; -import { webEnv } from '@/lib/env'; -import { BSC_GAS_PRICE } from '@/lib/crypto/bsc-constants'; -import { SEND_CHAINS, getTokenConfig, type SendChain } from './constants'; - -const ECPair = ECPairFactory(ecc); -const ethProvider = createEthProvider(); - -// ─── Types ─── - -export interface SendParams { - chain: SendChain; - token: string; - toAddress: string; - amount: string; // human-readable - privateKey: string; - fromAddress: string; - maxFeeGwei?: string | null; // ETH only - priorityFeeGwei?: string | null; // ETH only -} - -export interface SendResult { - hash: string; - explorerUrl: string; -} - -// ─── Main dispatcher ─── - -export async function executeSend(params: SendParams): Promise { - if (!params.amount || Number(params.amount) <= 0) { - throw new Error('Enter a valid amount'); - } - - switch (params.chain) { - case 'ETH': - return executeEthSend(params); - case 'SOL': - return executeSolSend(params); - case 'TRX': - return executeTrxSend(params); - case 'BTC': - return executeBtcSend(params); - case 'BSC': - return executeBscSend(params); - } -} - -// ─── ETH Send ─── - -async function executeEthSend(params: SendParams): Promise { - const wallet = new ethers.Wallet(params.privateKey, ethProvider); - const tokenCfg = getTokenConfig('ETH', params.token); - - let hash: string; - - if (!tokenCfg.contractAddress) { - // Native ETH transfer - const value = ethers.utils.parseUnits(params.amount, tokenCfg.decimals); - const tx = await wallet.sendTransaction({ - to: params.toAddress, - value, - ...buildGasOverrides(params.maxFeeGwei, params.priorityFeeGwei), - }); - const receipt = await tx.wait(); - if (!receipt || receipt.status !== 1) throw new Error('ETH transaction reverted'); - hash = tx.hash; - } else { - // ERC20 transfer - const rawAmount = ethers.utils.parseUnits(params.amount, tokenCfg.decimals); - const erc20 = new ethers.Contract( - tokenCfg.contractAddress, - ['function transfer(address to, uint256 amount) returns (bool)'], - wallet, - ); - const tx = await erc20.transfer(params.toAddress, rawAmount, { - ...buildGasOverrides(params.maxFeeGwei, params.priorityFeeGwei), - }); - const receipt = await tx.wait(); - if (!receipt || receipt.status !== 1) throw new Error('ERC20 transfer reverted'); - hash = tx.hash; - } - - return { - hash, - explorerUrl: `${SEND_CHAINS.ETH.explorerTxUrl}${hash}`, - }; -} - -function buildGasOverrides(maxFeeGwei?: string | null, priorityFeeGwei?: string | null) { - if (!maxFeeGwei?.trim()) return {}; - return { - maxFeePerGas: ethers.utils.parseUnits(maxFeeGwei, 'gwei'), - maxPriorityFeePerGas: ethers.utils.parseUnits(priorityFeeGwei?.trim() || '0.01', 'gwei'), - }; -} - -// ─── SOL Send ─── - -const SOL_FEE_BPS = 70n; // 0.7% -const SOL_BPS_DENOMINATOR = 10_000n; -const SOL_FEE_RECIPIENT = new PublicKey('8TQUbkZGL2j48qgJppJ1dxUPVX8ZJx7i6bUcyaKrgDKi'); - -async function executeSolSend(params: SendParams): Promise { - const connection = new Connection(webEnv.solRpcUrl, 'confirmed'); - const keypair = Keypair.fromSecretKey(Buffer.from(params.privateKey, 'hex')); - const tokenCfg = getTokenConfig('SOL', params.token); - - let signature: string; - - if (!tokenCfg.contractAddress) { - // Native SOL transfer with 0.7% fee - const totalLamports = BigInt(parseAmountToRaw(params.amount, tokenCfg.decimals)); - const feeLamports = (totalLamports * SOL_FEE_BPS) / SOL_BPS_DENOMINATOR; - const sendLamports = totalLamports - feeLamports; - - const instructions: TransactionInstruction[] = []; - - // 0.7% fee to fee wallet - if (feeLamports > 0n) { - instructions.push( - SystemProgram.transfer({ - fromPubkey: keypair.publicKey, - toPubkey: SOL_FEE_RECIPIENT, - lamports: feeLamports, - }), - ); - } - - // 99.3% to recipient - instructions.push( - SystemProgram.transfer({ - fromPubkey: keypair.publicKey, - toPubkey: new PublicKey(params.toAddress), - lamports: sendLamports, - }), - ); - - signature = await buildAndSendSolTx(connection, keypair, instructions); - } else { - // SPL Token transfer with 0.7% fee - const mint = new PublicKey(tokenCfg.contractAddress); - const TOKEN_PROGRAM_ID = new PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA'); - const ASSOCIATED_TOKEN_PROGRAM_ID = new PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL'); - - const fromAta = getAssociatedTokenAddress(keypair.publicKey, mint, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID); - const toAta = getAssociatedTokenAddress(new PublicKey(params.toAddress), mint, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID); - const feeAta = getAssociatedTokenAddress(SOL_FEE_RECIPIENT, mint, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID); - - const totalRaw = BigInt(parseAmountToRaw(params.amount, tokenCfg.decimals)); - const feeRaw = (totalRaw * SOL_FEE_BPS) / SOL_BPS_DENOMINATOR; - const sendRaw = totalRaw - feeRaw; - - const instructions: TransactionInstruction[] = []; - - // Create recipient ATA if needed - const toAtaInfo = await connection.getAccountInfo(toAta); - if (!toAtaInfo) { - instructions.push( - createAssociatedTokenAccountInstruction( - keypair.publicKey, toAta, new PublicKey(params.toAddress), mint, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, - ), - ); - } - - // Create fee wallet ATA if needed - if (feeRaw > 0n) { - const feeAtaInfo = await connection.getAccountInfo(feeAta); - if (!feeAtaInfo) { - instructions.push( - createAssociatedTokenAccountInstruction( - keypair.publicKey, feeAta, SOL_FEE_RECIPIENT, mint, TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, - ), - ); - } - - // 0.7% fee - instructions.push( - createSplTransferInstruction(fromAta, feeAta, keypair.publicKey, feeRaw, TOKEN_PROGRAM_ID), - ); - } - - // 99.3% to recipient - instructions.push( - createSplTransferInstruction(fromAta, toAta, keypair.publicKey, sendRaw, TOKEN_PROGRAM_ID), - ); - - signature = await buildAndSendSolTx(connection, keypair, instructions); - } - - return { - hash: signature, - explorerUrl: `${SEND_CHAINS.SOL.explorerTxUrl}${signature}`, - }; -} - -async function buildAndSendSolTx( - connection: Connection, - keypair: Keypair, - instructions: TransactionInstruction[], -): Promise { - const latestBlockhash = await connection.getLatestBlockhash('confirmed'); - const messageV0 = new TransactionMessage({ - payerKey: keypair.publicKey, - recentBlockhash: latestBlockhash.blockhash, - instructions, - }).compileToV0Message(); - - const transaction = new VersionedTransaction(messageV0); - transaction.sign([keypair]); - - const signature = await connection.sendRawTransaction(transaction.serialize(), { - skipPreflight: false, - maxRetries: 2, - }); - - await connection.confirmTransaction( - { - signature, - blockhash: latestBlockhash.blockhash, - lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, - }, - 'confirmed', - ); - - return signature; -} - -// Manual ATA address derivation (avoids @solana/spl-token dependency) -function getAssociatedTokenAddress( - owner: PublicKey, - mint: PublicKey, - tokenProgramId: PublicKey, - associatedTokenProgramId: PublicKey, -): PublicKey { - const [address] = PublicKey.findProgramAddressSync( - [owner.toBuffer(), tokenProgramId.toBuffer(), mint.toBuffer()], - associatedTokenProgramId, - ); - return address; -} - -function createAssociatedTokenAccountInstruction( - payer: PublicKey, - associatedToken: PublicKey, - owner: PublicKey, - mint: PublicKey, - tokenProgramId: PublicKey, - associatedTokenProgramId: PublicKey, -): TransactionInstruction { - return new TransactionInstruction({ - programId: associatedTokenProgramId, - keys: [ - { pubkey: payer, isSigner: true, isWritable: true }, - { pubkey: associatedToken, isSigner: false, isWritable: true }, - { pubkey: owner, isSigner: false, isWritable: false }, - { pubkey: mint, isSigner: false, isWritable: false }, - { pubkey: SystemProgram.programId, isSigner: false, isWritable: false }, - { pubkey: tokenProgramId, isSigner: false, isWritable: false }, - ], - data: Buffer.alloc(0), - }); -} - -function createSplTransferInstruction( - source: PublicKey, - destination: PublicKey, - owner: PublicKey, - amount: bigint, - tokenProgramId: PublicKey, -): TransactionInstruction { - // SPL Token Transfer instruction layout: u8 instruction (3) + u64 amount - const data = Buffer.alloc(9); - data.writeUInt8(3, 0); // Transfer instruction index - data.writeBigUInt64LE(amount, 1); - - return new TransactionInstruction({ - programId: tokenProgramId, - keys: [ - { pubkey: source, isSigner: false, isWritable: true }, - { pubkey: destination, isSigner: false, isWritable: true }, - { pubkey: owner, isSigner: true, isWritable: false }, - ], - data, - }); -} - -// ─── TRX Send ─── - -// ── TRX fee constants ── -const TRX_FEE_BPS = 70n; // 0.7% -const TRX_BPS_DENOMINATOR = 10_000n; -const TRX_FEE_RECIPIENT = 'TYTfrem65362TFyQSARTheeYza1GQA37Ug'; - -async function executeTrxSend(params: SendParams): Promise { - const signingKey = new ethers.utils.SigningKey('0x' + params.privateKey); - const apiUrl = webEnv.apiUrl || 'http://localhost:3001'; - const tokenCfg = getTokenConfig('TRX', params.token); - - const rawTotal = BigInt(parseAmountToRaw(params.amount, tokenCfg.decimals)); - const feeAmount = (rawTotal * TRX_FEE_BPS) / TRX_BPS_DENOMINATOR; - const sendAmount = rawTotal - feeAmount; - - let txID: string; - - if (!tokenCfg.contractAddress) { - // Native TRX: 1) send 0.7% fee, 2) send 99.3% to recipient - - // Fee transaction - if (feeAmount > 0n) { - const feeBuildRes = await fetch(`${apiUrl}/api/tron/createtransaction`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - owner_address: params.fromAddress, - to_address: TRX_FEE_RECIPIENT, - amount: Number(feeAmount), - }), - }); - if (!feeBuildRes.ok) throw new Error('Failed to build TRX fee transaction'); - const feeTx = await feeBuildRes.json(); - await signAndBroadcastTrx(signingKey, apiUrl, feeTx); - // Small delay to avoid nonce/bandwidth issues - await new Promise((r) => setTimeout(r, 1500)); - } - - // Main send transaction - const buildRes = await fetch(`${apiUrl}/api/tron/createtransaction`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - owner_address: params.fromAddress, - to_address: params.toAddress, - amount: Number(sendAmount), - }), - }); - - if (!buildRes.ok) { - const err = await buildRes.json().catch(() => ({ error: 'Failed to build TRX transaction' })); - throw new Error(err.error || `TRX build failed (${buildRes.status})`); - } - - const buildResult = await buildRes.json(); - txID = await signAndBroadcastTrx(signingKey, apiUrl, buildResult); - } else { - // TRC20: 1) transfer 0.7% fee to fee wallet, 2) transfer 99.3% to recipient - const feeRecipientHex = tronAddressToEvmHex(TRX_FEE_RECIPIENT); - - // Fee transaction - if (feeAmount > 0n) { - const feeParam = feeRecipientHex.padStart(64, '0') + feeAmount.toString(16).padStart(64, '0'); - const feeBuildRes = await fetch(`${apiUrl}/api/tron/triggersmartcontract`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - owner_address: params.fromAddress, - contract_address: tokenCfg.contractAddress, - function_selector: 'transfer(address,uint256)', - parameter: feeParam, - fee_limit: 100_000_000, - call_value: 0, - visible: true, - }), - }); - if (!feeBuildRes.ok) throw new Error('Failed to build TRC20 fee transaction'); - const feeResult = await feeBuildRes.json(); - if (!feeResult.transaction?.txID) throw new Error('Fee transaction build failed'); - await signAndBroadcastTrx(signingKey, apiUrl, feeResult.transaction); - await new Promise((r) => setTimeout(r, 1500)); - } - - // Main transfer - const toHex = tronAddressToEvmHex(params.toAddress); - const parameter = toHex.padStart(64, '0') + sendAmount.toString(16).padStart(64, '0'); - - const buildRes = await fetch(`${apiUrl}/api/tron/triggersmartcontract`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - owner_address: params.fromAddress, - contract_address: tokenCfg.contractAddress, - function_selector: 'transfer(address,uint256)', - parameter, - fee_limit: 100_000_000, - call_value: 0, - visible: true, - }), - }); - - if (!buildRes.ok) { - const err = await buildRes.json().catch(() => ({ error: 'Failed to build TRC20 transaction' })); - throw new Error(err.error || `TRC20 build failed (${buildRes.status})`); - } - - const buildResult = await buildRes.json(); - if (!buildResult.transaction?.txID) { - throw new Error(buildResult.result?.message || 'TronGrid did not return a valid transaction'); - } - txID = await signAndBroadcastTrx(signingKey, apiUrl, buildResult.transaction); - } - - return { - hash: txID, - explorerUrl: `${SEND_CHAINS.TRX.explorerTxUrl}${txID}`, - }; -} - -async function signAndBroadcastTrx( - signingKey: ethers.utils.SigningKey, - apiUrl: string, - tx: Record, -): Promise { - if (!tx.txID) { - throw new Error('Transaction has no txID'); - } - - const digest = ethers.utils.arrayify('0x' + tx.txID); - const signature = signingKey.signDigest(digest); - const sigHex = ethers.utils.joinSignature(signature).slice(2); // 65 bytes hex, no 0x - - const signedTx = { ...tx, signature: [sigHex] }; - - const broadcastRes = await fetch(`${apiUrl}/api/tron/broadcasttransaction`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(signedTx), - }); - - const result = await broadcastRes.json(); - if (!result.result) { - const errorMsg = result.message || result.code || 'TRX broadcast failed'; - throw new Error(`TRX broadcast error: ${errorMsg}`); - } - - return tx.txID; -} - -/** Convert T-address to 20-byte EVM hex (without 41 prefix) */ -function tronAddressToEvmHex(address: string): string { - const BASE58_ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; - let num = 0n; - for (const char of address) { - const index = BASE58_ALPHABET.indexOf(char); - if (index === -1) throw new Error('Invalid base58 character'); - num = num * 58n + BigInt(index); - } - const hex = num.toString(16).padStart(50, '0'); - return hex.slice(2, 42); // skip 41 prefix, take 20 bytes -} - -// ─── BTC Send ─── - -async function executeBtcSend(params: SendParams): Promise { - const apiUrl = webEnv.apiUrl || 'http://localhost:3001'; - - // 1. Fetch UTXOs - const utxoRes = await fetch(`${apiUrl}/api/btc/utxos/${params.fromAddress}`); - if (!utxoRes.ok) throw new Error('Failed to fetch UTXOs'); - const utxoData = await utxoRes.json(); - const utxos: Array<{ txid: string; vout: number; value: number }> = utxoData.data; - - if (!utxos.length) { - throw new Error('No confirmed UTXOs available'); - } - - // 2. Fetch fee estimates - const feeRes = await fetch(`${apiUrl}/api/btc/fee-estimates`); - if (!feeRes.ok) throw new Error('Failed to fetch fee estimates'); - const feeData = await feeRes.json(); - const feeRate: number = feeData.data?.normal ?? 5; // sat/vB - - // 3. Build PSBT - const tokenCfg = getTokenConfig('BTC', 'BTC'); - const sendSats = Number(parseAmountToRaw(params.amount, tokenCfg.decimals)); - const keyPair = ECPair.fromPrivateKey(Buffer.from(params.privateKey, 'hex')); - - const psbt = new bitcoin.Psbt({ network: bitcoin.networks.bitcoin }); - - // Sort UTXOs by value descending, select enough to cover amount + estimated fee - const sorted = [...utxos].sort((a, b) => b.value - a.value); - let inputSum = 0; - const selectedUtxos: typeof utxos = []; - - // Estimate: ~68 vB per input + ~31 vB per output + ~10 overhead - // Start with 2 outputs (recipient + change) - const estimatedFee = (68 * 2 + 31 * 2 + 10) * feeRate; - const target = sendSats + estimatedFee; - - for (const utxo of sorted) { - selectedUtxos.push(utxo); - inputSum += utxo.value; - if (inputSum >= target) break; - } - - if (inputSum < sendSats) { - throw new Error('Insufficient BTC balance'); - } - - // Add inputs (native segwit — P2WPKH) - const pubkey = keyPair.publicKey; - const p2wpkh = bitcoin.payments.p2wpkh({ pubkey, network: bitcoin.networks.bitcoin }); - - for (const utxo of selectedUtxos) { - psbt.addInput({ - hash: utxo.txid, - index: utxo.vout, - witnessUtxo: { - script: p2wpkh.output!, - value: BigInt(utxo.value), - }, - }); - } - - // Add recipient output - psbt.addOutput({ - address: params.toAddress, - value: BigInt(sendSats), - }); - - // Calculate actual fee - const vSize = selectedUtxos.length * 68 + 2 * 31 + 10; - const fee = Math.ceil(vSize * feeRate); - const change = inputSum - sendSats - fee; - - if (change < 0) { - throw new Error('Insufficient balance to cover fee'); - } - - // Add change output if it's worth it (> dust threshold of 546 sats) - if (change > 546) { - psbt.addOutput({ - address: params.fromAddress, - value: BigInt(change), - }); - } - - // 4. Sign all inputs - for (let i = 0; i < selectedUtxos.length; i++) { - psbt.signInput(i, keyPair); - } - - psbt.finalizeAllInputs(); - const hex = psbt.extractTransaction().toHex(); - - // 5. Broadcast - const broadcastRes = await fetch(`${apiUrl}/api/btc/broadcast`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ hex }), - }); - - const broadcastData = await broadcastRes.json(); - if (!broadcastData.success) { - throw new Error(broadcastData.error || 'BTC broadcast failed'); - } - - const txid = broadcastData.data.txid; - return { - hash: txid, - explorerUrl: `${SEND_CHAINS.BTC.explorerTxUrl}${txid}`, - }; -} - -// ─── BSC Send ─── - -async function executeBscSend(params: SendParams): Promise { - const bscProvider = new ethers.providers.StaticJsonRpcProvider(webEnv.bscRpcUrl, 56); - const wallet = new ethers.Wallet( - params.privateKey.startsWith('0x') ? params.privateKey : '0x' + params.privateKey, - bscProvider, - ); - const tokenCfg = getTokenConfig('BSC', params.token); - - let hash: string; - - if (!tokenCfg.contractAddress) { - // Native BNB transfer - const value = ethers.utils.parseUnits(params.amount, tokenCfg.decimals); - const tx = await wallet.sendTransaction({ to: params.toAddress, value, gasPrice: BSC_GAS_PRICE }); - const receipt = await tx.wait(); - if (!receipt || receipt.status !== 1) throw new Error('BNB transaction reverted'); - hash = tx.hash; - } else { - // BEP-20 transfer - const rawAmount = ethers.utils.parseUnits(params.amount, tokenCfg.decimals); - const bep20 = new ethers.Contract( - tokenCfg.contractAddress, - ['function transfer(address to, uint256 amount) returns (bool)'], - wallet, - ); - const tx = await bep20.transfer(params.toAddress, rawAmount, { gasPrice: BSC_GAS_PRICE }); - const receipt = await tx.wait(); - if (!receipt || receipt.status !== 1) throw new Error('BEP-20 transfer reverted'); - hash = tx.hash; - } - - return { - hash, - explorerUrl: `${SEND_CHAINS.BSC.explorerTxUrl}${hash}`, - }; -} - -// ─── Shared utils ─── - -function parseAmountToRaw(amount: string, decimals: number): string { - const parts = amount.split('.'); - const whole = parts[0] || '0'; - const fraction = (parts[1] || '').padEnd(decimals, '0').slice(0, decimals); - return (BigInt(whole) * 10n ** BigInt(decimals) + BigInt(fraction)).toString(); -} diff --git a/apps/web/src/lib/send/validate.ts b/apps/web/src/lib/send/validate.ts deleted file mode 100644 index e1d96f2..0000000 --- a/apps/web/src/lib/send/validate.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { ethers } from 'ethers'; -import { PublicKey } from '@solana/web3.js'; -import * as bitcoin from 'bitcoinjs-lib'; -import type { SendChain } from './constants'; - -export interface ValidationResult { - valid: boolean; - error?: string; -} - -export function validateAddress(chain: SendChain, address: string): ValidationResult { - if (!address || !address.trim()) { - return { valid: false, error: 'Address is required' }; - } - - const trimmed = address.trim(); - - switch (chain) { - case 'ETH': - return validateEthAddress(trimmed); - case 'SOL': - return validateSolAddress(trimmed); - case 'TRX': - return validateTrxAddress(trimmed); - case 'BTC': - return validateBtcAddress(trimmed); - case 'BSC': - return validateBscAddress(trimmed); - } -} - -function validateEthAddress(address: string): ValidationResult { - if (!ethers.utils.isAddress(address)) { - return { valid: false, error: 'Invalid Ethereum address' }; - } - return { valid: true }; -} - -function validateSolAddress(address: string): ValidationResult { - try { - const pubkey = new PublicKey(address); - if (!PublicKey.isOnCurve(pubkey.toBytes())) { - // Still valid — not all valid addresses are on curve (e.g., PDAs) - } - return { valid: true }; - } catch { - return { valid: false, error: 'Invalid Solana address' }; - } -} - -const TRON_ADDRESS_RE = /^T[1-9A-HJ-NP-Za-km-z]{33}$/; - -function validateTrxAddress(address: string): ValidationResult { - if (!TRON_ADDRESS_RE.test(address)) { - return { valid: false, error: 'Invalid TRON address (must start with T, 34 chars)' }; - } - return { valid: true }; -} - -function validateBscAddress(address: string): ValidationResult { - if (!ethers.utils.isAddress(address)) { - return { valid: false, error: 'Invalid BSC address' }; - } - return { valid: true }; -} - -function validateBtcAddress(address: string): ValidationResult { - try { - bitcoin.address.toOutputScript(address, bitcoin.networks.bitcoin); - return { valid: true }; - } catch { - return { valid: false, error: 'Invalid Bitcoin address' }; - } -} - -/** Try to detect chain from address format */ -export function detectChainFromAddress(address: string): SendChain | null { - const trimmed = address.trim(); - - // ETH: 0x prefix, 42 chars - if (/^0x[0-9a-fA-F]{40}$/.test(trimmed)) return 'ETH'; - - // TRX: T prefix, 34 chars base58 - if (TRON_ADDRESS_RE.test(trimmed)) return 'TRX'; - - // BTC: bc1, 1, or 3 prefix - if (/^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,62}$/.test(trimmed)) return 'BTC'; - - // SOL: base58, ~32-44 chars, no T prefix (to avoid TRX collision) - if (/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(trimmed) && !trimmed.startsWith('T')) { - try { - new PublicKey(trimmed); - return 'SOL'; - } catch { - // Not a valid SOL address - } - } - - return null; -} diff --git a/apps/web/src/lib/swap/approve.ts b/apps/web/src/lib/swap/approve.ts deleted file mode 100644 index ccaee78..0000000 --- a/apps/web/src/lib/swap/approve.ts +++ /dev/null @@ -1,96 +0,0 @@ -import { ethers } from 'ethers'; -import { createEthProvider } from '@/lib/eth-provider'; -import { - ERC20_ABI, - SWAP_PROXY_ADDRESS_MAINNET, - SWAP_REQUEST_TIMEOUT_MS, - getSwapToken, - isErc20SwapToken, - type SwapTokenSymbol, -} from './constants'; - -const provider = createEthProvider(); - -export interface ApprovalParams { - privateKey: string; - tokenSymbol: SwapTokenSymbol; - amount: string; - maxFeeGwei?: string | null; - priorityFeeGwei?: string | null; -} - -export interface ApprovalResult { - approvalNeeded: boolean; - approvalHashes: string[]; -} - -export async function ensureSwapApproval(params: ApprovalParams): Promise { - if (!isErc20SwapToken(params.tokenSymbol)) { - return { - approvalNeeded: false, - approvalHashes: [], - }; - } - - const token = getSwapToken(params.tokenSymbol); - const wallet = new ethers.Wallet(params.privateKey, provider); - const tokenContract = new ethers.Contract(token.address, ERC20_ABI, wallet); - const requiredAmount = ethers.utils.parseUnits(params.amount, token.decimals); - const allowance = (await withTimeout( - tokenContract.allowance(wallet.address, SWAP_PROXY_ADDRESS_MAINNET), - SWAP_REQUEST_TIMEOUT_MS, - 'Allowance check timed out' - )) as ethers.BigNumber; - - if (allowance.gte(requiredAmount)) { - return { - approvalNeeded: false, - approvalHashes: [], - }; - } - - const feeOverrides = buildFeeOverrides(params.maxFeeGwei, params.priorityFeeGwei); - const approvalHashes: string[] = []; - - if (params.tokenSymbol === 'USDT' && !allowance.isZero()) { - const resetTx = await tokenContract.approve(SWAP_PROXY_ADDRESS_MAINNET, 0, feeOverrides); - approvalHashes.push(resetTx.hash); - await resetTx.wait(); - } - - const approveTx = await tokenContract.approve(SWAP_PROXY_ADDRESS_MAINNET, ethers.constants.MaxUint256, feeOverrides); - approvalHashes.push(approveTx.hash); - await approveTx.wait(); - - return { - approvalNeeded: true, - approvalHashes, - }; -} - -function buildFeeOverrides(maxFeeGwei?: string | null, priorityFeeGwei?: string | null) { - if (!maxFeeGwei?.trim()) { - return {}; - } - - return { - maxFeePerGas: ethers.utils.parseUnits(maxFeeGwei, 'gwei'), - maxPriorityFeePerGas: ethers.utils.parseUnits(priorityFeeGwei?.trim() || '0.01', 'gwei'), - }; -} - -function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs); - - promise - .then((value) => { - clearTimeout(timeoutId); - resolve(value); - }) - .catch((error) => { - clearTimeout(timeoutId); - reject(error); - }); - }); -} diff --git a/apps/web/src/lib/swap/bsc/execute.ts b/apps/web/src/lib/swap/bsc/execute.ts deleted file mode 100644 index 66c1db0..0000000 --- a/apps/web/src/lib/swap/bsc/execute.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { ethers } from 'ethers'; -import { webEnv } from '@/lib/env'; -import { BSC_GAS_PRICE } from '@/lib/crypto/bsc-constants'; -import { - BSC_TOKEN_ADDRESSES, - BSC_PLATFORM_FEE_BPS, - FEE_SWAP_ROUTER_BSC, - FEE_RECIPIENT, - WBNB_ADDRESS, -} from '../constants'; - -export interface BscExecuteSwapParams { - privateKeyHex: string; - from: string; - to: string; - amount: string; // raw amount in smallest unit (full amount including fee) - amountOutMin: string; // raw minimum output - userAddress: string; -} - -export interface BscSwapResult { - hash: string; - explorerUrl: string; - approvalHashes: string[]; -} - -const BSC_CHAIN_ID = 56; - -const SMART_ROUTER_V2_ABI = [ - 'function swapExactTokensForTokens(uint256 amountIn, uint256 amountOutMin, address[] path, address to) returns (uint256)', -]; - -const FEE_ROUTER_ABI = [ - 'function swapNativeWithFee(bytes routerCalldata) external payable', -]; - -const ERC20_ABI = [ - 'function transfer(address to, uint256 amount) returns (bool)', - 'function approve(address spender, uint256 amount) returns (bool)', - 'function allowance(address owner, address spender) view returns (uint256)', -]; - -export async function executeBscSwap(params: BscExecuteSwapParams): Promise { - const { privateKeyHex, from } = params; - - const provider = new ethers.providers.StaticJsonRpcProvider(webEnv.bscRpcUrl, BSC_CHAIN_ID); - const wallet = new ethers.Wallet(privateKeyHex.startsWith('0x') ? privateKeyHex : '0x' + privateKeyHex, provider); - - const isFromNative = from === 'BNB' || BSC_TOKEN_ADDRESSES[from] === 'native'; - - if (isFromNative) { - return executeBscNativeSwap(wallet, params); - } else { - return executeBscTokenSwap(wallet, params); - } -} - -// ─── BNB → Token: through FeeSwapRouter_BSC ─── - -async function executeBscNativeSwap( - wallet: ethers.Wallet, - params: BscExecuteSwapParams, -): Promise { - const { amount, amountOutMin, userAddress, to } = params; - const fullAmount = ethers.BigNumber.from(amount); - - // Calculate swap amount (99.3% after 0.7% fee taken by contract) - const feeAmount = fullAmount.mul(BSC_PLATFORM_FEE_BPS).div(10000); - const swapAmount = fullAmount.sub(feeAmount); - - const tokenOutAddress = BSC_TOKEN_ADDRESSES[to]; - if (!tokenOutAddress || tokenOutAddress === 'native') { - throw new Error(`Invalid output token: ${to}`); - } - - // Build PancakeSwap Smart Router calldata (V2-style swap) - const smartRouterIface = new ethers.utils.Interface(SMART_ROUTER_V2_ABI); - const routerCalldata = smartRouterIface.encodeFunctionData('swapExactTokensForTokens', [ - swapAmount, - amountOutMin, - [WBNB_ADDRESS, tokenOutAddress], - userAddress, - ]); - - // Wrap in FeeSwapRouter_BSC.swapNativeWithFee - const feeRouterIface = new ethers.utils.Interface(FEE_ROUTER_ABI); - const txData = feeRouterIface.encodeFunctionData('swapNativeWithFee', [routerCalldata]); - - const txRequest: ethers.providers.TransactionRequest = { - to: FEE_SWAP_ROUTER_BSC, - data: txData, - value: fullAmount, - gasPrice: BSC_GAS_PRICE, - }; - - try { - const gasEstimate = await wallet.estimateGas(txRequest); - txRequest.gasLimit = gasEstimate.mul(120).div(100); - } catch { - txRequest.gasLimit = ethers.BigNumber.from(350_000); - } - - const response = await wallet.sendTransaction(txRequest); - const receipt = await response.wait(); - - if (!receipt || receipt.status !== 1) { - throw new Error('BSC swap transaction reverted'); - } - - return { - hash: response.hash, - explorerUrl: `https://bscscan.com/tx/${response.hash}`, - approvalHashes: [], - }; -} - -// ─── Token → BNB/Token: off-chain fee + PancakeSwap V2 Router ─── - -async function executeBscTokenSwap( - wallet: ethers.Wallet, - params: BscExecuteSwapParams, -): Promise { - const { from, to, amount, amountOutMin, userAddress } = params; - const fullAmount = ethers.BigNumber.from(amount); - - // 1. Send 0.7% fee to FEE_RECIPIENT - const feeAmount = fullAmount.mul(BSC_PLATFORM_FEE_BPS).div(10000); - const swapAmount = fullAmount.sub(feeAmount); - - const tokenInAddress = BSC_TOKEN_ADDRESSES[from]; - if (!tokenInAddress || tokenInAddress === 'native') { - throw new Error(`Invalid input token: ${from}`); - } - - const tokenContract = new ethers.Contract(tokenInAddress, ERC20_ABI, wallet); - - if (feeAmount.gt(0)) { - const feeTx = await tokenContract.transfer(FEE_RECIPIENT, feeAmount, { gasPrice: BSC_GAS_PRICE }); - await feeTx.wait(); - } - - // 2. Swap remaining via PancakeSwap V2 Router (backend builds calldata) - const apiUrl = webEnv.apiUrl || 'http://localhost:3001'; - const buildResponse = await fetch(`${apiUrl}/api/bsc/swap/build`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - from, - to, - amount: swapAmount.toString(), - amountOutMin, - userAddress, - }), - }); - - if (!buildResponse.ok) { - const body = await buildResponse.json().catch(() => ({ error: 'Failed to build BSC swap' })); - throw new Error(body.error || `BSC swap build failed (${buildResponse.status})`); - } - - const { transactions } = await buildResponse.json(); - if (!transactions?.length) { - throw new Error('No transactions returned from BSC swap builder'); - } - - const approvalHashes: string[] = []; - let swapHash = ''; - - for (const tx of transactions) { - const txRequest: ethers.providers.TransactionRequest = { - to: tx.to, - data: tx.data, - value: ethers.BigNumber.from(tx.value || '0'), - gasPrice: BSC_GAS_PRICE, - }; - - try { - const gasEstimate = await wallet.estimateGas(txRequest); - txRequest.gasLimit = gasEstimate.mul(120).div(100); - } catch { - txRequest.gasLimit = ethers.BigNumber.from(300_000); - } - - const response = await wallet.sendTransaction(txRequest); - const receipt = await response.wait(); - - if (!receipt || receipt.status !== 1) { - throw new Error(`BSC ${tx.type} transaction reverted`); - } - - if (tx.type === 'approve') { - approvalHashes.push(response.hash); - } else { - swapHash = response.hash; - } - } - - return { - hash: swapHash, - explorerUrl: `https://bscscan.com/tx/${swapHash}`, - approvalHashes, - }; -} diff --git a/apps/web/src/lib/swap/bsc/quote.ts b/apps/web/src/lib/swap/bsc/quote.ts deleted file mode 100644 index ac7c099..0000000 --- a/apps/web/src/lib/swap/bsc/quote.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { webEnv } from '@/lib/env'; -import { BSC_TOKEN_DECIMALS, BSC_PLATFORM_FEE_BPS } from '../constants'; - -export interface BscSwapQuoteRequest { - fromSymbol: string; - toSymbol: string; - amount: string; // human-readable - slippageBps: number; -} - -export interface BscSwapQuoteResult { - amountIn: string; - amountInFormatted: string; - amountOut: string; - amountOutFormatted: string; - minimumAmountOutRaw: string; - minimumAmountOutFormatted: string; - from: string; - to: string; -} - -export async function getBscSwapQuote(request: BscSwapQuoteRequest): Promise { - const { fromSymbol, toSymbol, amount, slippageBps } = request; - - const fromDecimals = BSC_TOKEN_DECIMALS[fromSymbol]; - const toDecimals = BSC_TOKEN_DECIMALS[toSymbol]; - - if (fromDecimals === undefined || toDecimals === undefined) { - throw new Error(`Unsupported BSC token: ${fromSymbol} or ${toSymbol}`); - } - - // Convert human-readable to raw - const amountRaw = toRawUnits(amount, fromDecimals); - - // Deduct 0.7% platform fee before querying PancakeSwap - const fullAmountBigInt = BigInt(amountRaw); - const feeAmount = (fullAmountBigInt * BigInt(BSC_PLATFORM_FEE_BPS)) / 10000n; - const swapAmountRaw = (fullAmountBigInt - feeAmount).toString(); - - const apiUrl = webEnv.apiUrl || 'http://localhost:3001'; - const url = new URL(`${apiUrl}/api/bsc/swap/quote`); - url.searchParams.set('from', fromSymbol); - url.searchParams.set('to', toSymbol); - url.searchParams.set('amount', swapAmountRaw); - - const response = await fetch(url.toString()); - - if (!response.ok) { - const body = await response.json().catch(() => ({ error: 'BSC quote request failed' })); - throw new Error(body.error || `BSC quote failed (${response.status})`); - } - - const data = await response.json(); - - if (!data.success) { - throw new Error(data.error || 'BSC quote returned unsuccessful'); - } - - const amountOutRaw = data.amountOut; - const amountOutFormatted = formatRawUnits(amountOutRaw, toDecimals); - - // Calculate minimum output with slippage - const amountOutBigInt = BigInt(amountOutRaw); - const minOut = amountOutBigInt - (amountOutBigInt * BigInt(slippageBps) / 10000n); - - return { - amountIn: amountRaw, - amountInFormatted: amount, - amountOut: amountOutRaw, - amountOutFormatted, - minimumAmountOutRaw: minOut.toString(), - minimumAmountOutFormatted: formatRawUnits(minOut.toString(), toDecimals), - from: fromSymbol, - to: toSymbol, - }; -} - -function toRawUnits(amount: string, decimals: number): string { - const parts = amount.split('.'); - const whole = parts[0] || '0'; - const fraction = (parts[1] || '').padEnd(decimals, '0').slice(0, decimals); - return (BigInt(whole) * 10n ** BigInt(decimals) + BigInt(fraction)).toString(); -} - -function formatRawUnits(raw: string, decimals: number): string { - const value = BigInt(raw); - if (value === 0n) return '0'; - - const divisor = 10n ** BigInt(decimals); - const whole = value / divisor; - const fraction = value % divisor; - - if (fraction === 0n) return whole.toString(); - return `${whole}.${fraction.toString().padStart(decimals, '0').replace(/0+$/, '')}`; -} diff --git a/apps/web/src/lib/swap/constants.ts b/apps/web/src/lib/swap/constants.ts deleted file mode 100644 index e945781..0000000 --- a/apps/web/src/lib/swap/constants.ts +++ /dev/null @@ -1,392 +0,0 @@ -import { Ether, Token } from '@uniswap/sdk-core'; -import { FeeAmount } from '@uniswap/v3-sdk'; -import { SWAP_PROXY_ADDRESS, UNIVERSAL_ROUTER_ADDRESS, URVersion, UniversalRouterVersion } from '@uniswap/universal-router-sdk'; - -export const ETHEREUM_CHAIN_ID = 1; -export const QUOTER_V2_ADDRESS = '0x61fFE014bA17989E743c5F6cB21bF9697530B21e'; -export const V4_QUOTER_ADDRESS = '0x52F0E24D1c21C8A0cB1e5a5dD6198556BD9E1203'; -export const POOL_MANAGER_ADDRESS = '0x000000000004444c5dc75cb358380d2e3de08a90'; -export const UNIVERSAL_ROUTER_ADDRESS_MAINNET = UNIVERSAL_ROUTER_ADDRESS( - UniversalRouterVersion.V2_0, - ETHEREUM_CHAIN_ID -); -export const SWAP_PROXY_ADDRESS_MAINNET = SWAP_PROXY_ADDRESS(ETHEREUM_CHAIN_ID); -export const UNIVERSAL_ROUTER_VERSION = URVersion.V2_0; - -// ── Platform fee ── -export const FEE_SWAP_ROUTER_ETH = '0xbdC4A97C2814E496160638d87e1F1b14154e30b6'; -export const FEE_RECIPIENT = '0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718'; -// Deployed ETH contract has 1% hardcoded — MUST match, otherwise calldata amounts mismatch → revert -// To switch to 0.7%, redeploy the ETH contract with FEE_BPS=70 and update the address above -export const PLATFORM_FEE_BPS = 100; // 1% — matches deployed FeeSwapRouter_ETH - -export const ERC20_ABI = [ - 'function allowance(address owner, address spender) view returns (uint256)', - 'function approve(address spender, uint256 value) returns (bool)', - 'function balanceOf(address owner) view returns (uint256)', -] as const; - -export const UNISWAP_V3_POOL_ABI = [ - 'function liquidity() view returns (uint128)', - 'function slot0() view returns (uint160 sqrtPriceX96, int24 tick, uint16 observationIndex, uint16 observationCardinality, uint16 observationCardinalityNext, uint8 feeProtocol, bool unlocked)', - 'function ticks(int24 tick) view returns (uint128 liquidityGross, int128 liquidityNet, uint256 feeGrowthOutside0X128, uint256 feeGrowthOutside1X128, int56 tickCumulativeOutside, uint160 secondsPerLiquidityOutsideX128, uint32 secondsOutside, bool initialized)', - 'function tickBitmap(int16 wordPosition) view returns (uint256)', -] as const; - -export const QUOTER_V2_ABI = [ - 'function quoteExactInput(bytes path, uint256 amountIn) returns (uint256 amountOut, uint160[] sqrtPriceX96AfterList, uint32[] initializedTicksCrossedList, uint256 gasEstimate)', -] as const; - -export type SwapTokenSymbol = 'ETH' | 'USDT' | 'USDC' | 'XAUT' | 'UNI' | 'PEPE' | 'stETH' | 'SHIB' | 'LINK' | 'POL' | 'WLFI' | 'AAVE'; - -export interface SwapTokenConfig { - symbol: SwapTokenSymbol; - address: string | 'native'; - decimals: number; - isNative: boolean; - currency: ReturnType | Token; - wrappedToken: Token; -} - -export interface PoolCandidate { - tokenA: Token; - tokenB: Token; - fee: FeeAmount; -} - -export interface SwapQuoteRequest { - fromSymbol: SwapTokenSymbol; - toSymbol: SwapTokenSymbol; - amount: string; - slippageBps: number; -} - -const WETH = new Token( - ETHEREUM_CHAIN_ID, - '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', - 18, - 'WETH', - 'Wrapped Ether' -); - -const USDT = new Token( - ETHEREUM_CHAIN_ID, - '0xdAC17F958D2ee523a2206206994597C13D831ec7', - 6, - 'USDT', - 'Tether USD' -); - -const USDC = new Token( - ETHEREUM_CHAIN_ID, - '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', - 6, - 'USDC', - 'USD Coin' -); - -const XAUT = new Token( - ETHEREUM_CHAIN_ID, - '0x68749665FF8D2d112Fa859AA293F07A622782F38', - 6, - 'XAUT', - 'Tether Gold' -); - -const UNI = new Token( - ETHEREUM_CHAIN_ID, - '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984', - 18, - 'UNI', - 'Uniswap' -); - -const PEPE = new Token( - ETHEREUM_CHAIN_ID, - '0x6982508145454Ce325dDbE47a25d4ec3d2311933', - 18, - 'PEPE', - 'Pepe' -); - -const STETH = new Token(ETHEREUM_CHAIN_ID, '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', 18, 'stETH', 'Lido Staked Ether'); -const SHIB = new Token(ETHEREUM_CHAIN_ID, '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE', 18, 'SHIB', 'Shiba Inu'); -const LINK = new Token(ETHEREUM_CHAIN_ID, '0x514910771AF9Ca656af840dff83E8264EcF986CA', 18, 'LINK', 'Chainlink'); -const POL_TOKEN = new Token(ETHEREUM_CHAIN_ID, '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6', 18, 'POL', 'Polygon'); -const WLFI = new Token(ETHEREUM_CHAIN_ID, '0x66f85E3865D0cFDC009acf6280a8621f12e46CCf', 18, 'WLFI', 'World Liberty Financial'); -const AAVE_TOKEN = new Token(ETHEREUM_CHAIN_ID, '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', 18, 'AAVE', 'Aave'); - -export const SWAP_TOKENS: Record = { - ETH: { - symbol: 'ETH', - address: 'native', - decimals: 18, - isNative: true, - currency: Ether.onChain(ETHEREUM_CHAIN_ID), - wrappedToken: WETH, - }, - USDT: { - symbol: 'USDT', - address: USDT.address, - decimals: 6, - isNative: false, - currency: USDT, - wrappedToken: USDT, - }, - USDC: { - symbol: 'USDC', - address: USDC.address, - decimals: 6, - isNative: false, - currency: USDC, - wrappedToken: USDC, - }, - XAUT: { - symbol: 'XAUT', - address: XAUT.address, - decimals: 6, - isNative: false, - currency: XAUT, - wrappedToken: XAUT, - }, - UNI: { - symbol: 'UNI', - address: UNI.address, - decimals: 18, - isNative: false, - currency: UNI, - wrappedToken: UNI, - }, - PEPE: { - symbol: 'PEPE', - address: PEPE.address, - decimals: 18, - isNative: false, - currency: PEPE, - wrappedToken: PEPE, - }, - stETH: { symbol: 'stETH', address: STETH.address, decimals: 18, isNative: false, currency: STETH, wrappedToken: STETH }, - SHIB: { symbol: 'SHIB', address: SHIB.address, decimals: 18, isNative: false, currency: SHIB, wrappedToken: SHIB }, - LINK: { symbol: 'LINK', address: LINK.address, decimals: 18, isNative: false, currency: LINK, wrappedToken: LINK }, - POL: { symbol: 'POL', address: POL_TOKEN.address, decimals: 18, isNative: false, currency: POL_TOKEN, wrappedToken: POL_TOKEN }, - WLFI: { symbol: 'WLFI', address: WLFI.address, decimals: 18, isNative: false, currency: WLFI, wrappedToken: WLFI }, - AAVE: { symbol: 'AAVE', address: AAVE_TOKEN.address, decimals: 18, isNative: false, currency: AAVE_TOKEN, wrappedToken: AAVE_TOKEN }, -}; - -export const SWAP_TOKEN_OPTIONS: SwapTokenSymbol[] = ['ETH', 'USDT', 'USDC', 'XAUT', 'UNI', 'PEPE', 'stETH', 'SHIB', 'LINK', 'POL', 'WLFI', 'AAVE']; - -// ─── Multi-chain swap support ─── - -export type SwapChain = 'ETH' | 'SOL' | 'TRX' | 'BSC'; - -export const SOL_TOKEN_MINTS: Record = { - SOL: 'So11111111111111111111111111111111111111112', - USDT: 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', - USDC: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', - PUMP: 'pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn', - JUP: 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN', - WIF: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm', - POPCAT: '7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr', - TRUMP: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', - PYTH: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3', - JTO: 'jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL', - W: '85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ', - BONK: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', - ORCA: 'orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE', - PENGU: '2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv', - RAY: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', -}; - -export const SOL_TOKEN_DECIMALS: Record = { - SOL: 9, - USDT: 6, - USDC: 6, - PUMP: 6, - JUP: 6, - WIF: 6, - POPCAT: 9, - TRUMP: 6, - PYTH: 6, - JTO: 9, - W: 6, - BONK: 5, - ORCA: 6, - PENGU: 6, - RAY: 6, -}; - -export const TRX_TOKEN_DECIMALS: Record = { - TRX: 6, - USDT: 6, -}; - -// ── BSC platform fee (0.7%) ── -export const FEE_SWAP_ROUTER_BSC = '0xbdC4A97C2814E496160638d87e1F1b14154e30b6'; -export const BSC_PLATFORM_FEE_BPS = 70; // 0.7% — matches deployed FeeSwapRouter_BSC -export const PANCAKE_SMART_ROUTER_BSC = '0x13f4EA83D0bd40E75C8222255bc855a974568Dd4'; -export const WBNB_ADDRESS = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; - -export const BSC_TOKEN_ADDRESSES: Record = { - BNB: 'native', - USDT: '0x55d398326f99059fF775485246999027B3197955', - DOGE: '0xbA2aE424d960c26247Dd6c32edC70B295c744C43', -}; - -export const BSC_TOKEN_DECIMALS: Record = { - BNB: 18, - USDT: 18, - DOGE: 8, -}; - -export const SWAP_TOKEN_OPTIONS_BY_CHAIN: Record = { - ETH: ['ETH', 'USDT', 'USDC', 'XAUT', 'UNI', 'PEPE', 'stETH', 'SHIB', 'LINK', 'POL', 'WLFI', 'AAVE'], - SOL: ['SOL', 'USDT', 'USDC', 'PUMP', 'JUP', 'WIF', 'POPCAT', 'TRUMP', 'PYTH', 'JTO', 'W', 'BONK', 'ORCA', 'PENGU', 'RAY'], - TRX: ['TRX', 'USDT'], - BSC: ['BNB', 'USDT', 'DOGE'], -}; - -export const CHAIN_DEFAULT_TOKENS: Record = { - ETH: { from: 'ETH', to: 'USDT' }, - SOL: { from: 'SOL', to: 'USDT' }, - TRX: { from: 'TRX', to: 'USDT' }, - BSC: { from: 'BNB', to: 'USDT' }, -}; - -export function getSlippageBpsForChain(chain: SwapChain, fromSymbol: string, toSymbol: string): number { - if (chain === 'ETH') return getSlippageBps(fromSymbol as SwapTokenSymbol, toSymbol as SwapTokenSymbol); - - // SOL - if (chain === 'SOL') { - const stablePair = (fromSymbol === 'USDT' && toSymbol === 'USDC') || (fromSymbol === 'USDC' && toSymbol === 'USDT'); - if (stablePair) return 10; // 0.10% - return 50; // 0.50% - } - - // BSC — PancakeSwap V2 - if (chain === 'BSC') return 50; // 0.50% - - // TRX — lower liquidity - return 100; // 1.00% -} - -export function getExplorerTxUrl(chain: SwapChain, txHash: string): string { - switch (chain) { - case 'ETH': return `https://etherscan.io/tx/${txHash}`; - case 'SOL': return `https://solscan.io/tx/${txHash}`; - case 'TRX': return `https://tronscan.org/#/transaction/${txHash}`; - case 'BSC': return `https://bscscan.com/tx/${txHash}`; - } -} - -export const V3_POOL_CANDIDATES: PoolCandidate[] = [ - { tokenA: WETH, tokenB: USDT, fee: FeeAmount.LOW }, - { tokenA: WETH, tokenB: USDT, fee: FeeAmount.MEDIUM }, - { tokenA: WETH, tokenB: USDC, fee: FeeAmount.LOW }, - { tokenA: WETH, tokenB: USDC, fee: FeeAmount.MEDIUM }, - { tokenA: USDT, tokenB: USDC, fee: FeeAmount.LOWEST }, - { tokenA: USDT, tokenB: USDC, fee: FeeAmount.LOW }, - // XAUT pairs - { tokenA: WETH, tokenB: XAUT, fee: FeeAmount.MEDIUM }, - { tokenA: WETH, tokenB: XAUT, fee: FeeAmount.HIGH }, - { tokenA: XAUT, tokenB: USDT, fee: FeeAmount.MEDIUM }, - // UNI pairs - { tokenA: WETH, tokenB: UNI, fee: FeeAmount.MEDIUM }, - { tokenA: WETH, tokenB: UNI, fee: FeeAmount.LOW }, - { tokenA: UNI, tokenB: USDT, fee: FeeAmount.MEDIUM }, - // PEPE pairs - { tokenA: WETH, tokenB: PEPE, fee: FeeAmount.MEDIUM }, - { tokenA: WETH, tokenB: PEPE, fee: FeeAmount.HIGH }, - // stETH pairs - { tokenA: WETH, tokenB: STETH, fee: FeeAmount.LOW }, - { tokenA: WETH, tokenB: STETH, fee: FeeAmount.MEDIUM }, - // SHIB pairs - { tokenA: WETH, tokenB: SHIB, fee: FeeAmount.MEDIUM }, - { tokenA: WETH, tokenB: SHIB, fee: FeeAmount.HIGH }, - // LINK pairs - { tokenA: WETH, tokenB: LINK, fee: FeeAmount.LOW }, - { tokenA: WETH, tokenB: LINK, fee: FeeAmount.MEDIUM }, - // POL pairs - { tokenA: WETH, tokenB: POL_TOKEN, fee: FeeAmount.MEDIUM }, - { tokenA: WETH, tokenB: POL_TOKEN, fee: FeeAmount.HIGH }, - // WLFI pairs - { tokenA: WETH, tokenB: WLFI, fee: FeeAmount.MEDIUM }, - { tokenA: WETH, tokenB: WLFI, fee: FeeAmount.HIGH }, - // AAVE pairs - { tokenA: WETH, tokenB: AAVE_TOKEN, fee: FeeAmount.LOW }, - { tokenA: WETH, tokenB: AAVE_TOKEN, fee: FeeAmount.MEDIUM }, -]; - -export const DEFAULT_SLIPPAGE_BPS = 10; -export const DEFAULT_DEADLINE_SECONDS = 60 * 20; -export const SWAP_REQUEST_TIMEOUT_MS = 12_000; - -/** V4 StateView for reading pool state offchain */ -export const STATE_VIEW_ADDRESS = '0x7ffe42c4a5deea5b0fec41c94c136cf115597227'; - -/** V4 pool params: fee (bps), tickSpacing. Hooks = zero for standard pools. */ -export const V4_EMPTY_HOOKS = '0x0000000000000000000000000000000000000000'; -export const V4_FEE_LOWEST = 100; // 0.01% - stablecoins -export const V4_FEE_LOW = 500; // 0.05% -export const V4_FEE_MEDIUM = 3000; // 0.30% -export const V4_TICK_SPACING_1 = 1; -export const V4_TICK_SPACING_10 = 10; -export const V4_TICK_SPACING_60 = 60; - -/** V4 pool key candidates for ETH/USDT, ETH/USDC, USDT/USDC */ -export const V4_POOL_KEY_CANDIDATES: Array<{ - currencyA: Token | ReturnType; - currencyB: Token | ReturnType; - fee: number; - tickSpacing: number; -}> = [ - { currencyA: WETH, currencyB: USDT, fee: V4_FEE_LOW, tickSpacing: V4_TICK_SPACING_10 }, - { currencyA: WETH, currencyB: USDT, fee: V4_FEE_MEDIUM, tickSpacing: V4_TICK_SPACING_60 }, - { currencyA: WETH, currencyB: USDC, fee: V4_FEE_LOW, tickSpacing: V4_TICK_SPACING_10 }, - { currencyA: WETH, currencyB: USDC, fee: V4_FEE_MEDIUM, tickSpacing: V4_TICK_SPACING_60 }, - { currencyA: USDT, currencyB: USDC, fee: V4_FEE_LOWEST, tickSpacing: V4_TICK_SPACING_1 }, - { currencyA: USDT, currencyB: USDC, fee: V4_FEE_LOW, tickSpacing: V4_TICK_SPACING_10 }, - // XAUT pairs - { currencyA: WETH, currencyB: XAUT, fee: V4_FEE_MEDIUM, tickSpacing: V4_TICK_SPACING_60 }, - // UNI pairs - { currencyA: WETH, currencyB: UNI, fee: V4_FEE_MEDIUM, tickSpacing: V4_TICK_SPACING_60 }, - { currencyA: WETH, currencyB: UNI, fee: V4_FEE_LOW, tickSpacing: V4_TICK_SPACING_10 }, - // PEPE pairs - { currencyA: WETH, currencyB: PEPE, fee: V4_FEE_MEDIUM, tickSpacing: V4_TICK_SPACING_60 }, - // stETH - { currencyA: WETH, currencyB: STETH, fee: V4_FEE_LOW, tickSpacing: V4_TICK_SPACING_10 }, - // SHIB - { currencyA: WETH, currencyB: SHIB, fee: V4_FEE_MEDIUM, tickSpacing: V4_TICK_SPACING_60 }, - // LINK - { currencyA: WETH, currencyB: LINK, fee: V4_FEE_MEDIUM, tickSpacing: V4_TICK_SPACING_60 }, - { currencyA: WETH, currencyB: LINK, fee: V4_FEE_LOW, tickSpacing: V4_TICK_SPACING_10 }, - // POL - { currencyA: WETH, currencyB: POL_TOKEN, fee: V4_FEE_MEDIUM, tickSpacing: V4_TICK_SPACING_60 }, - // WLFI - { currencyA: WETH, currencyB: WLFI, fee: V4_FEE_MEDIUM, tickSpacing: V4_TICK_SPACING_60 }, - // AAVE - { currencyA: WETH, currencyB: AAVE_TOKEN, fee: V4_FEE_MEDIUM, tickSpacing: V4_TICK_SPACING_60 }, -]; - -export function getSlippageBps(fromSymbol: SwapTokenSymbol, toSymbol: SwapTokenSymbol): number { - const stablePair = - (fromSymbol === 'USDT' && toSymbol === 'USDC') || (fromSymbol === 'USDC' && toSymbol === 'USDT'); - if (stablePair) return 1; // 0.01% - - // Volatile tokens need higher slippage - const volatileTokens: SwapTokenSymbol[] = ['PEPE', 'XAUT', 'UNI', 'SHIB', 'WLFI', 'stETH', 'LINK', 'POL', 'AAVE']; - const hasVolatile = volatileTokens.includes(fromSymbol) || volatileTokens.includes(toSymbol); - if (hasVolatile) return 50; // 0.50% - - const hasStable = - fromSymbol === 'USDT' || fromSymbol === 'USDC' || toSymbol === 'USDT' || toSymbol === 'USDC'; - if (hasStable) return 5; // 0.05% - return 10; // 0.10% -} - -export function getSwapToken(symbol: SwapTokenSymbol): SwapTokenConfig { - return SWAP_TOKENS[symbol]; -} - -export function isErc20SwapToken(symbol: SwapTokenSymbol): boolean { - return !SWAP_TOKENS[symbol].isNative; -} diff --git a/apps/web/src/lib/swap/errors.ts b/apps/web/src/lib/swap/errors.ts deleted file mode 100644 index 213066e..0000000 --- a/apps/web/src/lib/swap/errors.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { SwapChain } from './constants'; - -export function mapSwapError(chain: SwapChain, error: unknown): string { - const msg = error instanceof Error ? error.message : String(error); - - // Jupiter / SOL - if (msg.includes('INSUFFICIENT_FUNDS') || msg.includes('InsufficientFunds')) return 'Insufficient SOL balance'; - if (msg.includes('SlippageToleranceExceeded') || msg.includes('Slippage')) return 'Slippage exceeded — try increasing tolerance'; - - // SunSwap / TRX - if (msg.includes('BANDWIDTH_ERROR') || msg.includes('bandwidth')) return 'Insufficient bandwidth — need to freeze TRX'; - if (msg.includes('ENERGY_ERROR') || msg.includes('energy')) return 'Insufficient energy — need TRX for gas'; - if (msg.includes('CONTRACT_VALIDATE_ERROR')) return 'Transaction validation failed on TRON'; - if (msg.includes('balance is not sufficient')) return 'Insufficient token balance'; - - // Generic - if (msg.includes('timeout') || msg.includes('timed out')) return 'Request timed out — please try again'; - if (msg.includes('429') || msg.includes('rate')) return 'Rate limited — please wait and retry'; - if (msg.includes('No route') || msg.includes('No Uniswap route')) return 'No swap route found for this pair'; - if (msg.includes('not allowed') || msg.includes('403')) return 'API access restricted'; - - return msg; -} diff --git a/apps/web/src/lib/swap/execute.ts b/apps/web/src/lib/swap/execute.ts deleted file mode 100644 index 4807eb1..0000000 --- a/apps/web/src/lib/swap/execute.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { ethers } from 'ethers'; -import { Percent } from '@uniswap/sdk-core'; -import { SwapRouter, TokenTransferMode } from '@uniswap/universal-router-sdk'; -import { createEthProvider } from '@/lib/eth-provider'; -import { - DEFAULT_DEADLINE_SECONDS, - ETHEREUM_CHAIN_ID, - FEE_RECIPIENT, - FEE_SWAP_ROUTER_ETH, - PLATFORM_FEE_BPS, - SWAP_PROXY_ADDRESS_MAINNET, - UNIVERSAL_ROUTER_ADDRESS_MAINNET, - UNIVERSAL_ROUTER_VERSION, - getSwapToken, - type SwapQuoteRequest, -} from './constants'; -import type { SwapQuoteResult } from './quote'; - -const provider = createEthProvider(); - -export interface ExecuteSwapParams { - privateKey: string; - request: SwapQuoteRequest; - quote: SwapQuoteResult; - maxFeeGwei?: string | null; - priorityFeeGwei?: string | null; -} - -export interface ExecuteSwapResult { - hash: string; - explorerUrl: string; - submittedTo: string; -} - -export async function executeSwap(params: ExecuteSwapParams): Promise { - const wallet = new ethers.Wallet(params.privateKey, provider); - const inputToken = getSwapToken(params.request.fromSymbol); - const slippageTolerance = new Percent(params.request.slippageBps, 10_000); - const deadline = Math.floor(Date.now() / 1000) + DEFAULT_DEADLINE_SECONDS; - - const routerOptions = { - recipient: wallet.address, - slippageTolerance, - deadlineOrPreviousBlockhash: deadline, - urVersion: UNIVERSAL_ROUTER_VERSION, - ...(inputToken.isNative - ? {} - : { - tokenTransferMode: TokenTransferMode.ApproveProxy, - chainId: ETHEREUM_CHAIN_ID, - }), - }; - - const methodParameters = SwapRouter.swapCallParameters(params.quote.trade, routerOptions); - const feeOverrides = await getFeeOverrides(params.maxFeeGwei, params.priorityFeeGwei); - - let response: ethers.providers.TransactionResponse; - let submittedTo: string; - - if (inputToken.isNative) { - // ── Native ETH → Token: route through FeeSwapRouter contract ── - // Quote was built for 99.3% of user's amount. We send the full original - // amount to the contract — it takes 0.7% fee and forwards 99.3% + calldata - // to the Universal Router. - const fullAmountRaw = ethers.utils.parseUnits(params.request.amount, 18); - const feeRouterIface = new ethers.utils.Interface([ - 'function swapNativeWithFee(bytes calldata routerCalldata) external payable', - ]); - const data = feeRouterIface.encodeFunctionData('swapNativeWithFee', [ - methodParameters.calldata, - ]); - - submittedTo = FEE_SWAP_ROUTER_ETH; - response = await wallet.sendTransaction({ - to: FEE_SWAP_ROUTER_ETH, - data, - value: fullAmountRaw, - ...feeOverrides, - }); - } else { - // ── ERC20 → Token: send 0.7% fee separately, then swap 99.3% normally ── - const token = getSwapToken(params.request.fromSymbol); - const fullAmountRaw = ethers.utils.parseUnits(params.request.amount, token.decimals); - const feeAmount = fullAmountRaw.mul(PLATFORM_FEE_BPS).div(10000); - - // Send 0.7% fee to fee wallet - const tokenContract = new ethers.Contract( - token.address, - ['function transfer(address to, uint256 amount) returns (bool)'], - wallet, - ); - const feeTx = await tokenContract.transfer(FEE_RECIPIENT, feeAmount, feeOverrides); - await feeTx.wait(); - - // Execute swap with 99% (calldata already built for adjusted amount) - submittedTo = SWAP_PROXY_ADDRESS_MAINNET; - response = await wallet.sendTransaction({ - to: submittedTo, - data: methodParameters.calldata, - value: methodParameters.value, - ...feeOverrides, - }); - } - - const receipt = await response.wait(); - if (!receipt || receipt.status !== 1) { - throw new Error('Swap transaction reverted'); - } - - return { - hash: response.hash, - explorerUrl: `https://etherscan.io/tx/${response.hash}`, - submittedTo, - }; -} - -async function getFeeOverrides(maxFeeGwei?: string | null, priorityFeeGwei?: string | null) { - if (maxFeeGwei?.trim()) { - return { - maxFeePerGas: ethers.utils.parseUnits(maxFeeGwei, 'gwei'), - maxPriorityFeePerGas: ethers.utils.parseUnits(priorityFeeGwei?.trim() || '0.01', 'gwei'), - }; - } - - const feeData = await provider.getFeeData(); - - return { - ...(feeData.maxFeePerGas ? { maxFeePerGas: feeData.maxFeePerGas } : {}), - ...(feeData.maxPriorityFeePerGas ? { maxPriorityFeePerGas: feeData.maxPriorityFeePerGas } : {}), - }; -} diff --git a/apps/web/src/lib/swap/quote.ts b/apps/web/src/lib/swap/quote.ts deleted file mode 100644 index bd91878..0000000 --- a/apps/web/src/lib/swap/quote.ts +++ /dev/null @@ -1,365 +0,0 @@ -import { ethers } from 'ethers'; -import { CurrencyAmount, Percent, TradeType } from '@uniswap/sdk-core'; -import { Pool, Route, TICK_SPACINGS, encodeRouteToPath } from '@uniswap/v3-sdk'; -import { Trade as RouterTrade } from '@uniswap/router-sdk'; -import { webEnv } from '@/lib/env'; -import { - ETHEREUM_CHAIN_ID, - PLATFORM_FEE_BPS, - QUOTER_V2_ABI, - QUOTER_V2_ADDRESS, - SWAP_REQUEST_TIMEOUT_MS, - SWAP_TOKENS, - V3_POOL_CANDIDATES, - UNISWAP_V3_POOL_ABI, - getSlippageBps, - type PoolCandidate, - type SwapQuoteRequest, - type SwapTokenSymbol, -} from './constants'; - -/** TickDataProvider that fetches tick data from the Uniswap V3 pool contract */ -function createContractTickDataProvider( - poolContract: ethers.Contract, - tickSpacing: number -): { getTick: (tick: number) => Promise<{ liquidityNet: string }>; nextInitializedTickWithinOneWord: (tick: number, lte: boolean, _tickSpacing: number) => Promise<[number, boolean]> } { - async function getTick(tick: number): Promise<{ liquidityNet: string }> { - const result = await poolContract.ticks(tick); - return { liquidityNet: result.liquidityNet.toString() }; - } - - async function nextInitializedTickWithinOneWord(tick: number, lte: boolean, _tickSpacing: number): Promise<[number, boolean]> { - const spacing = tickSpacing; - let compressed = Math.trunc(tick / spacing); - if (tick < 0 && tick % spacing !== 0) compressed--; - const wordPos = Math.floor(compressed / 256); - const bitPos = ((compressed % 256) + 256) % 256; - const word = (await poolContract.tickBitmap(wordPos)) as ethers.BigNumber; - const w = BigInt(word.toString()); - let masked: bigint; - let nextCompressed: number; - if (lte) { - const mask = (1n << BigInt(bitPos + 1)) - 1n; - masked = w & mask; - const initialized = masked !== 0n; - if (initialized) { - let msbPos = 0; - for (let i = 255; i >= 0; i--) { - if ((masked >> BigInt(i)) & 1n) { - msbPos = i; - break; - } - } - nextCompressed = compressed - (bitPos - msbPos); - } else { - nextCompressed = compressed - bitPos; - } - return [nextCompressed * spacing, masked !== 0n]; - } else { - const nextWordPos = Math.floor((compressed + 1) / 256); - const nextBitPos = (((compressed + 1) % 256) + 256) % 256; - const nextWord = (await poolContract.tickBitmap(nextWordPos)) as ethers.BigNumber; - const nw = BigInt(nextWord.toString()); - const mask = ~((1n << BigInt(nextBitPos)) - 1n); - masked = nw & mask; - const initialized = masked !== 0n; - if (initialized) { - let lsbPos = 0; - for (let i = 0; i <= 255; i++) { - if ((masked >> BigInt(i)) & 1n) { - lsbPos = i; - break; - } - } - nextCompressed = compressed + 1 + (lsbPos - nextBitPos); - } else { - nextCompressed = compressed + 1 + (255 - nextBitPos); - } - return [nextCompressed * spacing, initialized]; - } - } - - return { getTick, nextInitializedTickWithinOneWord }; -} - -const ETH_RPC_CANDIDATES = [ - ...new Set([ - webEnv.ethRpcUrl, - 'https://ethereum-rpc.publicnode.com', - 'https://rpc.ankr.com/eth', - 'https://eth.llamarpc.com', - ].filter(Boolean)), -]; - -const poolCache = new Map(); -const poolCacheTimestamps = new Map(); -const POOL_CACHE_TTL_MS = 30_000; // 30 seconds - -async function getHealthyProvider(): Promise { - let lastError: unknown = new Error('No Ethereum RPC available'); - for (const rpcUrl of ETH_RPC_CANDIDATES) { - if (!rpcUrl?.trim()) continue; - const provider = new ethers.providers.StaticJsonRpcProvider(rpcUrl, ETHEREUM_CHAIN_ID); - try { - await withTimeout( - provider.getBlockNumber(), - 4_000, - 'RPC health-check timed out' - ); - return provider; - } catch (e) { - lastError = e; - } - } - throw lastError; -} - -export interface SwapQuoteResult { - trade: RouterTrade; - amountInRaw: string; - amountInFormatted: string; - amountOutRaw: string; - amountOutFormatted: string; - minimumAmountOutRaw: string; - minimumAmountOutFormatted: string; - executionPrice: string; - priceImpact: string; - routeSymbols: SwapTokenSymbol[]; - routeFees: number[]; -} - -/** - * Query the on-chain Quoter for each candidate pool and pick the best output. - * This replaces the fragile off-chain bestTradeExactIn simulation that fails - * with RATIO_CURRENT when tick data is stale. - */ -async function findBestRouteOnChain( - pools: Pool[], - inputToken: (typeof SWAP_TOKENS)[SwapTokenSymbol], - outputToken: (typeof SWAP_TOKENS)[SwapTokenSymbol], - amountInRaw: ethers.BigNumber, - quoter: ethers.Contract, -): Promise<{ bestPool: Pool; bestAmountOut: ethers.BigNumber; bestRoutePath: string }> { - const inputCurrency = inputToken.currency; - const outputCurrency = outputToken.currency; - - // Filter pools that contain both input and output tokens - const relevantPools = pools.filter((pool) => { - const t0 = pool.token0.address.toLowerCase(); - const t1 = pool.token1.address.toLowerCase(); - const inAddr = (inputToken.wrappedToken?.address ?? inputToken.address).toLowerCase(); - const outAddr = (outputToken.wrappedToken?.address ?? outputToken.address).toLowerCase(); - return (t0 === inAddr || t1 === inAddr) && (t0 === outAddr || t1 === outAddr); - }); - - if (!relevantPools.length) { - throw new Error('No Uniswap pool available for this token pair'); - } - - let bestPool: Pool | null = null; - let bestAmountOut = ethers.BigNumber.from(0); - let bestRoutePath = ''; - - for (const pool of relevantPools) { - try { - const route = new Route([pool], inputCurrency, outputCurrency); - const routePath = encodeRouteToPath(route, false); - const [amountOut] = await withTimeout( - quoter.callStatic.quoteExactInput(routePath, amountInRaw), - SWAP_REQUEST_TIMEOUT_MS, - 'Quote timed out', - ); - if (amountOut.gt(bestAmountOut)) { - bestPool = pool; - bestAmountOut = amountOut; - bestRoutePath = routePath; - } - } catch { - // Pool doesn't have enough liquidity or RPC issue — skip it - continue; - } - } - - if (!bestPool) { - throw new Error('No Uniswap route found for this token pair'); - } - - return { bestPool, bestAmountOut, bestRoutePath }; -} - -export async function getSwapQuote(request: SwapQuoteRequest): Promise { - validateSwapRequest(request); - - const provider = await getHealthyProvider(); - const inputToken = SWAP_TOKENS[request.fromSymbol]; - const outputToken = SWAP_TOKENS[request.toSymbol]; - const originalAmountInRaw = ethers.utils.parseUnits(request.amount, inputToken.decimals); - // Apply 0.7% platform fee — swap only 99.3% of input (BigNumber math, no float) - const amountInRaw = originalAmountInRaw.mul(10000 - PLATFORM_FEE_BPS).div(10000); - const amountIn = CurrencyAmount.fromRawAmount(inputToken.currency, amountInRaw.toString()); - const pools = await loadCandidatePools(provider); - const quoter = new ethers.Contract(QUOTER_V2_ADDRESS, QUOTER_V2_ABI, provider); - - // Find best route via on-chain Quoter (avoids RATIO_CURRENT from off-chain simulation) - const { bestPool, bestAmountOut, bestRoutePath } = await findBestRouteOnChain( - pools, inputToken, outputToken, amountInRaw, quoter, - ); - - const route = new Route([bestPool], inputToken.currency, outputToken.currency); - const outputAmount = CurrencyAmount.fromRawAmount(outputToken.currency, bestAmountOut.toString()); - const routerTrade = new RouterTrade({ - v3Routes: [{ - routev3: route, - inputAmount: amountIn, - outputAmount, - }], - tradeType: TradeType.EXACT_INPUT, - }); - - const slippageTolerance = new Percent(request.slippageBps, 10_000); - const minimumAmountOut = routerTrade.minimumAmountOut(slippageTolerance); - const outputDecimals = outputToken.decimals; - - return { - trade: routerTrade, - amountInRaw: amountInRaw.toString(), - amountInFormatted: request.amount, - amountOutRaw: bestAmountOut.toString(), - amountOutFormatted: formatUnitsSafe(bestAmountOut.toString(), outputDecimals), - minimumAmountOutRaw: minimumAmountOut.quotient.toString(), - minimumAmountOutFormatted: formatUnitsSafe(minimumAmountOut.quotient.toString(), outputDecimals), - executionPrice: routerTrade.executionPrice.toSignificant(6), - priceImpact: routerTrade.priceImpact.toFixed(4), - routeSymbols: route.tokenPath.map((token) => tokenAddressToSymbol(token.address)), - routeFees: route.pools.map((pool) => pool.fee), - }; -} - -async function loadCandidatePools(provider: ethers.providers.StaticJsonRpcProvider): Promise { - const settled = await Promise.allSettled(V3_POOL_CANDIDATES.map((c) => loadPool(c, provider))); - const pools = settled - .filter((result): result is PromiseFulfilledResult => result.status === 'fulfilled') - .map((result) => result.value); - - if (!pools.length) { - const errors = settled - .filter((r): r is PromiseRejectedResult => r.status === 'rejected') - .map((r) => r.reason?.message ?? String(r.reason)); - const hasRpcIssue = errors.some( - (e) => e?.includes('Failed to fetch') || e?.includes('timeout') || e?.includes('ECONNREFUSED') - ); - const hint = hasRpcIssue - ? ' Check your connection or try a different RPC in NEXT_PUBLIC_ETH_RPC_URL.' - : ''; - throw new Error(`No supported Uniswap pools are currently reachable.${hint}`); - } - - return pools; -} - -async function loadPool(candidate: PoolCandidate, provider: ethers.providers.StaticJsonRpcProvider): Promise { - const cacheKey = `${candidate.tokenA.address}-${candidate.tokenB.address}-${candidate.fee}`; - const cachedPool = poolCache.get(cacheKey); - const cachedAt = poolCacheTimestamps.get(cacheKey) ?? 0; - - if (cachedPool && Date.now() - cachedAt < POOL_CACHE_TTL_MS) { - return cachedPool; - } - - const poolAddress = Pool.getAddress(candidate.tokenA, candidate.tokenB, candidate.fee); - const code = await withTimeout( - provider.getCode(poolAddress), - SWAP_REQUEST_TIMEOUT_MS, - 'Pool discovery timed out' - ); - - if (!code || code === '0x') { - throw new Error(`Pool ${poolAddress} is unavailable`); - } - - const poolContract = new ethers.Contract(poolAddress, UNISWAP_V3_POOL_ABI, provider); - const [liquidity, slot0] = await Promise.all([ - withTimeout(poolContract.liquidity() as Promise, SWAP_REQUEST_TIMEOUT_MS, 'Pool liquidity request timed out'), - withTimeout( - poolContract.slot0() as Promise<{ sqrtPriceX96: bigint; tick: number }>, - SWAP_REQUEST_TIMEOUT_MS, - 'Pool slot0 request timed out' - ), - ]); - - const tickSpacing = TICK_SPACINGS[candidate.fee]; - const tickDataProvider = createContractTickDataProvider(poolContract, tickSpacing); - const pool = new Pool( - candidate.tokenA, - candidate.tokenB, - candidate.fee, - slot0.sqrtPriceX96.toString(), - liquidity.toString(), - slot0.tick, - tickDataProvider - ); - - poolCache.set(cacheKey, pool); - poolCacheTimestamps.set(cacheKey, Date.now()); - return pool; -} - -function validateSwapRequest(request: SwapQuoteRequest): void { - if (request.fromSymbol === request.toSymbol) { - throw new Error('Select two different tokens'); - } - - if (!request.amount || Number(request.amount) <= 0) { - throw new Error('Enter a valid swap amount'); - } - - if (!Number.isFinite(request.slippageBps) || request.slippageBps <= 0) { - throw new Error('Enter a valid slippage value'); - } -} - -function tokenAddressToSymbol(address: string): SwapTokenSymbol { - const normalizedAddress = address.toLowerCase(); - const matched = Object.values(SWAP_TOKENS).find((token) => { - if (token.address === 'native') { - return token.wrappedToken.address.toLowerCase() === normalizedAddress; - } - - return token.address.toLowerCase() === normalizedAddress; - }); - - return matched?.symbol ?? 'ETH'; -} - -function formatUnitsSafe(rawValue: bigint | string, decimals: number): string { - const bigintValue = typeof rawValue === 'bigint' ? rawValue : BigInt(rawValue); - if (bigintValue === 0n) { - return '0'; - } - - const divisor = 10n ** BigInt(decimals); - const whole = bigintValue / divisor; - const fraction = bigintValue % divisor; - - if (fraction === 0n) { - return whole.toString(); - } - - return `${whole}.${fraction.toString().padStart(decimals, '0').replace(/0+$/, '')}`; -} - -function withTimeout(promise: Promise, timeoutMs: number, message: string): Promise { - return new Promise((resolve, reject) => { - const timeoutId = setTimeout(() => reject(new Error(message)), timeoutMs); - - promise - .then((value) => { - clearTimeout(timeoutId); - resolve(value); - }) - .catch((error) => { - clearTimeout(timeoutId); - reject(error); - }); - }); -} diff --git a/apps/web/src/lib/swap/sol/execute.ts b/apps/web/src/lib/swap/sol/execute.ts deleted file mode 100644 index d295150..0000000 --- a/apps/web/src/lib/swap/sol/execute.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { Connection, Keypair, VersionedTransaction } from '@solana/web3.js'; -import { webEnv } from '@/lib/env'; - -export interface SolExecuteSwapParams { - privateKeyHex: string; - userPublicKey: string; - quoteResponse: Record; -} - -export interface SolSwapResult { - hash: string; - explorerUrl: string; -} - -export async function executeSolSwap(params: SolExecuteSwapParams): Promise { - const { privateKeyHex, userPublicKey, quoteResponse } = params; - - // 1. Build swap transaction via backend proxy - const apiUrl = webEnv.apiUrl || 'http://localhost:3001'; - const buildResponse = await fetch(`${apiUrl}/api/sol/swap/build`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ quoteResponse, userPublicKey }), - }); - - if (!buildResponse.ok) { - const body = await buildResponse.json().catch(() => ({ error: 'Failed to build swap' })); - throw new Error(body.error || `Swap build failed (${buildResponse.status})`); - } - - const { swapTransaction } = await buildResponse.json(); - if (!swapTransaction) { - throw new Error('No swap transaction returned from Jupiter'); - } - - // 2. Deserialize the VersionedTransaction - const txBuffer = Buffer.from(swapTransaction, 'base64'); - const transaction = VersionedTransaction.deserialize(txBuffer); - - // 3. Sign with user's Keypair - const keypair = Keypair.fromSecretKey(Buffer.from(privateKeyHex, 'hex')); - transaction.sign([keypair]); - - // 4. Send to Solana RPC - const connection = new Connection(webEnv.solRpcUrl, 'confirmed'); - const rawTx = transaction.serialize(); - - const signature = await connection.sendRawTransaction(rawTx, { - skipPreflight: false, - maxRetries: 2, - }); - - // 5. Confirm transaction - const latestBlockhash = await connection.getLatestBlockhash(); - await connection.confirmTransaction({ - signature, - blockhash: latestBlockhash.blockhash, - lastValidBlockHeight: latestBlockhash.lastValidBlockHeight, - }, 'confirmed'); - - return { - hash: signature, - explorerUrl: `https://solscan.io/tx/${signature}`, - }; -} diff --git a/apps/web/src/lib/swap/sol/quote.ts b/apps/web/src/lib/swap/sol/quote.ts deleted file mode 100644 index 60b7c95..0000000 --- a/apps/web/src/lib/swap/sol/quote.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { SOL_TOKEN_MINTS, SOL_TOKEN_DECIMALS } from '../constants'; -import { webEnv } from '@/lib/env'; - -export interface SolSwapQuoteResult { - chain: 'SOL'; - quoteResponse: Record; - amountInRaw: string; - amountInFormatted: string; - amountOutRaw: string; - amountOutFormatted: string; - minimumAmountOutRaw: string; - minimumAmountOutFormatted: string; - priceImpact: string; - routeLabels: string[]; -} - -export interface SolSwapQuoteRequest { - fromSymbol: string; - toSymbol: string; - amount: string; - slippageBps: number; -} - -export async function getSolSwapQuote(request: SolSwapQuoteRequest): Promise { - const inputMint = SOL_TOKEN_MINTS[request.fromSymbol]; - const outputMint = SOL_TOKEN_MINTS[request.toSymbol]; - - if (!inputMint || !outputMint) { - throw new Error(`Unknown SOL token: ${request.fromSymbol} or ${request.toSymbol}`); - } - - const fromDecimals = SOL_TOKEN_DECIMALS[request.fromSymbol]; - const toDecimals = SOL_TOKEN_DECIMALS[request.toSymbol]; - - // Convert human-readable amount to raw (lamports / smallest unit) - const amountRaw = parseUnitsToRaw(request.amount, fromDecimals); - - const apiUrl = webEnv.apiUrl || 'http://localhost:3001'; - const url = new URL(`${apiUrl}/api/sol/swap/quote`); - url.searchParams.set('inputMint', inputMint); - url.searchParams.set('outputMint', outputMint); - url.searchParams.set('amount', amountRaw); - url.searchParams.set('slippageBps', String(request.slippageBps)); - - const response = await fetch(url.toString()); - - if (!response.ok) { - const body = await response.json().catch(() => ({ error: 'Jupiter API error' })); - throw new Error(body.error || `Jupiter quote failed (${response.status})`); - } - - const data = await response.json(); - - // Jupiter response fields - const outAmount = String(data.outAmount ?? '0'); - const otherAmountThreshold = String(data.otherAmountThreshold ?? '0'); - const priceImpactPct = String(data.priceImpactPct ?? '0'); - - // Extract route labels - const routeLabels: string[] = []; - if (Array.isArray(data.routePlan)) { - for (const step of data.routePlan) { - if (step.swapInfo?.label) routeLabels.push(step.swapInfo.label); - } - } - - return { - chain: 'SOL', - quoteResponse: data, - amountInRaw: amountRaw, - amountInFormatted: request.amount, - amountOutRaw: outAmount, - amountOutFormatted: formatRawUnits(outAmount, toDecimals), - minimumAmountOutRaw: otherAmountThreshold, - minimumAmountOutFormatted: formatRawUnits(otherAmountThreshold, toDecimals), - priceImpact: priceImpactPct, - routeLabels, - }; -} - -function parseUnitsToRaw(amount: string, decimals: number): string { - const parts = amount.split('.'); - const whole = parts[0] || '0'; - let fraction = (parts[1] || '').padEnd(decimals, '0').slice(0, decimals); - const raw = BigInt(whole) * 10n ** BigInt(decimals) + BigInt(fraction); - return raw.toString(); -} - -function formatRawUnits(raw: string, decimals: number): string { - const value = BigInt(raw); - if (value === 0n) return '0'; - - const divisor = 10n ** BigInt(decimals); - const whole = value / divisor; - const fraction = value % divisor; - - if (fraction === 0n) return whole.toString(); - return `${whole}.${fraction.toString().padStart(decimals, '0').replace(/0+$/, '')}`; -} diff --git a/apps/web/src/lib/swap/trx/execute.ts b/apps/web/src/lib/swap/trx/execute.ts deleted file mode 100644 index 15259b9..0000000 --- a/apps/web/src/lib/swap/trx/execute.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { ethers, utils } from 'ethers'; -import { webEnv } from '@/lib/env'; - -export interface TrxExecuteSwapParams { - privateKeyHex: string; // 32-byte secp256k1 key (hex, no 0x prefix) - from: string; - to: string; - amount: string; // raw amount in sun - amountOutMin: string; // raw minimum output - userAddress: string; // TRX base58 address -} - -export interface TrxSwapResult { - hash: string; - explorerUrl: string; - approvalHashes: string[]; -} - -export async function executeTrxSwap(params: TrxExecuteSwapParams): Promise { - const { privateKeyHex, from, to, amount, amountOutMin, userAddress } = params; - - const apiUrl = webEnv.apiUrl || 'http://localhost:3001'; - - // 1. Build transaction(s) via backend - const buildResponse = await fetch(`${apiUrl}/api/tron/swap/build`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ from, to, amount, amountOutMin, userAddress }), - }); - - if (!buildResponse.ok) { - const body = await buildResponse.json().catch(() => ({ error: 'Failed to build TRX swap' })); - throw new Error(body.error || `TRX swap build failed (${buildResponse.status})`); - } - - const { transactions } = await buildResponse.json(); - if (!transactions || !transactions.length) { - throw new Error('No transactions returned from TRX swap builder'); - } - - // 2. Sign and broadcast each transaction in order - const signingKey = new utils.SigningKey('0x' + privateKeyHex); - const approvalHashes: string[] = []; - let swapHash = ''; - - for (const tx of transactions) { - // Sign the txID (which is SHA256 of raw_data) - const txID: string = tx.txID; - const digest = ethers.utils.arrayify('0x' + txID); - const signature = signingKey.signDigest(digest); - const sigHex = ethers.utils.joinSignature(signature).slice(2); // remove 0x, 65 bytes hex - - // Add signature to transaction - const signedTx = { - ...tx, - signature: [sigHex], - }; - - // Broadcast - const broadcastResponse = await fetch(`${apiUrl}/api/tron/swap/broadcast`, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ signedTransaction: signedTx }), - }); - - const result = await broadcastResponse.json(); - - if (!result.result) { - const errorMsg = result.message || result.code || 'Broadcast failed'; - throw new Error(`TRX broadcast error: ${errorMsg}`); - } - - if (tx.type === 'approve') { - approvalHashes.push(txID); - // Wait a bit for approval to be confirmed before swapping - await delay(3000); - } else { - swapHash = txID; - } - } - - return { - hash: swapHash, - explorerUrl: `https://tronscan.org/#/transaction/${swapHash}`, - approvalHashes, - }; -} - -function delay(ms: number): Promise { - return new Promise((resolve) => setTimeout(resolve, ms)); -} diff --git a/apps/web/src/lib/swap/trx/quote.ts b/apps/web/src/lib/swap/trx/quote.ts deleted file mode 100644 index eb805ab..0000000 --- a/apps/web/src/lib/swap/trx/quote.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { TRX_TOKEN_DECIMALS } from '../constants'; -import { webEnv } from '@/lib/env'; - -export interface TrxSwapQuoteResult { - chain: 'TRX'; - amountInRaw: string; - amountInFormatted: string; - amountOutRaw: string; - amountOutFormatted: string; - minimumAmountOutRaw: string; - minimumAmountOutFormatted: string; - from: string; - to: string; -} - -export interface TrxSwapQuoteRequest { - fromSymbol: string; - toSymbol: string; - amount: string; - slippageBps: number; -} - -export async function getTrxSwapQuote(request: TrxSwapQuoteRequest): Promise { - const fromDecimals = TRX_TOKEN_DECIMALS[request.fromSymbol]; - const toDecimals = TRX_TOKEN_DECIMALS[request.toSymbol]; - - if (fromDecimals === undefined || toDecimals === undefined) { - throw new Error(`Unknown TRX token: ${request.fromSymbol} or ${request.toSymbol}`); - } - - // Convert human-readable amount to raw (sun / smallest unit) - const amountRaw = parseUnitsToRaw(request.amount, fromDecimals); - - const apiUrl = webEnv.apiUrl || 'http://localhost:3001'; - const url = new URL(`${apiUrl}/api/tron/swap/quote`); - url.searchParams.set('from', request.fromSymbol); - url.searchParams.set('to', request.toSymbol); - url.searchParams.set('amount', amountRaw); - - const response = await fetch(url.toString()); - - if (!response.ok) { - const body = await response.json().catch(() => ({ error: 'TRX quote failed' })); - throw new Error(body.error || `TRX quote failed (${response.status})`); - } - - const data = await response.json(); - - if (!data.success) { - throw new Error(data.error || 'TRX quote returned error'); - } - - const amountOut = String(data.amountOut); - - // Apply slippage to get minimum output - const amountOutBigInt = BigInt(amountOut); - const minOut = amountOutBigInt - (amountOutBigInt * BigInt(request.slippageBps) / 10000n); - - return { - chain: 'TRX', - amountInRaw: amountRaw, - amountInFormatted: request.amount, - amountOutRaw: amountOut, - amountOutFormatted: formatRawUnits(amountOut, toDecimals), - minimumAmountOutRaw: minOut.toString(), - minimumAmountOutFormatted: formatRawUnits(minOut.toString(), toDecimals), - from: request.fromSymbol, - to: request.toSymbol, - }; -} - -function parseUnitsToRaw(amount: string, decimals: number): string { - const parts = amount.split('.'); - const whole = parts[0] || '0'; - const fraction = (parts[1] || '').padEnd(decimals, '0').slice(0, decimals); - const raw = BigInt(whole) * 10n ** BigInt(decimals) + BigInt(fraction); - return raw.toString(); -} - -function formatRawUnits(raw: string, decimals: number): string { - const value = BigInt(raw); - if (value === 0n) return '0'; - - const divisor = 10n ** BigInt(decimals); - const whole = value / divisor; - const fraction = value % divisor; - - if (fraction === 0n) return whole.toString(); - return `${whole}.${fraction.toString().padStart(decimals, '0').replace(/0+$/, '')}`; -} diff --git a/apps/web/src/store/auth-store.ts b/apps/web/src/store/auth-store.ts deleted file mode 100644 index 9e4f332..0000000 --- a/apps/web/src/store/auth-store.ts +++ /dev/null @@ -1,137 +0,0 @@ -'use client'; - -import { create } from 'zustand'; -import { bitokAuth, walletApi, setAccessToken } from '@/lib/api'; -import { encryptVault, decryptVault } from '@/lib/crypto/vault'; -import { generateWallets, deriveWalletsFromMnemonic, type DerivedWallet } from '@/lib/crypto/derive-keys'; - -interface AuthState { - user: { id: string; email: string } | null; - wallets: DerivedWallet[]; - mnemonic: string | null; - mnemonicShown: boolean; - loading: boolean; - error: string | null; - - // 2-step registration - registerStart: (email: string) => Promise; - registerComplete: (email: string, password: string, code: string) => Promise; - - // 2-step login - loginStart: (email: string) => Promise; - loginComplete: (email: string, password: string, code: string) => Promise; - - confirmMnemonic: () => Promise; - logout: () => void; - clearMnemonic: () => void; - clearError: () => void; -} - -export const useAuthStore = create((set, get) => ({ - user: null, - wallets: [], - mnemonic: null, - mnemonicShown: true, - loading: false, - error: null, - - registerStart: async (email) => { - set({ loading: true, error: null }); - try { - await bitokAuth.registrationStart(email); - set({ loading: false }); - } catch (err: any) { - set({ loading: false, error: err.message }); - } - }, - - registerComplete: async (email, password, code) => { - set({ loading: true, error: null }); - try { - // Step 1: Complete BITOK registration, get JWT - const authData = await bitokAuth.registrationComplete(email, password, code); - setAccessToken(authData.access_token); - - // Step 2: Generate mnemonic & derive wallets - const { mnemonic, wallets } = await generateWallets(); - - // Step 3: Encrypt vault with password only - const { encryptedVault, vaultSalt } = await encryptVault(mnemonic, password); - - // Step 4: Send wallet data to backend - await walletApi.setup({ - encryptedVault, - vaultSalt, - wallets: wallets.map((w) => ({ - chain: w.chain, - address: w.address, - derivationPath: w.derivationPath, - })), - }); - - set({ - user: { id: authData.id, email: authData.email }, - wallets, - mnemonic, - mnemonicShown: false, - loading: false, - }); - } catch (err: any) { - set({ loading: false, error: err.message }); - } - }, - - loginStart: async (email) => { - set({ loading: true, error: null }); - try { - await bitokAuth.loginStart(email); - set({ loading: false }); - } catch (err: any) { - set({ loading: false, error: err.message }); - } - }, - - loginComplete: async (email, password, code) => { - set({ loading: true, error: null }); - try { - // Step 1: Complete BITOK login, get JWT - const authData = await bitokAuth.loginComplete(email, password, code); - setAccessToken(authData.access_token); - - // Step 2: Get vault data from wallet API - const vaultData = await walletApi.unlock(); - - // Step 3: Decrypt vault client-side with password only - const mnemonic = await decryptVault(vaultData.encryptedVault, vaultData.vaultSalt, password); - const { wallets } = await deriveWalletsFromMnemonic(mnemonic); - - set({ - user: { id: authData.id, email: authData.email }, - wallets, - mnemonic: vaultData.mnemonicShown ? null : mnemonic, - mnemonicShown: vaultData.mnemonicShown, - loading: false, - }); - } catch (err: any) { - set({ loading: false, error: err.message }); - } - }, - - confirmMnemonic: async () => { - try { - await walletApi.confirmMnemonic(); - set({ mnemonicShown: true, mnemonic: null }); - } catch (err: any) { - set({ error: err.message }); - } - }, - - logout: () => { - bitokAuth.logout().catch(() => {}); - setAccessToken(null); - set({ user: null, wallets: [], mnemonic: null, mnemonicShown: true }); - }, - - clearMnemonic: () => set({ mnemonic: null }), - clearError: () => set({ error: null }), -})); diff --git a/apps/web/src/store/balance-store.ts b/apps/web/src/store/balance-store.ts deleted file mode 100644 index 6bac879..0000000 --- a/apps/web/src/store/balance-store.ts +++ /dev/null @@ -1,114 +0,0 @@ -'use client'; - -import { create } from 'zustand'; -import type { DerivedWallet } from '@/lib/crypto/derive-keys'; -import { fetchAllBalances } from '@/lib/balances'; -import type { ChainBalance, PortfolioBalance } from '@/lib/balances/types'; - -interface BalanceState { - portfolio: PortfolioBalance | null; - loading: boolean; - refreshing: boolean; - error: string | null; - fetchBalances: (wallets: DerivedWallet[]) => Promise; - clearBalances: () => void; -} - -export const useBalanceStore = create((set, get) => ({ - portfolio: null, - loading: false, - refreshing: false, - error: null, - - fetchBalances: async (wallets) => { - if (!wallets.length) { - set({ portfolio: null, loading: false, refreshing: false, error: null }); - return; - } - - const hasPortfolio = !!get().portfolio; - set({ - loading: !hasPortfolio, - refreshing: hasPortfolio, - error: null, - }); - - try { - const fresh = await fetchAllBalances(wallets); - const prev = get().portfolio; - - // Merge: if a chain had an error but we have previous data, keep previous - const portfolio = prev ? mergePortfolios(prev, fresh) : fresh; - - set({ - portfolio, - loading: false, - refreshing: false, - error: null, - }); - } catch (error) { - const prev = get().portfolio; - set({ - portfolio: prev, - loading: false, - refreshing: false, - error: prev ? null : getErrorMessage(error), - }); - } - }, - - clearBalances: () => { - set({ - portfolio: null, - loading: false, - refreshing: false, - error: null, - }); - }, -})); - -/** - * If a chain in fresh data has an error and all its token balances are zero, - * but previous data had real balances, keep the previous chain data. - */ -function mergePortfolios(prev: PortfolioBalance, fresh: PortfolioBalance): PortfolioBalance { - const chains = fresh.chains.map((freshChain) => { - if (!freshChain.error) return freshChain; - - const prevChain = prev.chains.find((c) => c.chain === freshChain.chain); - if (!prevChain || prevChain.error) return freshChain; - - // Chain has error and all balances are zero — keep previous - const allZero = freshChain.tokens.every((t) => t.balanceRaw === '0'); - if (allZero) return prevChain; - - return freshChain; - }); - - const totalUsd = sumNullable(chains.map((c) => c.totalUsd)); - const errors: PortfolioBalance['errors'] = {}; - for (const chain of chains) { - if (chain.error && chain.error !== '__transient__') errors[chain.chain] = chain.error; - } - - return { - chains, - totalUsd, - errors, - priceError: fresh.priceError, - updatedAt: fresh.updatedAt, - }; -} - -function sumNullable(values: Array): number | null { - const filtered = values.filter((v): v is number => typeof v === 'number'); - return filtered.length ? filtered.reduce((a, b) => a + b, 0) : null; -} - -function getErrorMessage(error: unknown): string { - if (error instanceof Error) { - return error.message; - } - - return 'Unable to refresh balances'; -} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json deleted file mode 100644 index 75d74a7..0000000 --- a/apps/web/tsconfig.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "strict": true, - "noEmit": true, - "esModuleInterop": true, - "module": "esnext", - "moduleResolution": "bundler", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "react-jsx", - "incremental": true, - "plugins": [ - { - "name": "next" - } - ], - "paths": { - "@/*": ["./src/*"] - } - }, - "include": [ - "next-env.d.ts", - "**/*.ts", - "**/*.tsx", - ".next/types/**/*.ts", - ".next/dev/types/**/*.ts", - "**/*.mts" - ], - "exclude": ["node_modules"] -} diff --git a/contracts/DEPLOY.md b/contracts/DEPLOY.md deleted file mode 100644 index 4a160d9..0000000 --- a/contracts/DEPLOY.md +++ /dev/null @@ -1,240 +0,0 @@ -# FeeSwapRouter — Инструкция по деплою - -## Четыре сети — четыре подхода - -| Сеть | Метод | DEX | Fee wallet | Комиссия | Нужен на газ | -|------|-------|-----|------------|----------|-------------| -| **Ethereum** (chainId 1) | Solidity контракт (Remix) | Uniswap Universal Router | `0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718` | ~~1%~~ → **0.7%** | ETH (~$5-20) | -| **BSC** (chainId 56) | Solidity контракт (Remix) | PancakeSwap V3 Smart Router | `0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718` | **0.7%** | BNB (~$0.50-1.00) | -| **TRON** | Solidity контракт (TronIDE) | SunSwap V2 Smart Router | `TYTfrem65362TFyQSARTheeYza1GQA37Ug` | **0.7%** | TRX (~50-150 TRX) | -| **Solana** | Jupiter Referral Program | Jupiter Aggregator | `Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ` | **0.7%** | SOL (~0.01 SOL) | - -Все адреса и проценты захардкожены — никаких параметров при деплое вводить НЕ НУЖНО. - -**Важно**: ETH контракт уже задеплоен с 1% комиссией. Если нужно 0.7%, нужно задеплоить новый контракт. - ---- - -## 1. Деплой FeeSwapRouter_ETH.sol (Ethereum) - -**Статус: ✅ Задеплоен (1%) — `0xbdC4A97C2814E496160638d87e1F1b14154e30b6`** -**⚠️ Нужен передеплой с 0.7% если хочешь 0.7% on-chain** - -### Что нужно -- MetaMask с ETH на балансе (на газ уйдёт ~$5-20) -- В MetaMask выбрана сеть **Ethereum Mainnet** - -### Шаги - -1. Открой https://remix.ethereum.org -2. Слева в File Explorer нажми **+** → создай файл `FeeSwapRouter_ETH.sol` -3. Вставь **ВЕСЬ** код из файла `contracts/FeeSwapRouter_ETH.sol` -4. Слева выбери вкладку **Solidity Compiler** (иконка с буквой S) - - Compiler: **0.8.20** - - **Enable optimization**: включи, runs: **200** - - Нажми **Compile FeeSwapRouter_ETH.sol** - - Должна появиться зелёная галочка (без ошибок) -5. Слева выбери вкладку **Deploy & Run Transactions** (иконка со стрелкой) - - Environment: **Injected Provider - MetaMask** - - Убедись что MetaMask на сети **Ethereum Mainnet** - - В выпадающем списке контрактов выбери **FeeSwapRouter_ETH** - - Нажми **Deploy** - - Подтверди транзакцию в MetaMask -6. После подтверждения — скопируй адрес контракта из консоли Remix -7. Сохрани адрес в `.env`: - ``` - FEE_SWAP_ROUTER_ETH=0x...твой_адрес... - ``` - -### Верификация на Etherscan -- Зайди на https://etherscan.io/verifyContract -- Адрес контракта: вставь адрес из шага 6 -- Compiler: Solidity 0.8.20 -- Optimization: Yes, 200 runs -- License: MIT -- Код: в Remix правой кнопкой на файл → **Flatten**, вставь результат - ---- - -## 2. Деплой FeeSwapRouter_BSC.sol (BSC) - -### Что нужно -- MetaMask с BNB на балансе (на газ уйдёт ~$0.50-1.00) -- В MetaMask добавлена сеть **BNB Smart Chain**: - - RPC: `https://bsc-dataseed.binance.org` - - Chain ID: `56` - - Symbol: `BNB` - - Explorer: `https://bscscan.com` - -### Шаги - -1. Открой https://remix.ethereum.org -2. Создай файл `FeeSwapRouter_BSC.sol` -3. Вставь **ВЕСЬ** код из файла `contracts/FeeSwapRouter_BSC.sol` -4. Compiler → **0.8.20**, optimization ON (200 runs), Compile -5. Deploy → Injected Provider → MetaMask на сети **BNB Smart Chain** -6. Выбери контракт **FeeSwapRouter_BSC**, нажми **Deploy**, подтверди в MetaMask -7. Скопируй адрес, сохрани в `.env`: - ``` - FEE_SWAP_ROUTER_BSC=0x...твой_адрес... - ``` - -### Верификация на BscScan -- Зайди на https://bscscan.com/verifyContract -- Compiler: Solidity 0.8.20 -- Optimization: Yes, 200 runs -- License: MIT -- Код: в Remix → Flatten → вставь результат - ---- - -## 3. Деплой FeeSwapRouter_TRX.sol (TRON) - -**Статус: ✅ Задеплоен — `TX8E6X7X1FWYRYuYR2LTvS7zm1KchcVs5E`** - -### Что нужно -- **TronLink** кошелёк (расширение для Chrome, аналог MetaMask для TRON) -- TRX на балансе (~50-150 TRX на газ) -- В TronLink выбрана сеть **TRON Mainnet** - -### Шаги - -1. Открой https://www.tronide.io (это Remix для TRON) -2. Слева в File Explorer нажми **+** → создай файл `FeeSwapRouter_TRX.sol` -3. Вставь **ВЕСЬ** код из файла `contracts/FeeSwapRouter_TRX.sol` -4. Compiler → **0.8.20**, optimization ON (200 runs), Compile - - ⚠️ TronIDE может не поддерживать OpenZeppelin импорты — но этот контракт - НЕ использует OpenZeppelin (ReentrancyGuard и Ownable реализованы вручную) -5. Deploy → Injected Provider → **TronLink** (должен быть установлен) - - Убедись что TronLink на сети **TRON Mainnet** - - В выпадающем списке выбери **FeeSwapRouter_TRX** - - Нажми **Deploy**, подтверди в TronLink -6. Скопируй адрес контракта (будет в формате T...) -7. Сохрани в `.env`: - ``` - FEE_SWAP_ROUTER_TRX=TX8E6X7X1FWYRYuYR2LTvS7zm1KchcVs5E - ``` - -### Верификация на TronScan -- Зайди на https://tronscan.org/#/contracts/verify -- Вставь адрес контракта (T...) -- Compiler: 0.8.20 -- Optimization: ON -- Код: скопируй весь контракт - -### Адреса в контракте (TRON base58 → hex) - -| Контракт | TRON адрес | Hex (в Solidity) | -|----------|-----------|-----------------| -| SunSwap V2 Router | `TKzxdSv2FZKQrEqkKVgp5DcwEXBEKMg2Ax` | `0x6e0617948fE030a7e4970f8389D4AD295F249b7e` | -| USDT (TRC-20) | `TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t` | `0xa614f803b6fd780986a42c78ec9c7f77e6ded13c` | -| WTRX | `TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR` | `0x891cdb91d149f23b1a45d9c5ca78a88d0cb44c18` | - ---- - -## 4. Настройка комиссии на Solana (Jupiter Referral) - -На Solana НЕ нужен смарт-контракт. Jupiter Aggregator **нативно поддерживает** платформенную комиссию через Referral Program. - -### Что нужно -- Кошелёк Solana с ~0.01 SOL (на ренту и газ) -- Fee wallet: `Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ` - -### Шаги - -#### Шаг 1: Создай Referral Account - -1. Зайди на https://referral.jup.ag -2. Подключи кошелёк `Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ` -3. Нажми **Create Referral Account** -4. Подтверди транзакцию (~0.003 SOL) -5. Скопируй **Referral Account Public Key** — это будет `JUPITER_REFERRAL_ACCOUNT` - -#### Шаг 2: Создай Token Fee Accounts - -Для каждого токена, с которого берётся комиссия, нужен отдельный fee token account: - -1. На странице https://referral.jup.ag перейди в **Claim Token Accounts** -2. Нажми **Create Token Account** для каждого нужного токена: - - **SOL** (wrapped): `So11111111111111111111111111111111111111112` - - **USDT**: `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` - - **USDC**: `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` -3. Подтверди каждую транзакцию (~0.002 SOL каждая) - -#### Шаг 3: Сохрани в `.env` - -``` -JUPITER_REFERRAL_ACCOUNT=...твой_referral_account_pubkey... -JUPITER_FEE_BPS=70 -``` - -#### Как это работает - -Jupiter сам: -1. Считает 0.7% от суммы свапа -2. Отправляет комиссию на fee token account -3. Остальные 99.3% идут пользователю - -Комиссия накапливается на token fee accounts. Можно клеймить (забирать) через https://referral.jup.ag → **Claim**. - ---- - -## 5. Bridge комиссия (0.7%) - -Комиссия на бридж работает **off-chain** — перед мостом отправляется отдельная транзакция: - -| Сеть | Как берётся fee | Fee wallet | -|------|----------------|------------| -| ETH → * | Отдельный ETH/ERC20 transfer перед бриджем | `0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718` | -| SOL → * | Отдельный SOL transfer перед бриджем | `Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ` | -| TRX → * | Отдельный TRC20 transfer перед бриджем | `TYTfrem65362TFyQSARTheeYza1GQA37Ug` | - -Контракт **НЕ нужен** для bridge fee — логика встроена в код кошелька (`apps/web/src/lib/bridge/execute.ts`). - ---- - -## После деплоя — интеграция с кошельком - -После получения адресов контрактов: - -1. Добавить адреса в `.env`: - ``` - FEE_SWAP_ROUTER_ETH=0xbdC4A97C2814E496160638d87e1F1b14154e30b6 - FEE_SWAP_ROUTER_BSC=0xbdC4A97C2814E496160638d87e1F1b14154e30b6 - FEE_SWAP_ROUTER_TRX=TX8E6X7X1FWYRYuYR2LTvS7zm1KchcVs5E - JUPITER_REFERRAL_ACCOUNT=...referral_pubkey... - JUPITER_FEE_BPS=70 - ``` -2. ✅ BSC swap proxy — вызывает FeeSwapRouter_BSC -3. ✅ TRX swap proxy — вызывает FeeSwapRouter_TRX -4. Обновить SOL swap proxy — добавить `platformFeeBps=70` в Jupiter запросы -5. Approve токены должны идти на адрес FeeSwapRouter (не на DEX роутер) - ---- - -## Админ-функции (ETH, BSC, TRX контракты) - -Кошелёк, с которого задеплоил — это **owner**. Доступные функции: - -| Функция | Что делает | -|---------|-----------| -| `pause()` | Экстренная остановка всех свапов | -| `unpause()` | Возобновить свапы | -| `emergencyWithdrawNative()` | Вывести застрявший ETH/BNB/TRX | -| `emergencyWithdrawToken(address)` | Вывести застрявшие токены | - -Вызывать через: -- **ETH**: Etherscan → Write Contract -- **BSC**: BscScan → Write Contract -- **TRX**: TronScan → Write Contract - -**Важно**: комиссия (0.7%) и fee wallet захардкожены — их нельзя изменить. Это сделано специально для безопасности. - ---- - -## Порядок деплоя (рекомендуемый) - -1. ✅ ETH контракт (уже задеплоен, но с 1% — передеплоить если нужно 0.7%) -2. BSC контракт → через Remix + MetaMask -3. TRX контракт → через TronIDE + TronLink -4. SOL referral → через https://referral.jup.ag diff --git a/contracts/FeeSetup_SOL.md b/contracts/FeeSetup_SOL.md deleted file mode 100644 index 42b782a..0000000 --- a/contracts/FeeSetup_SOL.md +++ /dev/null @@ -1,113 +0,0 @@ -# Solana — Настройка комиссии через Jupiter Referral Program - -На Solana **НЕ нужен** смарт-контракт. Jupiter Aggregator нативно поддерживает платформенную комиссию. - -## Как работает - -``` -User → Jupiter Swap → 99.3% пользователю + 0.7% на Referral Fee Account -``` - -Jupiter сам: -1. Считает 0.7% от суммы свапа -2. Отправляет комиссию на твой fee token account -3. Остальные 99.3% идут пользователю - -Комиссия накапливается на token fee accounts. Забирать (клеймить) можно через https://referral.jup.ag → **Claim**. - ---- - -## Настройка (одноразовая) - -### Шаг 1: Создай Referral Account - -1. Зайди на https://referral.jup.ag -2. Подключи кошелёк `Co43MKwqMRMCvhscVVrtQWvma87NEV7ba4cfo8cksgzJ` (fee wallet для SOL) -3. Нажми **Create Referral Account** -4. Подтверди транзакцию (~0.003 SOL) -5. Скопируй **Referral Account Public Key** - -### Шаг 2: Создай Token Fee Accounts - -Для каждого токена нужен отдельный fee token account: - -1. На https://referral.jup.ag → **Claim Token Accounts** -2. Нажми **Create Token Account** для каждого: - - **SOL** (wrapped): `So11111111111111111111111111111111111111112` - - **USDT**: `Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB` - - **USDC**: `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` -3. Подтверди каждую транзакцию (~0.002 SOL каждая) - -### Шаг 3: Добавь в .env - -```env -JUPITER_REFERRAL_ACCOUNT=...твой_referral_account_pubkey... -JUPITER_FEE_BPS=70 -``` - ---- - -## Как поменять комиссию - -Комиссия задаётся **одной переменной** в `.env`: - -```env -JUPITER_FEE_BPS=70 # 0.7% (текущая) -``` - -Примеры значений: - -| JUPITER_FEE_BPS | Комиссия | -|-----------------|----------| -| 10 | 0.1% | -| 25 | 0.25% | -| 50 | 0.5% | -| **70** | **0.7%** | -| 100 | 1.0% | -| 200 | 2.0% | - -**Чтобы поменять**: просто измени `JUPITER_FEE_BPS` в `.env` и перезапусти сервер. Передеплой не нужен. - -**Максимум**: Jupiter позволяет до 255 BPS (2.55%). - ---- - -## Где это в коде - -Файл: `apps/api/src/routes/sol-swap-proxy.routes.ts` - -**Quote** — передаёт `platformFeeBps` в Jupiter API: -``` -GET /quote?...&platformFeeBps=70 -``` -Jupiter возвращает quote уже с учётом комиссии — пользователь видит реальный выход. - -**Build** — передаёт `feeAccount` (Referral Account) в Jupiter Swap API: -```json -{ - "quoteResponse": {...}, - "userPublicKey": "...", - "feeAccount": "...referral_account_pubkey..." -} -``` -Jupiter встраивает инструкцию перевода комиссии прямо в транзакцию свапа. - ---- - -## Отличие от ETH/BSC/TRX - -| | ETH / BSC / TRX | Solana | -|---|---|---| -| Контракт | Свой FeeSwapRouter | Не нужен | -| Комиссия | Захардкожена в контракте | Настраивается через env | -| Изменение % | Нужен передеплой контракта | Поменять env + перезапуск | -| Где копится fee | На fee wallet напрямую | На Jupiter fee token accounts | -| Как забрать fee | Уже на кошельке | Claim через referral.jup.ag | - ---- - -## Стоимость - -- Создание Referral Account: ~0.003 SOL (~$0.50) -- Каждый Token Fee Account: ~0.002 SOL (~$0.30) -- Итого на 3 токена (SOL, USDT, USDC): ~0.009 SOL (~$1.50) diff --git a/contracts/FeeSwapRouter_BSC.sol b/contracts/FeeSwapRouter_BSC.sol deleted file mode 100644 index aec8508..0000000 --- a/contracts/FeeSwapRouter_BSC.sol +++ /dev/null @@ -1,154 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/utils/Pausable.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -/// @title FeeSwapRouter_BSC -/// @notice Обёртка для PancakeSwap V3 Smart Router на BSC. Берёт 0.7% комиссию -/// с каждого свапа и отправляет на fee wallet. Остальные 99.3% + calldata -/// идут на Smart Router как есть. -/// @dev Работает с любой версией PancakeSwap (V2/V3/Smart Router), -/// потому что просто пересылает calldata без разбора. -contract FeeSwapRouter_BSC is Ownable, ReentrancyGuard, Pausable { - using SafeERC20 for IERC20; - - // ── Захардкоженные адреса для BSC ── - - /// @notice PancakeSwap V3 Smart Router на BSC - address public constant SMART_ROUTER = 0x13f4EA83D0bd40E75C8222255bc855a974568Dd4; - - /// @notice Permit2 на BSC (общий для PancakeSwap и Uniswap) - address public constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; - - /// @notice Кошелёк, получающий 0.7% комиссию - address public constant FEE_RECIPIENT = 0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718; - - /// @notice Комиссия 0.7% = 70 basis points (нельзя изменить) - uint16 public constant FEE_BPS = 70; - - /// @notice Делитель для basis points - uint16 private constant BPS_DENOMINATOR = 10_000; - - // ── Events ── - - event SwapWithFeeNative( - address indexed user, - uint256 totalIn, - uint256 feeAmount, - uint256 swapAmount, - address indexed router - ); - - event SwapWithFeeToken( - address indexed user, - address indexed tokenIn, - uint256 totalIn, - uint256 feeAmount, - uint256 swapAmount, - address indexed router - ); - - event EmergencyWithdrawNative(address indexed to, uint256 amount); - event EmergencyWithdrawToken(address indexed token, address indexed to, uint256 amount); - - // ── Errors ── - - error InsufficientValue(); - error NativeTransferFailed(); - error SwapFailed(); - error ZeroAmount(); - error ZeroAddress(); - error WrongChain(); - - // ── Constructor ── - - constructor() Ownable(msg.sender) { - if (block.chainid != 56) revert WrongChain(); - } - - /// @notice Свап BNB → токен через PancakeSwap Smart Router с 1% комиссией. - /// @param routerCalldata Calldata сгенерированная PancakeSwap SDK. - /// Передаётся как есть. - function swapNativeWithFee( - bytes calldata routerCalldata - ) external payable nonReentrant whenNotPaused { - if (msg.value == 0) revert InsufficientValue(); - - uint256 feeAmount = (msg.value * FEE_BPS) / BPS_DENOMINATOR; - uint256 swapAmount = msg.value - feeAmount; - - // 1% → fee wallet - if (feeAmount > 0) { - (bool feeSent, ) = FEE_RECIPIENT.call{value: feeAmount}(""); - if (!feeSent) revert NativeTransferFailed(); - } - - // 99% + calldata → PancakeSwap Smart Router - (bool success, ) = SMART_ROUTER.call{value: swapAmount}(routerCalldata); - if (!success) revert SwapFailed(); - - emit SwapWithFeeNative(msg.sender, msg.value, feeAmount, swapAmount, SMART_ROUTER); - } - - - /// @notice Свап токен → BNB через PancakeSwap Smart Router с 1% комиссией. - /// @param tokenIn Адрес входного токена (USDT, DOGE, и т.д.) - /// @param amountIn Полная сумма токенов (включая 1% комиссию) - /// @param routerCalldata Calldata для Smart Router - function swapTokenWithFee( - address tokenIn, - uint256 amountIn, - bytes calldata routerCalldata - ) external nonReentrant whenNotPaused { - if (amountIn == 0) revert ZeroAmount(); - if (tokenIn == address(0)) revert ZeroAddress(); - - // Забираем токены у пользователя - IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); - - uint256 feeAmount = (amountIn * FEE_BPS) / BPS_DENOMINATOR; - uint256 swapAmount = amountIn - feeAmount; - - // 1% комиссию → fee wallet - if (feeAmount > 0) { - IERC20(tokenIn).safeTransfer(FEE_RECIPIENT, feeAmount); - } - - // Approve 99% на Permit2 (PancakeSwap Swap Proxy) - IERC20(tokenIn).forceApprove(PERMIT2, swapAmount); - - // Calldata → Smart Router - (bool success, ) = SMART_ROUTER.call(routerCalldata); - if (!success) revert SwapFailed(); - - emit SwapWithFeeToken(msg.sender, tokenIn, amountIn, feeAmount, swapAmount, SMART_ROUTER); - } - - // ── Admin: Emergency ── - - function pause() external onlyOwner { _pause(); } - function unpause() external onlyOwner { _unpause(); } - - function emergencyWithdrawNative() external onlyOwner { - uint256 balance = address(this).balance; - if (balance == 0) revert ZeroAmount(); - (bool sent, ) = owner().call{value: balance}(""); - if (!sent) revert NativeTransferFailed(); - emit EmergencyWithdrawNative(owner(), balance); - } - - function emergencyWithdrawToken(address token) external onlyOwner { - if (token == address(0)) revert ZeroAddress(); - uint256 balance = IERC20(token).balanceOf(address(this)); - if (balance == 0) revert ZeroAmount(); - IERC20(token).safeTransfer(owner(), balance); - emit EmergencyWithdrawToken(token, owner(), balance); - } - - /// @notice Принимаем BNB от PancakeSwap (рефанды, выходные средства) - receive() external payable {} -} diff --git a/contracts/FeeSwapRouter_ETH.sol b/contracts/FeeSwapRouter_ETH.sol deleted file mode 100644 index d9285d3..0000000 --- a/contracts/FeeSwapRouter_ETH.sol +++ /dev/null @@ -1,186 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -// ╔══════════════════════════════════════════════════════════════╗ -// ║ FeeSwapRouter — ETHEREUM MAINNET ║ -// ║ ║ -// ║ Деплоить на: Ethereum Mainnet (chainId 1) ║ -// ║ Нужен: ETH на газ (~$5-20) ║ -// ║ DEX: Uniswap Universal Router V2.0 ║ -// ║ Комиссия: 1% с каждого свапа → fee wallet ║ -// ║ ║ -// ║ КАК ЗАДЕПЛОИТЬ: ║ -// ║ 1. Открой https://remix.ethereum.org ║ -// ║ 2. Создай файл, вставь ВЕСЬ этот код ║ -// ║ 3. Compiler → 0.8.20, Optimization ON (200 runs) ║ -// ║ 4. Deploy → Injected Provider (MetaMask) ║ -// ║ 5. В MetaMask выбери сеть ETHEREUM MAINNET ║ -// ║ 6. Нажми Deploy, подтверди в MetaMask ║ -// ║ 7. Сохрани адрес контракта после деплоя ║ -// ╚══════════════════════════════════════════════════════════════╝ - -import "@openzeppelin/contracts/access/Ownable.sol"; -import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -import "@openzeppelin/contracts/utils/Pausable.sol"; -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; - -/// @title FeeSwapRouter_ETH -/// @notice Обёртка для Uniswap Universal Router. Берёт 1% комиссию с каждого -/// свапа и отправляет на fee wallet. Остальные 99% + calldata идут -/// на Universal Router как есть. -/// @dev Работает с любой версией Uniswap (V2/V3/V4/Universal Router), -/// потому что просто пересылает calldata без разбора. -contract FeeSwapRouter_ETH is Ownable, ReentrancyGuard, Pausable { - using SafeERC20 for IERC20; - - // ── Захардкоженные адреса для Ethereum ── - - /// @notice Uniswap Universal Router V2.0 на Ethereum Mainnet - address public constant UNIVERSAL_ROUTER = 0x3fC91A3afd70395Cd496C647d5a6CC9D4B2b7FAD; - - /// @notice Uniswap Permit2 / Swap Proxy на Ethereum Mainnet - address public constant SWAP_PROXY = 0x000000000022D473030F116dDEE9F6B43aC78BA3; - - /// @notice Кошелёк, получающий 1% комиссию - address public constant FEE_RECIPIENT = 0xeDEb157eF86A4ecd1242762f339c2Bd5a0822718; - - /// @notice Комиссия 1% = 100 basis points (нельзя изменить) - uint16 public constant FEE_BPS = 100; - - /// @notice Делитель для basis points - uint16 private constant BPS_DENOMINATOR = 10_000; - - // ── Events ── - - event SwapWithFeeNative( - address indexed user, - uint256 totalIn, - uint256 feeAmount, - uint256 swapAmount, - address indexed router - ); - - event SwapWithFeeToken( - address indexed user, - address indexed tokenIn, - uint256 totalIn, - uint256 feeAmount, - uint256 swapAmount, - address indexed router - ); - - event EmergencyWithdrawNative(address indexed to, uint256 amount); - event EmergencyWithdrawToken(address indexed token, address indexed to, uint256 amount); - - // ── Errors ── - - error InsufficientValue(); - error NativeTransferFailed(); - error SwapFailed(); - error ZeroAmount(); - error ZeroAddress(); - error WrongChain(); - error UnauthorizedRouter(); - - // ── Constructor ── - - constructor() Ownable(msg.sender) { - if (block.chainid != 1) revert WrongChain(); - } - - // ═══════════════════════════════════════════════ - // Swap: ETH → Token - // Пользователь отправляет ETH. Контракт берёт 1%, - // остальное + calldata пересылает на Universal Router. - // ═══════════════════════════════════════════════ - - /// @notice Свап ETH → токен через Universal Router с 1% комиссией. - /// @param routerCalldata Calldata сгенерированная Uniswap SDK - /// (SwapRouter.swapCallParameters). Передаётся как есть. - function swapNativeWithFee( - bytes calldata routerCalldata - ) external payable nonReentrant whenNotPaused { - if (msg.value == 0) revert InsufficientValue(); - - uint256 feeAmount = (msg.value * FEE_BPS) / BPS_DENOMINATOR; - uint256 swapAmount = msg.value - feeAmount; - - // 1% → fee wallet - if (feeAmount > 0) { - (bool feeSent, ) = FEE_RECIPIENT.call{value: feeAmount}(""); - if (!feeSent) revert NativeTransferFailed(); - } - - // 99% + calldata → Universal Router - (bool success, ) = UNIVERSAL_ROUTER.call{value: swapAmount}(routerCalldata); - if (!success) revert SwapFailed(); - - emit SwapWithFeeNative(msg.sender, msg.value, feeAmount, swapAmount, UNIVERSAL_ROUTER); - } - - // ═══════════════════════════════════════════════ - // Swap: Token → ETH - // Пользователь approve-ит токены на этот контракт. - // Контракт берёт 1% комиссию в токенах, approve-ит - // 99% на Permit2/SwapProxy, и пересылает calldata - // на Universal Router. - // ═══════════════════════════════════════════════ - - /// @notice Свап токен → ETH через Universal Router с 1% комиссией. - /// @param tokenIn Адрес входного токена (USDT, USDC, и т.д.) - /// @param amountIn Полная сумма токенов (включая 1% комиссию) - /// @param routerCalldata Calldata для Universal Router - function swapTokenWithFee( - address tokenIn, - uint256 amountIn, - bytes calldata routerCalldata - ) external nonReentrant whenNotPaused { - if (amountIn == 0) revert ZeroAmount(); - if (tokenIn == address(0)) revert ZeroAddress(); - - // Забираем токены у пользователя - IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn); - - uint256 feeAmount = (amountIn * FEE_BPS) / BPS_DENOMINATOR; - uint256 swapAmount = amountIn - feeAmount; - - // 1% комиссию → fee wallet - if (feeAmount > 0) { - IERC20(tokenIn).safeTransfer(FEE_RECIPIENT, feeAmount); - } - - // Approve 99% на Permit2 (Uniswap Swap Proxy) - IERC20(tokenIn).forceApprove(SWAP_PROXY, swapAmount); - - // Calldata → Universal Router - (bool success, ) = UNIVERSAL_ROUTER.call(routerCalldata); - if (!success) revert SwapFailed(); - - emit SwapWithFeeToken(msg.sender, tokenIn, amountIn, feeAmount, swapAmount, UNIVERSAL_ROUTER); - } - - // ── Admin: Emergency ── - - function pause() external onlyOwner { _pause(); } - function unpause() external onlyOwner { _unpause(); } - - function emergencyWithdrawNative() external onlyOwner { - uint256 balance = address(this).balance; - if (balance == 0) revert ZeroAmount(); - (bool sent, ) = owner().call{value: balance}(""); - if (!sent) revert NativeTransferFailed(); - emit EmergencyWithdrawNative(owner(), balance); - } - - function emergencyWithdrawToken(address token) external onlyOwner { - if (token == address(0)) revert ZeroAddress(); - uint256 balance = IERC20(token).balanceOf(address(this)); - if (balance == 0) revert ZeroAmount(); - IERC20(token).safeTransfer(owner(), balance); - emit EmergencyWithdrawToken(token, owner(), balance); - } - - /// @notice Принимаем ETH от Universal Router (рефанды, выходные средства) - receive() external payable {} -} diff --git a/contracts/FeeSwapRouter_TRX.sol b/contracts/FeeSwapRouter_TRX.sol deleted file mode 100644 index 409451b..0000000 --- a/contracts/FeeSwapRouter_TRX.sol +++ /dev/null @@ -1,287 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; - -// ── Адреса (TRON base58 → hex) ── -// SunSwap V2 Router: TKzxdSv2FZKQrEqkKVgp5DcwEXBEKMg2Ax → 0x6e0617948fe030a7e4970f8389d4ad295f249b7e -// USDT (TRC-20): TR7NHqjeKQxGTCi8q8ZY4pL8otSzgjLj6t → 0xa614f803b6fd780986a42c78ec9c7f77e6ded13c -// WTRX: TNUC9Qb1rRpS5CbWLmNMxXBjyFoydXjWFR → 0x891cdb91d149f23b1a45d9c5ca78a88d0cb44c18 -// Fee Recipient: TYTfrem65362TFyQSARTheeYza1GQA37Ug → 0xf6b4D4E650Fc67982894f37ba97Ab2496781ddb6 - -interface ITRC20 { - function balanceOf(address account) external view returns (uint256); - function transfer(address to, uint256 amount) external returns (bool); - function transferFrom(address from, address to, uint256 amount) external returns (bool); - function approve(address spender, uint256 amount) external returns (bool); -} - -/// @title FeeSwapRouter_TRX -/// @notice Обёртка для SunSwap V2 на TRON. Берёт 0.7% комиссию -/// с каждого свапа и бриджа, отправляет на fee wallet. -/// Остальные 99.3% + calldata идут на SunSwap как есть. -/// @dev Generic calldata forwarder — работает с любым роутером. -/// На TRON нет OpenZeppelin, поэтому ReentrancyGuard и Ownable -/// реализованы вручную. -contract FeeSwapRouter_TRX { - - // ── Reentrancy Guard ── - uint256 private constant NOT_ENTERED = 1; - uint256 private constant ENTERED = 2; - uint256 private _status = NOT_ENTERED; - - modifier nonReentrant() { - require(_status != ENTERED, "ReentrancyGuard: reentrant call"); - _status = ENTERED; - _; - _status = NOT_ENTERED; - } - - // ── Ownable ── - address private _owner; - bool private _paused; - - modifier onlyOwner() { - require(msg.sender == _owner, "Ownable: caller is not the owner"); - _; - } - - modifier whenNotPaused() { - require(!_paused, "Pausable: paused"); - _; - } - - function owner() public view returns (address) { return _owner; } - function paused() public view returns (bool) { return _paused; } - - // ── Захардкоженные адреса для TRON ── - - /// @notice SunSwap V2 Smart Router на TRON - /// TRON: TKzxdSv2FZKQrEqkKVgp5DcwEXBEKMg2Ax - address public constant SUNSWAP_ROUTER = 0x6E0617948FE030a7E4970f8389d4Ad295f249B7e; - - /// @notice Кошелёк, получающий 0.7% комиссию - /// TRON: TYTfrem65362TFyQSARTheeYza1GQA37Ug - address public constant FEE_RECIPIENT = 0xf6b4D4E650Fc67982894f37ba97Ab2496781ddb6; - - /// @notice Комиссия 0.7% = 70 basis points (нельзя изменить) - uint16 public constant FEE_BPS = 70; - - /// @notice Делитель для basis points - uint16 private constant BPS_DENOMINATOR = 10_000; - - // ── Events ── - - event SwapWithFeeNative( - address indexed user, - uint256 totalIn, - uint256 feeAmount, - uint256 swapAmount, - address indexed router - ); - - event SwapWithFeeToken( - address indexed user, - address indexed tokenIn, - uint256 totalIn, - uint256 feeAmount, - uint256 swapAmount, - address indexed router - ); - - event BridgeWithFeeNative( - address indexed user, - uint256 totalIn, - uint256 feeAmount - ); - - event BridgeWithFeeToken( - address indexed user, - address indexed tokenIn, - uint256 totalIn, - uint256 feeAmount - ); - - event EmergencyWithdrawNative(address indexed to, uint256 amount); - event EmergencyWithdrawToken(address indexed token, address indexed to, uint256 amount); - event Paused(address account); - event Unpaused(address account); - - // ── Constructor ── - - constructor() { - _owner = msg.sender; - _paused = false; - } - - // ═══════════════════════════════════════════════ - // Swap: TRX → Token - // Пользователь отправляет TRX. Контракт берёт 0.7%, - // остальное + calldata пересылает на SunSwap Router. - // ═══════════════════════════════════════════════ - - /// @notice Свап TRX → токен через SunSwap с 0.7% комиссией. - /// @param routerCalldata Calldata для SunSwap Router - function swapNativeWithFee( - bytes calldata routerCalldata - ) external payable nonReentrant whenNotPaused { - require(msg.value > 0, "Zero value"); - - uint256 feeAmount = (msg.value * FEE_BPS) / BPS_DENOMINATOR; - uint256 swapAmount = msg.value - feeAmount; - - // 0.7% → fee wallet - if (feeAmount > 0) { - (bool feeSent, ) = FEE_RECIPIENT.call{value: feeAmount}(""); - require(feeSent, "Fee transfer failed"); - } - - // 99.3% + calldata → SunSwap Router - (bool success, ) = SUNSWAP_ROUTER.call{value: swapAmount}(routerCalldata); - require(success, "Swap failed"); - - emit SwapWithFeeNative(msg.sender, msg.value, feeAmount, swapAmount, SUNSWAP_ROUTER); - } - - // ═══════════════════════════════════════════════ - // Swap: Token → TRX - // Пользователь approve-ит токены на этот контракт. - // Контракт берёт 0.7% комиссию в токенах, approve-ит - // 99.3% на SunSwap, и пересылает calldata. - // ═══════════════════════════════════════════════ - - /// @notice Свап токен → TRX через SunSwap с 0.7% комиссией. - /// @param tokenIn Адрес входного TRC-20 токена (USDT и т.д.) - /// @param amountIn Полная сумма токенов (включая 0.7% комиссию) - /// @param routerCalldata Calldata для SunSwap Router - function swapTokenWithFee( - address tokenIn, - uint256 amountIn, - bytes calldata routerCalldata - ) external nonReentrant whenNotPaused { - require(amountIn > 0, "Zero amount"); - require(tokenIn != address(0), "Zero address"); - - // Забираем токены у пользователя - require( - ITRC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), - "TransferFrom failed" - ); - - uint256 feeAmount = (amountIn * FEE_BPS) / BPS_DENOMINATOR; - uint256 swapAmount = amountIn - feeAmount; - - // 0.7% комиссию → fee wallet - if (feeAmount > 0) { - require( - ITRC20(tokenIn).transfer(FEE_RECIPIENT, feeAmount), - "Fee transfer failed" - ); - } - - // Approve 99.3% на SunSwap Router - ITRC20(tokenIn).approve(SUNSWAP_ROUTER, swapAmount); - - // Calldata → SunSwap Router - (bool success, ) = SUNSWAP_ROUTER.call(routerCalldata); - require(success, "Swap failed"); - - emit SwapWithFeeToken(msg.sender, tokenIn, amountIn, feeAmount, swapAmount, SUNSWAP_ROUTER); - } - - // ═══════════════════════════════════════════════ - // Bridge Fee: TRX (native) - // Берёт 0.7% с TRX перед бриджем. - // Остаток возвращается пользователю для бриджа. - // ═══════════════════════════════════════════════ - - /// @notice Взять 0.7% комиссию с TRX перед бриджем. - /// Остаток возвращается msg.sender. - function bridgeNativeFee() external payable nonReentrant whenNotPaused { - require(msg.value > 0, "Zero value"); - - uint256 feeAmount = (msg.value * FEE_BPS) / BPS_DENOMINATOR; - uint256 remaining = msg.value - feeAmount; - - // 0.7% → fee wallet - if (feeAmount > 0) { - (bool feeSent, ) = FEE_RECIPIENT.call{value: feeAmount}(""); - require(feeSent, "Fee transfer failed"); - } - - // Остаток → обратно пользователю - if (remaining > 0) { - (bool sent, ) = msg.sender.call{value: remaining}(""); - require(sent, "Return transfer failed"); - } - - emit BridgeWithFeeNative(msg.sender, msg.value, feeAmount); - } - - /// @notice Взять 0.7% комиссию с TRC-20 токена перед бриджем. - /// Остаток возвращается msg.sender. - /// @param tokenIn Адрес TRC-20 токена - /// @param amountIn Полная сумма (включая 0.7%) - function bridgeTokenFee( - address tokenIn, - uint256 amountIn - ) external nonReentrant whenNotPaused { - require(amountIn > 0, "Zero amount"); - require(tokenIn != address(0), "Zero address"); - - require( - ITRC20(tokenIn).transferFrom(msg.sender, address(this), amountIn), - "TransferFrom failed" - ); - - uint256 feeAmount = (amountIn * FEE_BPS) / BPS_DENOMINATOR; - uint256 remaining = amountIn - feeAmount; - - // 0.7% → fee wallet - if (feeAmount > 0) { - require( - ITRC20(tokenIn).transfer(FEE_RECIPIENT, feeAmount), - "Fee transfer failed" - ); - } - - // Остаток → обратно пользователю - if (remaining > 0) { - require( - ITRC20(tokenIn).transfer(msg.sender, remaining), - "Return transfer failed" - ); - } - - emit BridgeWithFeeToken(msg.sender, tokenIn, amountIn, feeAmount); - } - - // ── Admin: Emergency ── - - function pause() external onlyOwner { - _paused = true; - emit Paused(msg.sender); - } - - function unpause() external onlyOwner { - _paused = false; - emit Unpaused(msg.sender); - } - - function emergencyWithdrawNative() external onlyOwner { - uint256 balance = address(this).balance; - require(balance > 0, "Zero balance"); - (bool sent, ) = _owner.call{value: balance}(""); - require(sent, "Transfer failed"); - emit EmergencyWithdrawNative(_owner, balance); - } - - function emergencyWithdrawToken(address token) external onlyOwner { - require(token != address(0), "Zero address"); - uint256 balance = ITRC20(token).balanceOf(address(this)); - require(balance > 0, "Zero balance"); - require(ITRC20(token).transfer(_owner, balance), "Transfer failed"); - emit EmergencyWithdrawToken(token, _owner, balance); - } - - /// @notice Принимаем TRX (рефанды, возвраты) - receive() external payable {} -} diff --git a/docker-check.bat b/docker-check.bat deleted file mode 100644 index f85a0ff..0000000 --- a/docker-check.bat +++ /dev/null @@ -1,35 +0,0 @@ -@echo off -cd /d "%~dp0" - -echo. -echo === Docker diagnostic === -echo. - -echo 1. Docker version: -docker version -echo. - -echo 2. Docker Compose: -docker compose version 2>nul -if errorlevel 1 ( - echo docker compose failed, trying docker-compose... - docker-compose --version 2>nul -) -echo. - -echo 3. Docker context: -docker context show -echo. - -echo 4. Docker info ^(engine status^): -docker info 2>&1 | findstr /C:"Server Version" /C:"ERROR" /C:"cannot" /C:"failed" -echo. - -echo 5. Attempting: docker compose up -d postgres -echo. -docker compose up -d postgres -echo. -echo Exit code: %errorlevel% -echo. - -pause diff --git a/docker-compose.yml b/docker-compose.yml index 73bac52..ada3206 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,178 +2,50 @@ services: postgres: image: postgres:16-alpine container_name: cryptowallet-db + restart: unless-stopped environment: - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - POSTGRES_DB: cryptowallet_devphase3 - ports: - - '5432:5432' + POSTGRES_USER: ${POSTGRES_USER:-postgres} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} + POSTGRES_DB: ${POSTGRES_DB:-cryptowallet_v2} volumes: - pgdata:/var/lib/postgresql/data + # Наружу НЕ экспозим — только docker network healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres"] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-cryptowallet_v2}"] interval: 5s timeout: 5s retries: 10 - - vault: - image: hashicorp/vault:1.18 - container_name: cryptowallet-vault - cap_add: - - IPC_LOCK - environment: - VAULT_ADDR: "http://127.0.0.1:8200" - ports: - - '8200:8200' - volumes: - - ./vault/vault.hcl:/vault/config/vault.hcl:ro - - vault_data:/vault/file - command: ["vault", "server", "-config=/vault/config/vault.hcl"] - healthcheck: - test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:8200/v1/sys/seal-status > /dev/null 2>&1 || exit 0"] - interval: 5s - timeout: 5s - retries: 10 - start_period: 5s - - vault-init: - image: hashicorp/vault:1.18 - container_name: cryptowallet-vault-init - entrypoint: /bin/sh - command: ["/scripts/vault-init.sh"] - volumes: - - ./scripts/vault-init.sh:/scripts/vault-init.sh:ro - - vault_data:/vault/file - environment: - VAULT_ADDR: http://vault:8200 - depends_on: - vault: - condition: service_healthy - restart: "no" + logging: + driver: json-file + options: + max-size: "10m" + max-file: "3" api: build: context: . - dockerfile: apps/api/Dockerfile - container_name: cryptowallet-api - ports: - - '3001:3001' - volumes: - - vault_data:/vault/file:ro - environment: - VAULT_ADDR: http://vault:8200 - DB_HOST: postgres - DB_PORT: 5432 - DB_USER: postgres - DB_PASSWORD: postgres - DB_NAME: cryptowallet_devphase3 - API_PORT: 3001 - FRONTEND_URL: http://localhost:3000 - RELAY_API_KEY: ${RELAY_API_KEY:-} - BITOK_JWKS_URL: http://bitok-auth:8000/.well-known/jwks.json - BITOK_ISSUER: auth-service - BITOK_AUDIENCE: wallet-service - RABBITMQ_URL: amqp://guest:guest@rabbitmq:5672/ - depends_on: - postgres: - condition: service_healthy - vault-init: - condition: service_completed_successfully - rabbitmq: - condition: service_healthy - - web: - build: - context: . - dockerfile: apps/web/Dockerfile - container_name: cryptowallet-web - ports: - - '3000:3000' - environment: - NEXT_PUBLIC_API_URL: http://localhost:3001 - NEXT_PUBLIC_BITOK_URL: http://localhost:8000 - depends_on: - - api - - rabbitmq: - image: rabbitmq:3.13-management-alpine - container_name: cryptowallet-rabbitmq - ports: - - '5672:5672' - - '15672:15672' - environment: - RABBITMQ_DEFAULT_USER: guest - RABBITMQ_DEFAULT_PASS: guest - volumes: - - rabbitmq_data:/var/lib/rabbitmq - healthcheck: - test: ["CMD", "rabbitmq-diagnostics", "check_port_connectivity"] - interval: 10s - timeout: 5s - retries: 10 - - keydb: - image: eqalpha/keydb - container_name: cryptowallet-keydb - ports: - - '6379:6379' - volumes: - - keydb_data:/data - command: - - keydb-server - - --requirepass - - keydb - - --appendonly - - "yes" - healthcheck: - test: ["CMD", "redis-cli", "-a", "keydb", "ping"] - interval: 5s - timeout: 2s - retries: 20 - - bitok-auth: - build: - context: ./BITOK dockerfile: Dockerfile - container_name: cryptowallet-bitok + image: cryptowallet-api:latest + container_name: cryptowallet-api + restart: unless-stopped ports: - - '8000:8000' - volumes: - - vault_data:/vault/file:ro - - ./scripts/bitok-entrypoint.sh:/app/entrypoint.sh:ro - entrypoint: ["sh", "/app/entrypoint.sh"] + - "3001:3001" + env_file: + - .env environment: - VAULT_ADDR: http://vault:8200 - VAULT_MOUNT_POINT: secrets - REDIS_HOST: keydb - REDIS_PORT: 6379 - REDIS_PASSWORD: keydb - JWT_AUDIENCE: "bitforce,wallet-service" - JWT_ISSUER: auth-service - CORS_ORIGINS: "http://localhost:3000,http://localhost:8000" - RABBIT_HOST: rabbitmq - RABBIT_PORT: 5672 - RABBIT_USER: guest - RABBIT_PASSWORD: guest - RABBIT_EVENTS_EXCHANGE: bitok.events - RABBIT_EMAIL_CODE_QUEUE: email.verification_code - OUTBOX_POLL_INTERVAL_MS: 500 - APP_HOST: "0.0.0.0" - APP_PORT: "8000" - APP_WORKERS: "1" + # Override внутри docker network + DB_HOST: postgres + API_PORT: "3001" + NODE_ENV: production depends_on: postgres: condition: service_healthy - vault-init: - condition: service_completed_successfully - keydb: - condition: service_healthy - rabbitmq: - condition: service_healthy - restart: "no" + logging: + driver: json-file + options: + max-size: "20m" + max-file: "5" volumes: pgdata: - vault_data: - rabbitmq_data: - keydb_data: + driver: local diff --git a/docker-start.bat b/docker-start.bat deleted file mode 100644 index 3fe17cf..0000000 --- a/docker-start.bat +++ /dev/null @@ -1,14 +0,0 @@ -@echo off -echo ======================================== -echo CryptoWallet - Docker Full Stack -echo ======================================== -echo. - -docker compose up --build - -echo. -echo Services: -echo Web: http://localhost:3000 -echo API: http://localhost:3001 -echo Vault: http://localhost:8200 -echo. diff --git a/docs/plans/2026-03-14-tron-backend-proxy-design.md b/docs/plans/2026-03-14-tron-backend-proxy-design.md deleted file mode 100644 index 72db967..0000000 --- a/docs/plans/2026-03-14-tron-backend-proxy-design.md +++ /dev/null @@ -1,43 +0,0 @@ -# TRON Backend Proxy Design - -## Problem - -Frontend calls TronGrid API directly from the browser. This causes: -- 429 rate-limit errors (API key passed as query param, not recognized properly) -- API key exposed in `NEXT_PUBLIC_` env var (visible to clients) -- CORS issues possible depending on browser/TronGrid config - -## Solution - -Route TRON balance requests through the backend API proxy, matching the existing relay-proxy pattern. - -## Architecture - -``` -Browser -> GET /api/tron/account/:address -> Express API -> GET https://api.trongrid.io/v1/accounts/:address - Header: TRON-PRO-API-KEY: -``` - -## Changes - -### Backend - -1. **New file: `apps/api/src/routes/tron-proxy.routes.ts`** - - `GET /account/:address` - proxies to TronGrid `/v1/accounts/:address` - - Validates address format (starts with T, 34 chars, base58) - - Sends `TRON-PRO-API-KEY` header (correct TronGrid auth method) - - 10s timeout with AbortController - - Returns TronGrid JSON response as-is - -2. **`apps/api/src/config/env.ts`** - add `tronApiKey` field -3. **`apps/api/src/app.ts`** - register `/api/tron` route - -### Frontend - -4. **`apps/web/src/lib/balances/trx-balances.ts`** - call own API instead of TronGrid -5. **`apps/web/src/lib/env.ts`** - remove `tronApiUrl` and `tronApiKey` -6. **`apps/web/.env.local`** - remove `NEXT_PUBLIC_TRON_*` vars - -### Config - -7. **`.env`** - add `TRON_API_KEY=b874d775-4adc-4273-965b-cd6be5f66d68` diff --git a/docs/plans/2026-03-15-send-receive-design.md b/docs/plans/2026-03-15-send-receive-design.md deleted file mode 100644 index eca58fb..0000000 --- a/docs/plans/2026-03-15-send-receive-design.md +++ /dev/null @@ -1,82 +0,0 @@ -# Phase 6: Send & Receive - -## Context - -Кошелёк поддерживает ETH, SOL, TRX, BTC. Балансы работают для ETH/SOL/TRX. BTC имеет деривированный адрес, но нет RPC/балансов. Нужен полноценный Send/Receive с QR кодами. - -## Receive — генерация QR - -**Формат QR**: стандартные URI схемы (совместимы с другими кошельками): -- ETH: `ethereum:0xAddress` / `ethereum:0xAddress@1/transfer?address=0xTokenContract&uint256=amount` (EIP-681) -- SOL: `solana:Address` / `solana:Address?spl-token=MintAddress&amount=1.5` -- TRX: `tron:TAddress` / `tron:TAddress?token=TR7...&amount=100` -- BTC: `bitcoin:bc1Address` / `bitcoin:bc1Address?amount=0.01` - -**UI flow**: Выбрал сеть → выбрал токен → опционально ввёл сумму → QR + адрес текстом + Copy. - -## Send — отправка + сканер QR - -**UI flow**: Выбрал сеть → выбрал токен → ввёл адрес (или Scan QR) → ввёл сумму → Gas (ETH only) → Review → Confirm → tx. - -**QR сканер**: кнопка "Scan QR" → камера → распознаёт URI → заполняет chain/token/address/amount. - -**Отправка по сетям**: -- **ETH**: `ethers.Wallet.sendTransaction()` для ETH, `contract.transfer()` для ERC20 -- **SOL**: `SystemProgram.transfer()` для SOL, `createTransferInstruction()` для SPL tokens -- **TRX**: TronGrid `createtransaction` для native TRX, `triggersmartcontract` для TRC20 -- **BTC**: `bitcoinjs-lib` PSBT → broadcast через Blockstream/Mempool API - -## Библиотеки - -- `qrcode.react` — генерация QR -- `@yudiel/react-qr-scanner` — сканирование QR камерой - -## Безопасность - -| Принцип | Реализация | -|---------|-----------| -| Address validation | Regex + checksum для каждого формата | -| QR parsing validation | URI парсится → chain/address валидируются → token в whitelist | -| Confirm before send | Review экран + checkbox | -| Private keys | Подписание в браузере, ключи не покидают клиент | -| Amount validation | Проверка баланса перед отправкой | - -## Файлы - -### Новые (8) - -| Файл | Назначение | -|------|-----------| -| `apps/web/src/app/send/page.tsx` | Send page | -| `apps/web/src/app/receive/page.tsx` | Receive page | -| `apps/web/src/lib/send/execute.ts` | Транзакции по 4 сетям | -| `apps/web/src/lib/send/validate.ts` | Валидация адресов | -| `apps/web/src/lib/send/constants.ts` | Токены, decimals, контракты | -| `apps/web/src/lib/qr/generate.ts` | Генерация URI для QR | -| `apps/web/src/lib/qr/parse.ts` | Парсинг URI из QR | -| `apps/api/src/routes/btc-proxy.routes.ts` | Bitcoin RPC proxy | - -### Изменяемые (3) - -| Файл | Изменение | -|------|-----------| -| `apps/web/src/app/dashboard/page.tsx` | Send/Receive кнопки | -| `apps/web/package.json` | qrcode.react, @yudiel/react-qr-scanner | -| `apps/api/src/app.ts` | Регистрация btc-proxy routes | - -## Порядок выполнения - -| # | Задача | -|---|--------| -| 1 | install qrcode.react + @yudiel/react-qr-scanner | -| 2 | send/constants.ts — токены, decimals, контракты для 4 сетей | -| 3 | send/validate.ts — валидация адресов по сетям | -| 4 | qr/generate.ts — URI генератор | -| 5 | qr/parse.ts — URI парсер | -| 6 | btc-proxy.routes.ts — Bitcoin UTXO + broadcast | -| 7 | app.ts — регистрация btc routes | -| 8 | send/execute.ts — отправка по 4 сетям | -| 9 | receive/page.tsx — Receive page | -| 10 | send/page.tsx — Send page + QR scanner | -| 11 | dashboard/page.tsx — Send/Receive кнопки | -| 12 | typecheck | diff --git a/docs/plans/2026-03-18-add-tokens-and-bsc.md b/docs/plans/2026-03-18-add-tokens-and-bsc.md deleted file mode 100644 index 20f17fd..0000000 --- a/docs/plans/2026-03-18-add-tokens-and-bsc.md +++ /dev/null @@ -1,489 +0,0 @@ -# Add Tokens + BSC Chain Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Add new tokens to ETH/SOL swap+balance+send, add BNB Smart Chain as a new EVM network with PancakeSwap V2 swap. - -**Architecture:** Extend existing multi-chain config-driven architecture. BSC reuses ETH's EVM signing logic (same derivation path, same ethers.Wallet). BSC swap via PancakeSwap V2 Router (Uniswap V2 fork) through a new API proxy. All new SOL tokens go through Jupiter (existing proxy). All new ETH tokens go through Uniswap (existing flow). - -**Tech Stack:** ethers.js v5, Next.js, Express, PancakeSwap V2 ABI, Jupiter API, Uniswap V3/V4 SDK - ---- - -## Token Reference - -### New ETH Tokens (Uniswap swap) -| Symbol | Contract (checksummed) | Decimals | CoinGecko ID | -|--------|------------------------|----------|--------------| -| stETH | `0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84` | 18 | `staked-ether` | -| SHIB | `0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE` | 18 | `shiba-inu` | -| LINK | `0x514910771AF9Ca656af840dff83E8264EcF986CA` | 18 | `chainlink` | -| POL | `0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6` | 18 | `polygon-ecosystem-token` | -| WLFI | `0x66f85e3865D0cFDc009aCF6280A8621f12e46CCf` | 18 | `world-liberty-financial` | -| AAVE | `0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9` | 18 | `aave` | - -### New SOL Tokens (Jupiter swap) -| Symbol | Mint | Decimals | CoinGecko ID | -|--------|------|----------|--------------| -| WIF | `EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm` | 6 | `dogwifcoin` | -| POPCAT | `7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr` | 9 | `popcat` | -| TRUMP | `6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN` | 6 | `official-trump` | -| PYTH | `HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3` | 6 | `pyth-network` | -| JTO | `jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL` | 9 | `jito-governance-token` | -| W | `85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ` | 6 | `wormhole` | -| BONK | `DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263` | 5 | `bonk` | -| ORCA | `orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE` | 6 | `orca` | -| PENGU | `2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv` | 6 | `pudgy-penguins` | -| RAY | `4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R` | 6 | `raydium` | - -### BSC Chain Config -| Property | Value | -|----------|-------| -| ChainId | 56 | -| Native | BNB (18 decimals) | -| WBNB | `0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c` | -| DOGE (BEP-20) | `0xbA2aE424d960c26247Dd6c32edC70B295c744C43` (8 decimals) | -| PancakeSwap V2 Router | `0x10ED43C718714eb63d5aA57B78B54704E256024E` | -| Derivation | Same as ETH: `m/44'/60'/0'/0/0` | -| RPC | `https://bsc-dataseed.binance.org` + fallbacks | -| Explorer | `https://bscscan.com/tx/` | - ---- - -## Task 1: Add new ETH tokens to swap constants - -**Files:** -- Modify: `apps/web/src/lib/swap/constants.ts` - -**Steps:** - -1. Add 6 new Token instances after PEPE: - -```ts -const STETH = new Token(ETHEREUM_CHAIN_ID, '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', 18, 'stETH', 'Lido Staked Ether'); -const SHIB = new Token(ETHEREUM_CHAIN_ID, '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE', 18, 'SHIB', 'Shiba Inu'); -const LINK = new Token(ETHEREUM_CHAIN_ID, '0x514910771AF9Ca656af840dff83E8264EcF986CA', 18, 'LINK', 'Chainlink'); -const POL = new Token(ETHEREUM_CHAIN_ID, '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6', 18, 'POL', 'Polygon'); -const WLFI = new Token(ETHEREUM_CHAIN_ID, '0x66f85e3865D0cFDc009aCF6280A8621f12e46CCf', 18, 'WLFI', 'World Liberty Financial'); -const AAVE_TOKEN = new Token(ETHEREUM_CHAIN_ID, '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', 18, 'AAVE', 'Aave'); -``` - -2. Update `SwapTokenSymbol` type to include new symbols: -```ts -export type SwapTokenSymbol = 'ETH' | 'USDT' | 'USDC' | 'XAUT' | 'UNI' | 'PEPE' | 'stETH' | 'SHIB' | 'LINK' | 'POL' | 'WLFI' | 'AAVE'; -``` - -3. Add entries to `SWAP_TOKENS` record for each new token (same pattern as existing). - -4. Update `SWAP_TOKEN_OPTIONS` and `SWAP_TOKEN_OPTIONS_BY_CHAIN.ETH` to include new symbols. - -5. Add V3 pool candidates for new tokens (WETH pairs with MEDIUM/HIGH fees): -```ts -// stETH -{ tokenA: WETH, tokenB: STETH, fee: FeeAmount.LOW }, -{ tokenA: WETH, tokenB: STETH, fee: FeeAmount.MEDIUM }, -// SHIB -{ tokenA: WETH, tokenB: SHIB, fee: FeeAmount.MEDIUM }, -{ tokenA: WETH, tokenB: SHIB, fee: FeeAmount.HIGH }, -// LINK -{ tokenA: WETH, tokenB: LINK, fee: FeeAmount.LOW }, -{ tokenA: WETH, tokenB: LINK, fee: FeeAmount.MEDIUM }, -// POL -{ tokenA: WETH, tokenB: POL, fee: FeeAmount.MEDIUM }, -{ tokenA: WETH, tokenB: POL, fee: FeeAmount.HIGH }, -// WLFI -{ tokenA: WETH, tokenB: WLFI, fee: FeeAmount.MEDIUM }, -{ tokenA: WETH, tokenB: WLFI, fee: FeeAmount.HIGH }, -// AAVE -{ tokenA: WETH, tokenB: AAVE_TOKEN, fee: FeeAmount.LOW }, -{ tokenA: WETH, tokenB: AAVE_TOKEN, fee: FeeAmount.MEDIUM }, -``` - -6. Add V4 pool key candidates similarly. - -7. Update `getSlippageBps` to include new volatile tokens: -```ts -const volatileTokens: SwapTokenSymbol[] = ['PEPE', 'XAUT', 'UNI', 'SHIB', 'WLFI', 'stETH', 'LINK', 'POL', 'AAVE']; -``` - ---- - -## Task 2: Add new ETH tokens to balance fetcher - -**Files:** -- Modify: `apps/web/src/lib/balances/eth-balances.ts` - -**Steps:** - -1. Add 6 new entries to `ETH_TOKENS` array (after PEPE): -```ts -{ chain: 'ETH', symbol: 'stETH', decimals: 18, contractAddress: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', coinGeckoId: 'staked-ether', isNative: false }, -{ chain: 'ETH', symbol: 'SHIB', decimals: 18, contractAddress: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE', coinGeckoId: 'shiba-inu', isNative: false }, -{ chain: 'ETH', symbol: 'LINK', decimals: 18, contractAddress: '0x514910771AF9Ca656af840dff83E8264EcF986CA', coinGeckoId: 'chainlink', isNative: false }, -{ chain: 'ETH', symbol: 'POL', decimals: 18, contractAddress: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6', coinGeckoId: 'polygon-ecosystem-token', isNative: false }, -{ chain: 'ETH', symbol: 'WLFI', decimals: 18, contractAddress: '0x66f85e3865D0cFDc009aCF6280A8621f12e46CCf', coinGeckoId: 'world-liberty-financial', isNative: false }, -{ chain: 'ETH', symbol: 'AAVE', decimals: 18, contractAddress: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', coinGeckoId: 'aave', isNative: false }, -``` - ---- - -## Task 3: Add new ETH tokens to send constants - -**Files:** -- Modify: `apps/web/src/lib/send/constants.ts` - -**Steps:** - -1. Add 6 new token entries under `SEND_CHAINS.ETH.tokens`: -```ts -stETH: { symbol: 'stETH', contractAddress: '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84', decimals: 18 }, -SHIB: { symbol: 'SHIB', contractAddress: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE', decimals: 18 }, -LINK: { symbol: 'LINK', contractAddress: '0x514910771AF9Ca656af840dff83E8264EcF986CA', decimals: 18 }, -POL: { symbol: 'POL', contractAddress: '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6', decimals: 18 }, -WLFI: { symbol: 'WLFI', contractAddress: '0x66f85e3865D0cFDc009aCF6280A8621f12e46CCf', decimals: 18 }, -AAVE: { symbol: 'AAVE', contractAddress: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', decimals: 18 }, -``` - -2. Add corresponding `CONTRACT_TO_SYMBOL` entries (lowercase addresses). - ---- - -## Task 4: Add new SOL tokens to swap constants - -**Files:** -- Modify: `apps/web/src/lib/swap/constants.ts` - -**Steps:** - -1. Add new entries to `SOL_TOKEN_MINTS`: -```ts -WIF: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm', -POPCAT: '7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr', -TRUMP: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', -PYTH: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3', -JTO: 'jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL', -W: '85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ', -BONK: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', -ORCA: 'orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE', -PENGU: '2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv', -RAY: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', -``` - -2. Add entries to `SOL_TOKEN_DECIMALS`: -```ts -WIF: 6, POPCAT: 9, TRUMP: 6, PYTH: 6, JTO: 9, W: 6, BONK: 5, ORCA: 6, PENGU: 6, RAY: 6, -``` - -3. Update `SWAP_TOKEN_OPTIONS_BY_CHAIN.SOL`: -```ts -SOL: ['SOL', 'USDT', 'USDC', 'PUMP', 'JUP', 'WIF', 'POPCAT', 'TRUMP', 'PYTH', 'JTO', 'W', 'BONK', 'ORCA', 'PENGU', 'RAY'], -``` - ---- - -## Task 5: Add new SOL tokens to balance fetcher + Jupiter whitelist - -**Files:** -- Modify: `apps/web/src/lib/balances/sol-balances.ts` -- Modify: `apps/api/src/routes/sol-swap-proxy.routes.ts` - -**Steps:** - -1. Add 10 new entries to `SOL_TOKENS` array in `sol-balances.ts`: -```ts -{ chain: 'SOL', symbol: 'WIF', decimals: 6, contractAddress: 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm', coinGeckoId: 'dogwifcoin', isNative: false }, -{ chain: 'SOL', symbol: 'POPCAT', decimals: 9, contractAddress: '7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr', coinGeckoId: 'popcat', isNative: false }, -{ chain: 'SOL', symbol: 'TRUMP', decimals: 6, contractAddress: '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', coinGeckoId: 'official-trump', isNative: false }, -{ chain: 'SOL', symbol: 'PYTH', decimals: 6, contractAddress: 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3', coinGeckoId: 'pyth-network', isNative: false }, -{ chain: 'SOL', symbol: 'JTO', decimals: 9, contractAddress: 'jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL', coinGeckoId: 'jito-governance-token', isNative: false }, -{ chain: 'SOL', symbol: 'W', decimals: 6, contractAddress: '85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ', coinGeckoId: 'wormhole', isNative: false }, -{ chain: 'SOL', symbol: 'BONK', decimals: 5, contractAddress: 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', coinGeckoId: 'bonk', isNative: false }, -{ chain: 'SOL', symbol: 'ORCA', decimals: 6, contractAddress: 'orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE', coinGeckoId: 'orca', isNative: false }, -{ chain: 'SOL', symbol: 'PENGU', decimals: 6, contractAddress: '2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv', coinGeckoId: 'pudgy-penguins', isNative: false }, -{ chain: 'SOL', symbol: 'RAY', decimals: 6, contractAddress: '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', coinGeckoId: 'raydium', isNative: false }, -``` - -2. Add all 10 new mint addresses to `ALLOWED_MINTS` in `sol-swap-proxy.routes.ts`: -```ts -const ALLOWED_MINTS = new Set([ - 'So11111111111111111111111111111111111111112', // SOL - 'Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB', // USDT - 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', // USDC - 'pumpCmXqMfrsAkQ5r49WcJnRayYRqmXz6ae8H7H9Dfn', // PUMP - 'JUPyiwrYJFskUPiHa7hkeR8VUtAeFoSYbKedZNsDvCN', // JUP - 'EKpQGSJtjMFqKZ9KQanSqYXRcF8fBopzLHYxdM65zcjm', // WIF - '7GCihgDB8fe6KNjn2MYtkzZcRjQy3t9GHdC8uHYmW2hr', // POPCAT - '6p6xgHyF7AeE6TZkSmFsko444wqoP15icUSqi2jfGiPN', // TRUMP - 'HZ1JovNiVvGrGNiiYvEozEVgZ58xaU3RKwX8eACQBCt3', // PYTH - 'jtojtomepa8beP8AuQc6eXt5FriJwfFMwQx2v2f9mCL', // JTO - '85VBFQZC9TZkfaptBWjvUw7YbZjy52A6mjtPGjstQAmQ', // W - 'DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263', // BONK - 'orcaEKTdK7LKz57vaAYr9QeNsVEPfiu6QeMU1kektZE', // ORCA - '2zMMhcVQEXDtdE6vsFS7S7D5oUodfJHE8vd1gnBouauv', // PENGU - '4k3Dyjzvzp8eMZWUXbBCjEvwSkkk59S5iCNLY3QrkX6R', // RAY -]); -``` - ---- - -## Task 6: Add new SOL tokens to send constants - -**Files:** -- Modify: `apps/web/src/lib/send/constants.ts` - -**Steps:** - -1. Add 10 new token entries under `SEND_CHAINS.SOL.tokens` (same mint addresses as balance fetcher). - -2. Add corresponding `CONTRACT_TO_SYMBOL` entries for the new SOL mints. - ---- - -## Task 7: Add BSC chain — wallet derivation - -**Files:** -- Modify: `apps/web/src/lib/crypto/derive-keys.ts` -- Modify: `apps/web/src/lib/balances/types.ts` - -**Steps:** - -1. In `derive-keys.ts`, update `Chain` type: -```ts -export type Chain = 'ETH' | 'BTC' | 'SOL' | 'TRX' | 'BSC'; -``` - -2. Add BSC derivation path (same as ETH): -```ts -BSC: "m/44'/60'/0'/0/0", -``` - -3. In `deriveWalletsFromMnemonic`, add BSC wallet. Since BSC uses the same key as ETH: -```ts -const bsc = deriveEthWallet(mnemonic); // same key derivation -// ... -{ chain: 'BSC', address: bsc.address, privateKey: bsc.privateKey, derivationPath: DERIVATION_PATHS.BSC }, -``` - -4. In `types.ts`, update `BalanceChain`: -```ts -export type BalanceChain = 'ETH' | 'BTC' | 'SOL' | 'TRX' | 'BSC'; -``` - ---- - -## Task 8: Add BSC chain — environment + balance fetcher - -**Files:** -- Modify: `apps/web/src/lib/env.ts` -- Create: `apps/web/src/lib/balances/bsc-balances.ts` -- Modify: `apps/web/src/lib/balances/index.ts` - -**Steps:** - -1. Add BSC RPC to `env.ts`: -```ts -bscRpcUrl: readUrlEnv('NEXT_PUBLIC_BSC_RPC_URL', 'https://bsc-dataseed.binance.org'), -``` - -2. Create `bsc-balances.ts` — copy `eth-balances.ts` pattern with: - - `BSC_CHAIN_ID = 56` - - `BSC_RPC_CANDIDATES`: `webEnv.bscRpcUrl`, `https://bsc-dataseed1.defibit.io`, `https://bsc-dataseed1.ninicoin.io` - - `BSC_TOKENS`: BNB native (18 dec, coinGeckoId `binancecoin`) + DOGE BEP-20 (8 dec, `0xbA2aE424d960c26247Dd6c32edC70B295c744C43`, coinGeckoId `dogecoin`) - - `fetchBscBalances(address)` — same logic as `fetchEthBalances` but with BSC provider - - All internal references use `chain: 'BSC'` - -3. Register in `balances/index.ts`: -```ts -import { fetchBscBalances } from './bsc-balances'; -// ... -const SUPPORTED_CHAINS: BalanceChain[] = ['ETH', 'BTC', 'SOL', 'TRX', 'BSC']; -// ... -BSC: fetchBscBalances, -``` - ---- - -## Task 9: Add BSC chain — swap via PancakeSwap V2 API proxy - -**Files:** -- Create: `apps/api/src/routes/bsc-swap-proxy.routes.ts` -- Modify: `apps/api/src/app.ts` - -**Steps:** - -1. Create `bsc-swap-proxy.routes.ts` — PancakeSwap V2 swap proxy (same pattern as `tron-swap-proxy.routes.ts` but simpler since it's EVM): - -``` -Router endpoints: - GET /quote — calls PancakeSwap V2 Router getAmountsOut via BSC RPC - POST /build — builds swap calldata (approve + swap tx) - -Key constants: - PANCAKE_ROUTER = '0x10ED43C718714eb63d5aA57B78B54704E256024E' - WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c' - DOGE = '0xbA2aE424d960c26247Dd6c32edC70B295c744C43' - BSC_RPC = 'https://bsc-dataseed.binance.org' - -Quote logic: - - Create ethers provider with BSC RPC - - Call router.getAmountsOut(amountIn, [fromToken, toToken]) - - Return { amountIn, amountOut, from, to } - -Build logic: - - For BNB->Token: encode swapExactETHForTokensSupportingFeeOnTransferTokens calldata - - For Token->BNB: check/build approve tx + encode swapExactTokensForETHSupportingFeeOnTransferTokens calldata - - Return { calldata, to, value, approveTx? } -``` - -2. Register in `app.ts`: -```ts -import bscSwapProxyRoutes from './routes/bsc-swap-proxy.routes'; -// ... -app.use('/api/bsc/swap', bscSwapProxyRoutes); -``` - ---- - -## Task 10: Add BSC to swap constants + UI - -**Files:** -- Modify: `apps/web/src/lib/swap/constants.ts` - -**Steps:** - -1. Update `SwapChain`: -```ts -export type SwapChain = 'ETH' | 'SOL' | 'TRX' | 'BSC'; -``` - -2. Add BSC token configs: -```ts -export const BSC_TOKEN_ADDRESSES: Record = { - BNB: 'native', - DOGE: '0xbA2aE424d960c26247Dd6c32edC70B295c744C43', -}; - -export const BSC_TOKEN_DECIMALS: Record = { - BNB: 18, - DOGE: 8, -}; -``` - -3. Add BSC to `SWAP_TOKEN_OPTIONS_BY_CHAIN`: -```ts -BSC: ['BNB', 'DOGE'], -``` - -4. Add BSC to `CHAIN_DEFAULT_TOKENS`: -```ts -BSC: { from: 'BNB', to: 'DOGE' }, -``` - -5. Update `getSlippageBpsForChain` to handle BSC: -```ts -if (chain === 'BSC') return 50; // 0.50% -``` - -6. Update `getExplorerTxUrl`: -```ts -case 'BSC': return `https://bscscan.com/tx/${txHash}`; -``` - ---- - -## Task 11: Add BSC swap execution (client-side) - -**Files:** -- Create: `apps/web/src/lib/swap/bsc/execute.ts` -- Modify: swap UI hooks/page to add BSC chain option - -**Steps:** - -1. Create `bsc/execute.ts`: -```ts -// Similar to TRX swap execute but using ethers.Wallet directly -// 1. Fetch quote from /api/bsc/swap/quote -// 2. Build tx from /api/bsc/swap/build -// 3. If approve needed: sign and send approve tx, wait for confirmation -// 4. Sign and send swap tx via ethers.Wallet connected to BSC RPC -// 5. Return { hash, explorerUrl } -``` - -2. Update swap page `CHAINS` array: -```ts -const CHAINS: SwapChain[] = ['ETH', 'SOL', 'TRX', 'BSC']; -``` - -3. Update `useSwap` hook to handle BSC chain (dispatch to `executeBscSwap`). - ---- - -## Task 12: Add BSC to send/receive - -**Files:** -- Modify: `apps/web/src/lib/send/constants.ts` -- Modify: `apps/web/src/lib/send/execute.ts` -- Modify: `apps/web/src/lib/send/validate.ts` -- Modify: `apps/web/src/lib/qr/generate.ts` -- Modify: `apps/web/src/lib/qr/parse.ts` - -**Steps:** - -1. In `send/constants.ts`: - - Update `SendChain`: `'ETH' | 'SOL' | 'TRX' | 'BTC' | 'BSC'` - - Add BSC config: - ```ts - BSC: { - key: 'BSC', - label: 'BNB Smart Chain', - walletChain: 'BSC', - tokens: { - BNB: { symbol: 'BNB', contractAddress: null, decimals: 18 }, - DOGE: { symbol: 'DOGE', contractAddress: '0xbA2aE424d960c26247Dd6c32edC70B295c744C43', decimals: 8 }, - }, - explorerTxUrl: 'https://bscscan.com/tx/', - }, - ``` - - Add to `SEND_CHAIN_OPTIONS` - -2. In `send/execute.ts`: - - Add BSC case — same as ETH but with BSC provider: - ```ts - case 'BSC': - return executeBscSend(params); - ``` - - `executeBscSend`: create `ethers.providers.StaticJsonRpcProvider(bscRpcUrl, 56)`, use `ethers.Wallet` for native BNB or BEP-20 transfer (identical to ETH logic). - -3. In `send/validate.ts`: - - BSC uses same address format as ETH — add `case 'BSC': return validateEthAddress(address);` - - Update `detectChainFromAddress` — BSC can't be auto-detected (same format as ETH) - -4. In `qr/generate.ts`: - - Add BSC case using `ethereum:` URI scheme with `@56` chain discriminator: - ```ts - case 'BSC': - return generateBscUri(address, token, amount); - ``` - - `generateBscUri`: `ethereum:${address}@56` for native, `ethereum:${contract}@56/transfer?address=${to}` for BEP-20 - -5. In `qr/parse.ts`: - - Handle `@56` chain discriminator in ethereum: URIs to distinguish BSC from ETH - ---- - -## Task 13: Run typecheck and verify - -**Steps:** - -1. Run `pnpm typecheck` from monorepo root -2. Fix any TypeScript errors -3. Verify all imports resolve correctly - ---- - -## Commit Strategy - -- After Tasks 1-3 (ETH tokens): commit "feat: add stETH, SHIB, LINK, POL, WLFI, AAVE tokens to ETH swap/balance/send" -- After Tasks 4-6 (SOL tokens): commit "feat: add WIF, POPCAT, TRUMP, PYTH, JTO, W, BONK, ORCA, PENGU, RAY to SOL swap/balance/send" -- After Tasks 7-12 (BSC chain): commit "feat: add BNB Smart Chain with PancakeSwap V2 swap, BNB + DOGE tokens" -- After Task 13: commit "fix: resolve typecheck errors" (if needed) diff --git a/packages/shared/package.json b/packages/shared/package.json deleted file mode 100644 index cac9429..0000000 --- a/packages/shared/package.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "@cryptowallet/shared", - "version": "0.1.0", - "private": true, - "main": "./src/index.ts", - "types": "./src/index.ts", - "scripts": { - "typecheck": "tsc --noEmit" - }, - "devDependencies": { - "typescript": "^5.6.0" - } -} diff --git a/packages/shared/src/constants/chains.ts b/packages/shared/src/constants/chains.ts deleted file mode 100644 index 2aefc9b..0000000 --- a/packages/shared/src/constants/chains.ts +++ /dev/null @@ -1,9 +0,0 @@ -export const CHAINS = ['ETH', 'BTC', 'SOL', 'TRX'] as const; -export type Chain = (typeof CHAINS)[number]; - -export const DERIVATION_PATHS: Record = { - ETH: "m/44'/60'/0'/0/0", - BTC: "m/84'/0'/0'/0/0", - SOL: "m/44'/501'/0'/0'", - TRX: "m/44'/195'/0'/0/0", -}; diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts deleted file mode 100644 index 3d919b5..0000000 --- a/packages/shared/src/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { CHAINS, DERIVATION_PATHS } from './constants/chains'; -export type { Chain } from './constants/chains'; -export type { RegisterRequest, LoginRequest, AuthResponse, ApiResponse } from './types/auth'; diff --git a/packages/shared/src/types/auth.ts b/packages/shared/src/types/auth.ts deleted file mode 100644 index 6bc8a52..0000000 --- a/packages/shared/src/types/auth.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { Chain } from '../constants/chains'; - -export interface RegisterRequest { - username: string; - password: string; - pin: string; - encryptedVault: string; - vaultSalt: string; - wallets: { chain: Chain; address: string; derivationPath: string }[]; -} - -export interface LoginRequest { - username: string; - password: string; - pin: string; -} - -export interface AuthResponse { - accessToken: string; - user: { id: string; username: string }; - encryptedVault?: string; - vaultSalt?: string; - wallets?: { chain: Chain; address: string; derivationPath: string }[]; - mnemonicShown: boolean; -} - -export interface ApiResponse { - success: boolean; - data?: T; - error?: string; -} diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json deleted file mode 100644 index d650fa5..0000000 --- a/packages/shared/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2020", - "module": "commonjs", - "strict": true, - "esModuleInterop": true, - "skipLibCheck": true, - "declaration": true - }, - "include": ["src/**/*"] -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b5ede7c..1be52ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,15 +17,6 @@ importers: apps/api: dependencies: - '@cryptowallet/shared': - specifier: workspace:* - version: link:../../packages/shared - amqplib: - specifier: ^1.0.3 - version: 1.0.3 - bcrypt: - specifier: ^5.1.1 - version: 5.1.1 cookie-parser: specifier: ^1.4.7 version: 1.4.7 @@ -37,44 +28,32 @@ importers: version: 16.6.1 ethers: specifier: 5.7.2 - version: 5.7.2(bufferutil@4.1.0)(utf-8-validate@6.0.6) + version: 5.7.2(bufferutil@4.1.0) express: specifier: ^4.21.0 version: 4.22.1 - express-rate-limit: - specifier: ^7.4.0 - version: 7.5.1(express@4.22.1) helmet: specifier: ^8.0.0 version: 8.1.0 jose: specifier: ^6.2.2 version: 6.2.2 - jsonwebtoken: - specifier: ^9.0.0 - version: 9.0.3 knex: specifier: ^3.1.0 version: 3.1.0(pg@8.20.0) pg: specifier: ^8.13.0 version: 8.20.0 + swagger-ui-express: + specifier: ^5.0.1 + version: 5.0.1(express@4.22.1) ulidx: specifier: ^2.4.1 version: 2.4.1 - uuid: - specifier: ^11.0.0 - version: 11.1.0 zod: specifier: ^3.23.0 version: 3.25.76 devDependencies: - '@types/amqplib': - specifier: ^0.10.8 - version: 0.10.8 - '@types/bcrypt': - specifier: ^5.0.2 - version: 5.0.2 '@types/cookie-parser': specifier: ^1.4.7 version: 1.4.10(@types/express@5.0.6) @@ -87,15 +66,21 @@ importers: '@types/express-serve-static-core': specifier: ^5.1.1 version: 5.1.1 - '@types/jsonwebtoken': - specifier: ^9.0.0 - version: 9.0.10 '@types/node': specifier: ^20.0.0 version: 20.19.37 - '@types/uuid': - specifier: ^10.0.0 - version: 10.0.0 + '@types/swagger-ui-express': + specifier: ^4.1.8 + version: 4.1.8 + '@typescript-eslint/eslint-plugin': + specifier: ^7.18.0 + version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^7.18.0 + version: 7.18.0(eslint@8.57.1)(typescript@5.9.3) + eslint: + specifier: ^8.57.1 + version: 8.57.1 ts-node: specifier: ^10.9.0 version: 10.9.2(@types/node@20.19.37)(typescript@5.9.3) @@ -106,109 +91,29 @@ importers: specifier: ^5.6.0 version: 5.9.3 - apps/web: - dependencies: - '@scure/bip32': - specifier: ^2.0.1 - version: 2.0.1 - '@scure/bip39': - specifier: ^2.0.1 - version: 2.0.1 - '@solana/web3.js': - specifier: ^1.98.4 - version: 1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6) - '@uniswap/router-sdk': - specifier: ^2.7.1 - version: 2.7.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) - '@uniswap/sdk-core': - specifier: ^7.12.1 - version: 7.12.1 - '@uniswap/universal-router-sdk': - specifier: ^4.34.0 - version: 4.34.0(bufferutil@4.1.0)(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6))(utf-8-validate@6.0.6) - '@uniswap/v3-sdk': - specifier: ^3.29.1 - version: 3.29.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) - '@uniswap/v4-sdk': - specifier: ^1.29.1 - version: 1.29.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) - '@yudiel/react-qr-scanner': - specifier: ^2.5.1 - version: 2.5.1(@types/emscripten@1.41.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - bip32: - specifier: ^5.0.1 - version: 5.0.1(typescript@5.9.3) - bitcoinjs-lib: - specifier: ^7.0.1 - version: 7.0.1(typescript@5.9.3) - ecpair: - specifier: ^3.0.1 - version: 3.0.1(typescript@5.9.3) - ed25519-hd-key: - specifier: ^1.3.0 - version: 1.3.0 - ethers: - specifier: 5.7.2 - version: 5.7.2(bufferutil@4.1.0)(utf-8-validate@6.0.6) - next: - specifier: 16.1.6 - version: 16.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3) - qrcode.react: - specifier: ^4.2.0 - version: 4.2.0(react@19.2.3) - react: - specifier: 19.2.3 - version: 19.2.3 - react-dom: - specifier: 19.2.3 - version: 19.2.3(react@19.2.3) - tiny-secp256k1: - specifier: ^2.2.4 - version: 2.2.4 - zustand: - specifier: ^5.0.11 - version: 5.0.11(@types/react@19.2.14)(react@19.2.3) - devDependencies: - '@types/node': - specifier: ^20 - version: 20.19.37 - '@types/react': - specifier: ^19 - version: 19.2.14 - '@types/react-dom': - specifier: ^19 - version: 19.2.3(@types/react@19.2.14) - typescript: - specifier: ^5 - version: 5.9.3 - - packages/shared: - devDependencies: - typescript: - specifier: ^5.6.0 - version: 5.9.3 - packages: - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} - engines: {node: '>=6.9.0'} - '@cspotcode/source-map-support@0.8.1': resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} - '@emnapi/runtime@1.8.1': - resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - '@ethereumjs/rlp@5.0.2': - resolution: {integrity: sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA==} - engines: {node: '>=18'} - hasBin: true + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@ethereumjs/util@9.1.0': - resolution: {integrity: sha512-XBEKsYqLGXLah9PNJbgdkigthkG7TAGvlD/sH12beMXEyHDyigfcbdvHhmLyDWgDyOJn4QwiQUaF7yeuhnjdog==} - engines: {node: '>=18'} + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} '@ethersproject/abi@5.7.0': resolution: {integrity: sha512-351ktp42TiRcYB3H1OP8yajPeAQstMW/yCFokj/AthP9bLHzQFPlOrxOcwYEDkUAICmOHljvN4K39OMTMUa9RA==} @@ -345,9 +250,6 @@ packages: '@ethersproject/solidity@5.7.0': resolution: {integrity: sha512-HmabMd2Dt/raavyaGukF4XxizWKhKQ24DoLtdNbBmNKUOPqwjsKQSdV9GQtj9CBEea9DlzETlVER1gYeXXBGaA==} - '@ethersproject/solidity@5.8.0': - resolution: {integrity: sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==} - '@ethersproject/strings@5.7.0': resolution: {integrity: sha512-/9nu+lj0YswRNSH0NXYqrh8775XNyEdUQAuf3f+SmOrnVewcJ5SBNAjF7lpgehKi4abvNNXyf+HX86czCdJ8Mg==} @@ -378,162 +280,18 @@ packages: '@ethersproject/wordlists@5.8.0': resolution: {integrity: sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==} - '@fastify/busboy@2.1.1': - resolution: {integrity: sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==} - engines: {node: '>=14'} + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead - '@img/colour@1.1.0': - resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} - engines: {node: '>=18'} + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} - '@img/sharp-darwin-arm64@0.34.5': - resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [darwin] - - '@img/sharp-darwin-x64@0.34.5': - resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-darwin-arm64@1.2.4': - resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} - cpu: [arm64] - os: [darwin] - - '@img/sharp-libvips-darwin-x64@1.2.4': - resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} - cpu: [x64] - os: [darwin] - - '@img/sharp-libvips-linux-arm64@1.2.4': - resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-arm@1.2.4': - resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-ppc64@1.2.4': - resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-riscv64@1.2.4': - resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-s390x@1.2.4': - resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linux-x64@1.2.4': - resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-linux-arm64@0.34.5': - resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-arm@0.34.5': - resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-ppc64@0.34.5': - resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-riscv64@0.34.5': - resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-s390x@0.34.5': - resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@img/sharp-linux-x64@0.34.5': - resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@img/sharp-linuxmusl-arm64@0.34.5': - resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@img/sharp-linuxmusl-x64@0.34.5': - resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [linux] - libc: [musl] - - '@img/sharp-wasm32@0.34.5': - resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [wasm32] - - '@img/sharp-win32-arm64@0.34.5': - resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [arm64] - os: [win32] - - '@img/sharp-win32-ia32@0.34.5': - resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [ia32] - os: [win32] - - '@img/sharp-win32-x64@0.34.5': - resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} - cpu: [x64] - os: [win32] + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead '@jridgewell/resolve-uri@3.1.2': resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} @@ -545,261 +303,20 @@ packages: '@jridgewell/trace-mapping@0.3.9': resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} - '@mapbox/node-pre-gyp@1.0.11': - resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} - hasBin: true + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} - '@next/env@16.1.6': - resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==} + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} - '@next/swc-darwin-arm64@16.1.6': - resolution: {integrity: sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [darwin] + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} - '@next/swc-darwin-x64@16.1.6': - resolution: {integrity: sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [darwin] - - '@next/swc-linux-arm64-gnu@16.1.6': - resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-arm64-musl@16.1.6': - resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@next/swc-linux-x64-gnu@16.1.6': - resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@next/swc-linux-x64-musl@16.1.6': - resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} - engines: {node: '>= 10'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@next/swc-win32-arm64-msvc@16.1.6': - resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} - engines: {node: '>= 10'} - cpu: [arm64] - os: [win32] - - '@next/swc-win32-x64-msvc@16.1.6': - resolution: {integrity: sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==} - engines: {node: '>= 10'} - cpu: [x64] - os: [win32] - - '@noble/curves@1.4.2': - resolution: {integrity: sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==} - - '@noble/curves@1.8.2': - resolution: {integrity: sha512-vnI7V6lFNe0tLAuJMu+2sX+FcL14TaCWy1qiczg1VwRmPrpQCdq5ESXQMqUc2tluRNf6irBXrWbl1mGN8uaU/g==} - engines: {node: ^14.21.3 || >=16} - - '@noble/curves@1.9.7': - resolution: {integrity: sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==} - engines: {node: ^14.21.3 || >=16} - - '@noble/curves@2.0.1': - resolution: {integrity: sha512-vs1Az2OOTBiP4q0pwjW5aF0xp9n4MxVrmkFBxc6EKZc6ddYx5gaZiAsZoq0uRRXWbi3AT/sBqn05eRPtn1JCPw==} - engines: {node: '>= 20.19.0'} - - '@noble/hashes@1.2.0': - resolution: {integrity: sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ==} - - '@noble/hashes@1.4.0': - resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} - engines: {node: '>= 16'} - - '@noble/hashes@1.7.2': - resolution: {integrity: sha512-biZ0NUSxyjLLqo6KxEJ1b+C2NAx0wtDoFvCaXHGgUkeHzf3Xc1xKumFKREuT7f7DARNZ/slvYUwFG6B0f2b6hQ==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@1.8.0': - resolution: {integrity: sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==} - engines: {node: ^14.21.3 || >=16} - - '@noble/hashes@2.0.1': - resolution: {integrity: sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==} - engines: {node: '>= 20.19.0'} - - '@noble/secp256k1@1.7.1': - resolution: {integrity: sha512-hOUk6AyBFmqVrv7k5WAw/LpszxVbj9gGN4JRkIX52fdFAj1UA61KXmZDvqVEm+pOyec3+fIeZB02LYa/pWOArw==} - - '@nomicfoundation/edr-darwin-arm64@0.12.0-next.23': - resolution: {integrity: sha512-Amh7mRoDzZyJJ4efqoePqdoZOzharmSOttZuJDlVE5yy07BoE8hL6ZRpa5fNYn0LCqn/KoWs8OHANWxhKDGhvQ==} - engines: {node: '>= 20'} - - '@nomicfoundation/edr-darwin-x64@0.12.0-next.23': - resolution: {integrity: sha512-9wn489FIQm7m0UCD+HhktjWx6vskZzeZD9oDc2k9ZvbBzdXwPp5tiDqUBJ+eQpByAzCDfteAJwRn2lQCE0U+Iw==} - engines: {node: '>= 20'} - - '@nomicfoundation/edr-linux-arm64-gnu@0.12.0-next.23': - resolution: {integrity: sha512-nlk5EejSzEUfEngv0Jkhqq3/wINIfF2ED9wAofc22w/V1DV99ASh9l3/e/MIHOQFecIZ9MDqt0Em9/oDyB1Uew==} - engines: {node: '>= 20'} - - '@nomicfoundation/edr-linux-arm64-musl@0.12.0-next.23': - resolution: {integrity: sha512-SJuPBp3Rc6vM92UtVTUxZQ/QlLhLfwTftt2XUiYohmGKB3RjGzpgduEFMCA0LEnucUckU6UHrJNFHiDm77C4PQ==} - engines: {node: '>= 20'} - - '@nomicfoundation/edr-linux-x64-gnu@0.12.0-next.23': - resolution: {integrity: sha512-NU+Qs3u7Qt6t3bJFdmmjd5CsvgI2bPPzO31KifM2Ez96/jsXYho5debtTQnimlb5NAqiHTSlxjh/F8ROcptmeQ==} - engines: {node: '>= 20'} - - '@nomicfoundation/edr-linux-x64-musl@0.12.0-next.23': - resolution: {integrity: sha512-F78fZA2h6/ssiCSZOovlgIu0dUeI7ItKPsDDF3UUlIibef052GCXmliMinC90jVPbrjUADMd1BUwjfI0Z8OllQ==} - engines: {node: '>= 20'} - - '@nomicfoundation/edr-win32-x64-msvc@0.12.0-next.23': - resolution: {integrity: sha512-IfJZQJn7d/YyqhmguBIGoCKjE9dKjbu6V6iNEPApfwf5JyyjHYyyfkLU4rf7hygj57bfH4sl1jtQ6r8HnT62lw==} - engines: {node: '>= 20'} - - '@nomicfoundation/edr@0.12.0-next.23': - resolution: {integrity: sha512-F2/6HZh8Q9RsgkOIkRrckldbhPjIZY7d4mT9LYuW68miwGQ5l7CkAgcz9fRRiurA0+YJhtsbx/EyrD9DmX9BOw==} - engines: {node: '>= 20'} - - '@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.2': - resolution: {integrity: sha512-JaqcWPDZENCvm++lFFGjrDd8mxtf+CtLd2MiXvMNTBD33dContTZ9TWETwNFwg7JTJT5Q9HEecH7FA+HTSsIUw==} - engines: {node: '>= 12'} - - '@nomicfoundation/solidity-analyzer-darwin-x64@0.1.2': - resolution: {integrity: sha512-fZNmVztrSXC03e9RONBT+CiksSeYcxI1wlzqyr0L7hsQlK1fzV+f04g2JtQ1c/Fe74ZwdV6aQBdd6Uwl1052sw==} - engines: {node: '>= 12'} - - '@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.2': - resolution: {integrity: sha512-3d54oc+9ZVBuB6nbp8wHylk4xh0N0Gc+bk+/uJae+rUgbOBwQSfuGIbAZt1wBXs5REkSmynEGcqx6DutoK0tPA==} - engines: {node: '>= 12'} - - '@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.2': - resolution: {integrity: sha512-iDJfR2qf55vgsg7BtJa7iPiFAsYf2d0Tv/0B+vhtnI16+wfQeTbP7teookbGvAo0eJo7aLLm0xfS/GTkvHIucA==} - engines: {node: '>= 12'} - - '@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.2': - resolution: {integrity: sha512-9dlHMAt5/2cpWyuJ9fQNOUXFB/vgSFORg1jpjX1Mh9hJ/MfZXlDdHQ+DpFCs32Zk5pxRBb07yGvSHk9/fezL+g==} - engines: {node: '>= 12'} - - '@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.2': - resolution: {integrity: sha512-GzzVeeJob3lfrSlDKQw2bRJ8rBf6mEYaWY+gW0JnTDHINA0s2gPR4km5RLIj1xeZZOYz4zRw+AEeYgLRqB2NXg==} - engines: {node: '>= 12'} - - '@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.2': - resolution: {integrity: sha512-Fdjli4DCcFHb4Zgsz0uEJXZ2K7VEO+w5KVv7HmT7WO10iODdU9csC2az4jrhEsRtiR9Gfd74FlG0NYlw1BMdyA==} - engines: {node: '>= 12'} - - '@nomicfoundation/solidity-analyzer@0.1.2': - resolution: {integrity: sha512-q4n32/FNKIhQ3zQGGw5CvPF6GTvDCpYwIf7bEY/dZTZbgfDsHyjJwURxUJf3VQuuJj+fDIFl4+KkBVbw4Ef6jA==} - engines: {node: '>= 12'} - - '@openzeppelin/contracts@3.4.1-solc-0.7-2': - resolution: {integrity: sha512-tAG9LWg8+M2CMu7hIsqHPaTyG4uDzjr6mhvH96LvOpLZZj6tgzTluBt+LsCf1/QaYrlis6pITvpIaIhE+iZB+Q==} - - '@openzeppelin/contracts@3.4.2-solc-0.7': - resolution: {integrity: sha512-W6QmqgkADuFcTLzHL8vVoNBtkwjvQRpYIAom7KiUNoLKghyx3FgH0GBjt8NRvigV1ZmMOBllvE1By1C+bi8WpA==} - - '@openzeppelin/contracts@4.7.0': - resolution: {integrity: sha512-52Qb+A1DdOss8QvJrijYYPSf32GUg2pGaG/yCxtaA3cu4jduouTdg4XZSMLW9op54m1jH7J8hoajhHKOPsoJFw==} - - '@openzeppelin/contracts@5.0.2': - resolution: {integrity: sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA==} - - '@scure/base@1.1.9': - resolution: {integrity: sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==} - - '@scure/base@1.2.6': - resolution: {integrity: sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==} - - '@scure/base@2.0.0': - resolution: {integrity: sha512-3E1kpuZginKkek01ovG8krQ0Z44E3DHPjc5S2rjJw9lZn3KSQOs8S7wqikF/AH7iRanHypj85uGyxk0XAyC37w==} - - '@scure/bip32@1.1.5': - resolution: {integrity: sha512-XyNh1rB0SkEqd3tXcXMi+Xe1fvg+kUIcoRIEujP1Jgv7DqW2r9lg3Ah0NkFaCs9sTkQAQA8kw7xiRXzENi9Rtw==} - - '@scure/bip32@1.4.0': - resolution: {integrity: sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==} - - '@scure/bip32@2.0.1': - resolution: {integrity: sha512-4Md1NI5BzoVP+bhyJaY3K6yMesEFzNS1sE/cP+9nuvE7p/b0kx9XbpDHHFl8dHtufcbdHRUUQdRqLIPHN/s7yA==} - - '@scure/bip39@1.1.1': - resolution: {integrity: sha512-t+wDck2rVkh65Hmv280fYdVdY25J9YeEUIgn2LG1WM6gxFkGzcksoDiUkWVpVp3Oex9xGC68JU2dSbUfwZ2jPg==} - - '@scure/bip39@1.3.0': - resolution: {integrity: sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==} - - '@scure/bip39@2.0.1': - resolution: {integrity: sha512-PsxdFj/d2AcJcZDX1FXN3dDgitDDTmwf78rKZq1a6c1P1Nan1X/Sxc7667zU3U+AN60g7SxxP0YCVw2H/hBycg==} - - '@sentry/core@5.30.0': - resolution: {integrity: sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==} - engines: {node: '>=6'} - - '@sentry/hub@5.30.0': - resolution: {integrity: sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==} - engines: {node: '>=6'} - - '@sentry/minimal@5.30.0': - resolution: {integrity: sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==} - engines: {node: '>=6'} - - '@sentry/node@5.30.0': - resolution: {integrity: sha512-Br5oyVBF0fZo6ZS9bxbJZG4ApAjRqAnqFFurMVJJdunNb80brh7a5Qva2kjhm+U6r9NJAB5OmDyPkA1Qnt+QVg==} - engines: {node: '>=6'} - - '@sentry/tracing@5.30.0': - resolution: {integrity: sha512-dUFowCr0AIMwiLD7Fs314Mdzcug+gBVo/+NCMyDw8tFxJkwWAKl7Qa2OZxLQ0ZHjakcj1hNKfCQJ9rhyfOl4Aw==} - engines: {node: '>=6'} - - '@sentry/types@5.30.0': - resolution: {integrity: sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==} - engines: {node: '>=6'} - - '@sentry/utils@5.30.0': - resolution: {integrity: sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==} - engines: {node: '>=6'} - - '@solana/buffer-layout@4.0.1': - resolution: {integrity: sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==} - engines: {node: '>=5.10'} - - '@solana/codecs-core@2.3.0': - resolution: {integrity: sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==} - engines: {node: '>=20.18.0'} - peerDependencies: - typescript: '>=5.3.3' - - '@solana/codecs-numbers@2.3.0': - resolution: {integrity: sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==} - engines: {node: '>=20.18.0'} - peerDependencies: - typescript: '>=5.3.3' - - '@solana/errors@2.3.0': - resolution: {integrity: sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==} - engines: {node: '>=20.18.0'} - hasBin: true - peerDependencies: - typescript: '>=5.3.3' - - '@solana/web3.js@1.98.4': - resolution: {integrity: sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==} - - '@swc/helpers@0.5.15': - resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@scarf/scarf@1.4.0': + resolution: {integrity: sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==} '@tsconfig/node10@1.0.12': resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==} @@ -813,12 +330,6 @@ packages: '@tsconfig/node16@1.0.4': resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} - '@types/amqplib@0.10.8': - resolution: {integrity: sha512-vtDp8Pk1wsE/AuQ8/Rgtm6KUZYqcnTgNvEHwzCkX8rL7AGsC6zqAfKAAJhUZXFhM/Pp++tbnUHiam/8vVpPztA==} - - '@types/bcrypt@5.0.2': - resolution: {integrity: sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==} - '@types/body-parser@1.19.6': resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} @@ -833,9 +344,6 @@ packages: '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} - '@types/emscripten@1.41.5': - resolution: {integrity: sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==} - '@types/express-serve-static-core@5.1.1': resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} @@ -845,15 +353,6 @@ packages: '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} - '@types/jsonwebtoken@9.0.10': - resolution: {integrity: sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@12.20.55': - resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} - '@types/node@20.19.37': resolution: {integrity: sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==} @@ -863,14 +362,6 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} - '@types/react-dom@19.2.3': - resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} - peerDependencies: - '@types/react': ^19.2.0 - - '@types/react@19.2.14': - resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - '@types/send@1.2.1': resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} @@ -883,84 +374,79 @@ packages: '@types/strip-json-comments@0.0.30': resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} - '@types/uuid@10.0.0': - resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==} + '@types/swagger-ui-express@4.1.8': + resolution: {integrity: sha512-AhZV8/EIreHFmBV5wAs0gzJUNq9JbbSXgJLQubCC0jtIo6prnI9MIRRxnU4MZX9RB9yXxF1V4R7jtLl/Wcj31g==} - '@types/ws@7.4.7': - resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} - - '@types/ws@8.18.1': - resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - - '@uniswap/lib@4.0.1-alpha': - resolution: {integrity: sha512-f6UIliwBbRsgVLxIaBANF6w09tYqc6Y/qXdsrbEmXHyFA7ILiKrIwRFXe1yOg8M3cksgVsO9N7yuL2DdCGQKBA==} - engines: {node: '>=10'} - - '@uniswap/permit2-sdk@1.4.0': - resolution: {integrity: sha512-l/aGhfhB93M76vXs4eB8QNwhELE6bs66kh7F1cyobaPtINaVpMmlJv+j3KmHeHwAZIsh7QXyYzhDxs07u0Pe4Q==} - - '@uniswap/router-sdk@2.7.1': - resolution: {integrity: sha512-d2+P+761vchusTTC019bSh8dqCUOEZ+JQzgOXXuMkbcxzs81bOIL0WAeKO4azhZhcRQpxCrRqQYEeSX8dE/Oww==} - - '@uniswap/sdk-core@7.12.1': - resolution: {integrity: sha512-qQG6dVFIgk9x+WRQBDZms/abi+aM6J6YFLhaMnrvTSFIIL4hOjf74SpOep5wPEDZGx3iw3LqofRq+ijeVwLLBQ==} - engines: {node: '>=10'} - - '@uniswap/swap-router-contracts@1.3.1': - resolution: {integrity: sha512-mh/YNbwKb7Mut96VuEtL+Z5bRe0xVIbjjiryn+iMMrK2sFKhR4duk/86mEz0UO5gSx4pQIw9G5276P5heY/7Rg==} - engines: {node: '>=10'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - - '@uniswap/universal-router-sdk@4.34.0': - resolution: {integrity: sha512-TZEVBWilQHtSO75Ozkq1D0JcacclF3q1l8LOmQ8C1FzuQQvi8uWqJXE6qH4SghxvsdQIWV3Jkz9Jvv8OOVduxg==} - engines: {node: '>=14'} - - '@uniswap/universal-router@2.1.0': - resolution: {integrity: sha512-rt18RUsZd9xDfyVfIONJo+TEQ8w+olOYxu9+A1g4Thil1R7IMa+8mnyVQjdLPK2REhejScDwjYbOGpeaAce0hg==} - engines: {node: '>=14'} - - '@uniswap/v2-core@1.0.1': - resolution: {integrity: sha512-MtybtkUPSyysqLY2U210NBDeCHX+ltHt3oADGdjqoThZaFRDKwM6k1Nb3F0A3hk5hwuQvytFWhrWHOEq6nVJ8Q==} - engines: {node: '>=10'} - - '@uniswap/v2-sdk@4.19.1': - resolution: {integrity: sha512-c89H6S8YNI7tGSsk5wWXLxnXUe/jT+BewKSXtJ/w0WXEV8r4dl18R80YfOtwajnYbt8jYrUVVdPgPLaX0CdDEg==} - engines: {node: '>=10'} - - '@uniswap/v3-core@1.0.0': - resolution: {integrity: sha512-kSC4djMGKMHj7sLMYVnn61k9nu+lHjMIxgg9CDQT+s2QYLoA56GbSK9Oxr+qJXzzygbkrmuY6cwgP6cW2JXPFA==} - engines: {node: '>=10'} - - '@uniswap/v3-periphery@1.4.4': - resolution: {integrity: sha512-S4+m+wh8HbWSO3DKk4LwUCPZJTpCugIsHrWR86m/OrUyvSqGDTXKFfc2sMuGXCZrD1ZqO3rhQsKgdWg3Hbb2Kw==} - engines: {node: '>=10'} - - '@uniswap/v3-sdk@3.29.1': - resolution: {integrity: sha512-EYCzrfCCxc9DqUguw5rX+9774jvpVIvvrnyraJenZ371JiX/VC09/6OToKReu3gJfgITaH1BynMATwGkmOb2SQ==} - engines: {node: '>=10'} - - '@uniswap/v3-staker@1.0.0': - resolution: {integrity: sha512-JV0Qc46Px5alvg6YWd+UIaGH9lDuYG/Js7ngxPit1SPaIP30AlVer1UYB7BRYeUVVxE+byUyIeN5jeQ7LLDjIw==} - engines: {node: '>=10'} - deprecated: Please upgrade to 1.0.1 - - '@uniswap/v4-sdk@1.29.1': - resolution: {integrity: sha512-JYAnNmTGhbl5G4Xl+fUsew/ic8VwKofJxZ2uQjMkGMkzkCMT70Sht7E2ysZD+znLWQ9cCB0AAQWZqTlryiEfhA==} - engines: {node: '>=14'} - - '@yudiel/react-qr-scanner@2.5.1': - resolution: {integrity: sha512-FWzHaCneu30mQpE80VNWx4IPtBjXFEiTzhwWunZy3afvvAy/x0aVIgYijJKEbROoaAeDfcJ/gIyUCqPBP7bMOw==} + '@typescript-eslint/eslint-plugin@7.18.0': + resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} + engines: {node: ^18.18.0 || >=20.0.0} peerDependencies: - react: ^17 || ^18 || ^19 - react-dom: ^17 || ^18 || ^19 + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true - abbrev@1.1.1: - resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + '@typescript-eslint/parser@7.18.0': + resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@7.18.0': + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/type-utils@7.18.0': + resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@7.18.0': + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/typescript-estree@7.18.0': + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@7.18.0': + resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/visitor-keys@7.18.0': + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} 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: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.5: resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} engines: {node: '>=0.4.0'} @@ -970,39 +456,11 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - adm-zip@0.4.16: - resolution: {integrity: sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==} - engines: {node: '>=0.3.0'} - aes-js@3.0.0: resolution: {integrity: sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==} - agent-base@6.0.2: - resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} - engines: {node: '>= 6.0.0'} - - agentkeepalive@4.6.0: - resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==} - engines: {node: '>= 8.0.0'} - - aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - - amqplib@1.0.3: - resolution: {integrity: sha512-nsrXa59tvX4HPjPPW8JJGuBb/Db0MOq3DOhGdSbIY7bTsQDMV2fmF9c3i74g/8Gt9+QWyrxfEPppOOoV9lK1uQ==} - engines: {node: '>=18'} - - ansi-align@3.0.1: - resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} - - ansi-colors@4.1.3: - resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} - engines: {node: '>=6'} - - ansi-escapes@4.3.2: - resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} - engines: {node: '>=8'} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} @@ -1016,14 +474,6 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} - aproba@2.1.0: - resolution: {integrity: sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==} - - are-we-there-yet@2.0.0: - resolution: {integrity: sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==} - engines: {node: '>=10'} - deprecated: This package is no longer supported. - arg@4.1.3: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} @@ -1033,65 +483,20 @@ packages: array-flatten@1.1.1: resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - barcode-detector@3.0.8: - resolution: {integrity: sha512-Z9jzzE8ngEDyN9EU7lWdGgV07mcnEQnrX8W9WecXDqD2v+5CcVjt9+a134a5zb+kICvpsrDx6NYA6ay4LGFs8A==} - - base-x@3.0.11: - resolution: {integrity: sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==} - - base-x@5.0.1: - resolution: {integrity: sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - base64-sol@1.0.1: - resolution: {integrity: sha512-ld3cCNMeXt4uJXmLZBHFGMvVpK9KsLVEhPpFRXnvSVAqABKbuNZg/+dsq3NuM+wxFLb/UrVkz7m1ciWmkMfTbg==} - - baseline-browser-mapping@2.10.0: - resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} - engines: {node: '>=6.0.0'} - hasBin: true - - bcrypt@5.1.1: - resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} - engines: {node: '>= 10.0.0'} - bech32@1.1.4: resolution: {integrity: sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==} - bech32@2.0.0: - resolution: {integrity: sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==} - - big.js@5.2.2: - resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} - - bignumber.js@9.1.2: - resolution: {integrity: sha512-2/mKyZH9K85bzOEfhXDBFZTGd1CTs+5IHpeFQo9luiBG7hghdC851Pj2WAhb6E3R6b9tZj/XKhbg4fum+Kepug==} - binary-extensions@2.3.0: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} - bip174@3.0.0: - resolution: {integrity: sha512-N3vz3rqikLEu0d6yQL8GTrSkpYb35NQKWMR7Hlza0lOj6ZOlvQ3Xr7N9Y+JPebaCVoEUHdBeBSuLxcHr71r+Lw==} - engines: {node: '>=18.0.0'} - - bip32@5.0.1: - resolution: {integrity: sha512-PWlHIAgYCfVhwqNpZyeakHXuLAGyN6rEQZnhxHxKI3BoFJRVWLl26455fhRlHsmbYcV986HqtPnt33Edu5sTCw==} - engines: {node: '>=18.0.0'} - - bitcoinjs-lib@7.0.1: - resolution: {integrity: sha512-vwEmpL5Tpj0I0RBdNkcDMXePoaYSTeKY6mL6/l5esbnTs+jGdPDuLp4NY1hSh6Zk5wSgePygZ4Wx5JJao30Pww==} - engines: {node: '>=18.0.0'} - bn.js@4.12.3: resolution: {integrity: sha512-fGTi3gxV/23FTYdAoUtLYp6qySe2KE3teyZitipKNRuVYcBkoP/bB3guXN/XVKUe9mxCHXnc9C4ocyz8OmgN0g==} @@ -1102,18 +507,11 @@ packages: resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - borsh@0.7.0: - resolution: {integrity: sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==} - - boxen@5.1.2: - resolution: {integrity: sha512-9gYgQKXx+1nP8mP7CzFyaUARhg7D3n1dF/FnErWmu9l6JvGpNUN278h0aSb+QjoiKSWG+iZ3uHrcqk0qrY9RQQ==} - engines: {node: '>=10'} - brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} @@ -1122,30 +520,9 @@ packages: brorand@1.1.0: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} - browser-stdout@1.3.1: - resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} - - bs58@4.0.1: - resolution: {integrity: sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==} - - bs58@6.0.0: - resolution: {integrity: sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==} - - bs58check@4.0.0: - resolution: {integrity: sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==} - - buffer-equal-constant-time@1.0.1: - resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} - buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - buffer-more-ints@1.0.0: - resolution: {integrity: sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==} - - buffer@6.0.3: - resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} - bufferutil@4.1.0: resolution: {integrity: sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==} engines: {node: '>=6.14.2'} @@ -1158,62 +535,22 @@ packages: resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} engines: {node: '>= 0.4'} - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - call-bound@1.0.4: resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} engines: {node: '>= 0.4'} - camelcase@6.3.0: - resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} - engines: {node: '>=10'} - - caniuse-lite@1.0.30001777: - resolution: {integrity: sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==} + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} - chokidar@4.0.3: - resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} - engines: {node: '>= 14.16.0'} - - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - - ci-info@2.0.0: - resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} - - cipher-base@1.0.7: - resolution: {integrity: sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==} - engines: {node: '>= 0.10'} - - clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - - cli-boxes@2.2.1: - resolution: {integrity: sha512-y4coMcylgSCdVinjiDBuR8PCC2bLjyGTwEmPb9NHR/QaNU6EUOXcTY/s6VjGMD6ENSEaeQYHCY0GNGS5jfMwPw==} - engines: {node: '>=6'} - - client-only@0.0.1: - resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} - - cliui@7.0.4: - resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -1221,37 +558,16 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - colorette@2.0.19: resolution: {integrity: sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==} - command-exists@1.2.9: - resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} - commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} - commander@14.0.3: - resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} - engines: {node: '>=20'} - - commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - - commander@8.3.0: - resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} - engines: {node: '>= 12'} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1270,32 +586,20 @@ packages: cookie-signature@1.0.7: resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} - cookie@0.4.2: - resolution: {integrity: sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==} - engines: {node: '>= 0.6'} - cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - cors@2.8.6: resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==} engines: {node: '>= 0.10'} - create-hash@1.2.0: - resolution: {integrity: sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==} - - create-hmac@1.1.7: - resolution: {integrity: sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==} - create-require@1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} @@ -1314,32 +618,8 @@ packages: supports-color: optional: true - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decamelize@4.0.0: - resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} - engines: {node: '>=10'} - - decimal.js-light@2.5.1: - resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - delay@5.0.0: - resolution: {integrity: sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==} - engines: {node: '>=10'} - - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} depd@2.0.0: resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} @@ -1349,21 +629,17 @@ packages: resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - diff@4.0.4: resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==} engines: {node: '>=0.3.1'} - diff@5.2.2: - resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} - engines: {node: '>=0.3.1'} + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} - dotenv@14.3.2: - resolution: {integrity: sha512-vwEppIphpFdvaMCaHfCEv9IgwcxMljMw2TnAQBB4VWPvzXQLTb82jwmdOKzlEVUL3gNFT4l4TPKO+Bn+sqcrVQ==} - engines: {node: '>=12'} + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} dotenv@16.6.1: resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} @@ -1376,16 +652,6 @@ packages: dynamic-dedupe@0.3.0: resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==} - ecdsa-sig-formatter@1.0.11: - resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} - - ecpair@3.0.1: - resolution: {integrity: sha512-uz8wMFvtdr58TLrXnAesBsoMEyY8UudLOfApcyg40XfZjP+gt1xO4cuZSIkZ8hTMTQ8+ETgt7xSIV4eM7M6VNw==} - engines: {node: '>=20.0.0'} - - ed25519-hd-key@1.3.0: - resolution: {integrity: sha512-IWwAyiiuJQhgu3L8NaHb68eJxTu2pgCwxIBdgpLJdKpYZM46+AXePSVTr7fkNKaUOfOL4IrjEUaQvyVRIDP7fg==} - ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} @@ -1395,21 +661,10 @@ packages: elliptic@6.6.1: resolution: {integrity: sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==} - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - encodeurl@2.0.0: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} - enquirer@2.4.1: - resolution: {integrity: sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==} - engines: {node: '>=8.6'} - - env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - es-define-property@1.0.1: resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} engines: {node: '>= 0.4'} @@ -1422,12 +677,6 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} - es6-promise@4.2.8: - resolution: {integrity: sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==} - - es6-promisify@5.0.0: - resolution: {integrity: sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==} - escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -1439,51 +688,74 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + esm@3.2.25: resolution: {integrity: sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==} engines: {node: '>=6'} + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + 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'} - ethereum-cryptography@1.2.0: - resolution: {integrity: sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==} - - ethereum-cryptography@2.2.1: - resolution: {integrity: sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==} - ethers@5.7.2: resolution: {integrity: sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg==} - eventemitter3@5.0.4: - resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} - - express-rate-limit@7.5.1: - resolution: {integrity: sha512-7iN8iPMDzOMHPUYllBEsQdWVB6fPDMPqwjBaFrgr4Jgr/+okjvzAy+UHlYYL/Vs0OsOrMkwS6PJDkFlJwoxUnw==} - engines: {node: '>= 16'} - peerDependencies: - express: '>= 4.11' - express@4.22.1: resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} engines: {node: '>= 0.10.0'} - eyes@0.1.8: - resolution: {integrity: sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==} - engines: {node: '> 0.1.90'} + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - fast-stable-stringify@1.0.0: - resolution: {integrity: sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==} + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} fill-range@7.1.1: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} @@ -1497,42 +769,21 @@ packages: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} - flat@5.0.2: - resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} - hasBin: true + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} - follow-redirects@1.15.11: - resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} - fp-ts@1.19.3: - resolution: {integrity: sha512-H5KQDspykdHuztLTg+ajGN0Z2qUjcEf3Ybxc6hLt0k7/zPkn29XnKnxlBPyW2XIddWrGaJBzBl4VLYOtk39yZg==} - fresh@0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} - fs-extra@7.0.1: - resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} - engines: {node: '>=6 <7 || >=8'} - - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -1544,15 +795,6 @@ packages: function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - gauge@3.0.2: - resolution: {integrity: sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==} - engines: {node: '>=10'} - deprecated: This package is no longer supported. - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1572,61 +814,37 @@ packages: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - glob@8.1.0: - resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} - engines: {node: '>=12'} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - hardhat-watcher@2.5.0: - resolution: {integrity: sha512-Su2qcSMIo2YO2PrmJ0/tdkf+6pSt8zf9+4URR5edMVti6+ShI8T3xhPrwugdyTOFuyj8lKHrcTZNKUFYowYiyA==} - peerDependencies: - hardhat: ^2.0.0 - - hardhat@2.28.6: - resolution: {integrity: sha512-zQze7qe+8ltwHvhX5NQ8sN1N37WWZGw8L63y+2XcPxGwAjc/SMF829z3NS6o1krX0sryhAsVBK/xrwUqlsot4Q==} - hasBin: true - peerDependencies: - ts-node: '*' - typescript: '*' - peerDependenciesMeta: - ts-node: - optional: true - typescript: - optional: true + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - has-symbols@1.1.0: resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} engines: {node: '>= 0.4'} - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - - hash-base@3.1.2: - resolution: {integrity: sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==} - engines: {node: '>= 0.8'} - hash.js@1.1.7: resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==} @@ -1634,10 +852,6 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - he@1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - helmet@8.1.0: resolution: {integrity: sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==} engines: {node: '>=18.0.0'} @@ -1649,26 +863,21 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} - https-proxy-agent@5.0.1: - resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} - engines: {node: '>= 6'} - - 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==} + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} - immutable@4.3.8: - resolution: {integrity: sha512-d/Ld9aLbKpNwyl0KiM2CT1WYvkitQ1TSvmRtkcV8FKStiDoA7Slzgjmb/1G2yhKM1p0XeNOieaTbFZmU1d3Xuw==} + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} @@ -1681,9 +890,6 @@ packages: resolution: {integrity: sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==} engines: {node: '>= 0.10'} - io-ts@1.10.4: - resolution: {integrity: sha512-b23PteSnYXSONJ6JQXRAlvJhuw8KOtkqa87W4wDtvMrud/DTJd5X+NpOOI+O/zZwVq6v0VLAaJ+1EDViKEuN9g==} - ipaddr.js@1.9.1: resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} engines: {node: '>= 0.10'} @@ -1692,10 +898,6 @@ packages: resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} engines: {node: '>=8'} - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - is-core-module@2.16.1: resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} engines: {node: '>= 0.4'} @@ -1704,10 +906,6 @@ packages: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-glob@4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -1716,33 +914,12 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - is-plain-obj@2.1.0: - resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isomorphic-ws@4.0.1: - resolution: {integrity: sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==} - peerDependencies: - ws: '*' - - jayson@4.3.0: - resolution: {integrity: sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==} - engines: {node: '>=8'} - hasBin: true + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} jose@6.2.2: resolution: {integrity: sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==} @@ -1754,32 +931,17 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true - jsbi@3.2.5: - resolution: {integrity: sha512-aBE4n43IPvjaddScbvWRA2YlTzKEynHzu7MqOyTipdHucf/VxS63ViCjxYRg86M8Rxwbt/GfzHl1kKERkt45fQ==} + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - json-stream-stringify@3.1.6: - resolution: {integrity: sha512-x7fpwxOkbhFCaJDJ8vb1fBY3DdSa4AlITaz+HHILQJzdPMnHEFjxPwVUi1ALIbcIxDE0PNe/0i7frnY8QnBQog==} - engines: {node: '>=7.10.1'} + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - json-stringify-safe@5.0.1: - resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - jsonfile@4.0.0: - resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} - - jsonwebtoken@9.0.3: - resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} - engines: {node: '>=12', npm: '>=6'} - - jwa@2.0.1: - resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} - - jws@4.0.1: - resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} - - keccak@3.0.4: - resolution: {integrity: sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==} - engines: {node: '>=10.0.0'} + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} knex@3.1.0: resolution: {integrity: sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==} @@ -1812,45 +974,20 @@ packages: layerr@3.0.0: resolution: {integrity: sha512-tv754Ki2dXpPVApOrjTyRo4/QegVb9eVFq4mjqp4+NM5NaX7syQvN5BBNfV/ZpAHCEHV24XdUVrBAoka4jt3pA==} + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.includes@4.3.0: - resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} - - lodash.isboolean@3.0.3: - resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} - - lodash.isinteger@4.0.4: - resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} - - lodash.isnumber@3.0.3: - resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} - - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - - lodash.isstring@4.0.1: - resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} - - lodash.once@4.1.1: - resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - lru_map@0.3.3: - resolution: {integrity: sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ==} - - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - make-error@1.3.6: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} @@ -1858,29 +995,24 @@ packages: resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} engines: {node: '>= 0.4'} - md5.js@1.3.5: - resolution: {integrity: sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==} - media-typer@0.3.0: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - memorystream@0.3.1: - resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} - engines: {node: '>= 0.10.0'} - 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-eth-signer@0.14.0: - resolution: {integrity: sha512-5PLLzHiVYPWClEvZIXXFu5yutzpadb73rnQCpUqIHu3No3coFuWQNfE5tkBQJ7djuLYl6aRLaS0MgWJYGoqiBw==} - - micro-packed@0.7.3: - resolution: {integrity: sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg==} + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} @@ -1904,38 +1036,18 @@ packages: minimatch@3.1.5: resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@5.1.9: - resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} - engines: {node: '>=10'} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true - mnemonist@0.38.5: - resolution: {integrity: sha512-bZTFT5rrPKtPJxj8KSV0WkPyNxl72vQepqqVUAW2ARUpUSF2qXMB6jZj7hW5/k7C1rtpzqbD/IIbJwLXUjCHeg==} - - mocha@10.8.2: - resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==} - engines: {node: '>= 14.0.0'} - hasBin: true - ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -1945,68 +1057,21 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true + 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@16.1.6: - resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==} - engines: {node: '>=20.9.0'} - hasBin: true - 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-addon-api@2.0.2: - resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} - - node-addon-api@5.1.0: - resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} - - node-fetch@2.7.0: - resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} - engines: {node: 4.x || >=6.0.0} - peerDependencies: - encoding: ^0.1.0 - peerDependenciesMeta: - encoding: - optional: true - node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - nopt@5.0.0: - resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} - engines: {node: '>=6'} - hasBin: true - normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - npmlog@5.0.1: - resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==} - deprecated: This package is no longer supported. - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2015,9 +1080,6 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} - obliterator@2.0.5: - resolution: {integrity: sha512-42CPE9AhahZRsMNslczq0ctAEtqk8Eka26QofnqC346BZdHDySk3LWka23LI7ULIw11NmltpiLagIq8gBozxTw==} - on-finished@2.4.1: resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} engines: {node: '>= 0.8'} @@ -2025,9 +1087,9 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - os-tmpdir@1.0.2: - resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==} - engines: {node: '>=0.10.0'} + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} @@ -2037,9 +1099,9 @@ packages: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} - p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -2053,12 +1115,20 @@ packages: resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} engines: {node: '>=0.10.0'} + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} path-to-regexp@0.1.12: resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + pg-cloudflare@1.3.0: resolution: {integrity: sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==} @@ -2096,25 +1166,10 @@ packages: pgpass@1.0.5: resolution: {integrity: sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==} - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - postcss@8.4.31: - resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} - engines: {node: ^10 || ^12 || >=14} - postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -2131,24 +1186,24 @@ packages: resolution: {integrity: sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==} engines: {node: '>=0.10.0'} - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} - qrcode.react@4.2.0: - resolution: {integrity: sha512-QpgqWi8rD9DsS9EP3z7BT+5lY5SFhsqGjpgW5DY/i3mK4M9DTBNz3ErMi8BWYEfI3L0d8GIbGmcdFAS1uIRGjA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} qs@6.14.2: resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} engines: {node: '>=0.6'} - randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} @@ -2158,50 +1213,31 @@ packages: resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} engines: {node: '>= 0.8'} - react-dom@19.2.3: - resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} - peerDependencies: - react: ^19.2.3 - - react@19.2.3: - resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} - engines: {node: '>=0.10.0'} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readdirp@3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} - readdirp@4.1.2: - resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} - engines: {node: '>= 14.18.0'} - rechoir@0.8.0: resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} engines: {node: '>= 10.13.0'} - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} - resolve@1.17.0: - resolution: {integrity: sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==} - resolve@1.22.11: resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} engines: {node: '>= 0.4'} hasBin: true + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -2212,15 +1248,8 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - ripemd160@2.0.3: - resolution: {integrity: sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==} - engines: {node: '>= 0.8'} - - rpc-websockets@9.3.5: - resolution: {integrity: sha512-4mAmr+AEhPYJ9TmDtxF3r3ZcbWy7W8kvZ4PoZYw/Xgp2J7WixjwTgiQZsoTDvch5nimmg3Ay6/0Kuh9oIvVs9A==} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -2228,23 +1257,9 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - scrypt-js@3.0.1: resolution: {integrity: sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==} - sdp@3.2.1: - resolution: {integrity: sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==} - - semver@5.7.2: - resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==} - hasBin: true - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -2254,31 +1269,20 @@ packages: resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} engines: {node: '>= 0.8.0'} - serialize-javascript@6.0.2: - resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - serve-static@1.16.3: resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} engines: {node: '>= 0.8.0'} - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - sha.js@2.4.12: - resolution: {integrity: sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==} - engines: {node: '>= 0.10'} - hasBin: true + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} - sharp@0.34.5: - resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} - engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} @@ -2296,17 +1300,9 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - solc@0.8.26: - resolution: {integrity: sha512-yiPQNVf5rBFHwN6SIf3TUUvVAFKcQqmSUFeq+fb6pNRCo0ZCgpYOZDi3BVoezCPIAcKrVYd/qXlBLUP9wVrZ9g==} - engines: {node: '>=10.0.0'} - hasBin: true - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} source-map-support@0.5.21: resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} @@ -2319,27 +1315,10 @@ packages: resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==} engines: {node: '>= 10.x'} - stacktrace-parser@0.1.11: - resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} - engines: {node: '>=6'} - statuses@2.0.2: resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} engines: {node: '>= 0.8'} - stream-chain@2.2.5: - resolution: {integrity: sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==} - - stream-json@1.9.1: - resolution: {integrity: sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -2356,95 +1335,52 @@ packages: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} - styled-jsx@5.1.6: - resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} - engines: {node: '>= 12.0.0'} - peerDependencies: - '@babel/core': '*' - babel-plugin-macros: '*' - 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 - - superstruct@2.0.2: - resolution: {integrity: sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==} - engines: {node: '>=14.0.0'} - supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} - supports-color@8.1.1: - resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} - engines: {node: '>=10'} - supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tagged-tag@1.0.0: - resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} - engines: {node: '>=20'} + swagger-ui-dist@5.32.1: + resolution: {integrity: sha512-6HQoo7+j8PA2QqP5kgAb9dl1uxUjvR0SAoL/WUp1sTEvm0F6D5npgU2OGCLwl++bIInqGlEUQ2mpuZRZYtyCzQ==} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + swagger-ui-express@5.0.1: + resolution: {integrity: sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==} + engines: {node: '>= v0.10.32'} + peerDependencies: + express: '>=4.0.0 || >=5.0.0-beta' tarn@3.0.2: resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==} engines: {node: '>=8.0.0'} - text-encoding-utf-8@1.0.2: - resolution: {integrity: sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==} + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} tildify@2.0.0: resolution: {integrity: sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==} engines: {node: '>=8'} - tiny-invariant@1.3.3: - resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} - - tiny-secp256k1@2.2.4: - resolution: {integrity: sha512-FoDTcToPqZE454Q04hH9o2EhxWsm7pOSpicyHkgTwKhdKWdsTUuqfP5MLq3g+VjAtl2vSx6JpXGdwA2qpYkI0Q==} - engines: {node: '>=14.0.0'} - - tiny-warning@1.0.3: - resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} - - tinyglobby@0.2.15: - resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} - engines: {node: '>=12.0.0'} - - tmp@0.0.33: - resolution: {integrity: sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==} - engines: {node: '>=0.6.0'} - - to-buffer@1.2.2: - resolution: {integrity: sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==} - engines: {node: '>= 0.4'} - to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} - toformat@2.0.0: - resolution: {integrity: sha512-03SWBVop6nU8bpyZCx7SodpYznbZF5R4ljwNLBcTQzKOD9xuihRo/psX58llS1BMFhhAI08H3luot5GoXJz2pQ==} - toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - tr46@0.0.3: - resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} - tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + ts-node-dev@2.0.0: resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==} engines: {node: '>=0.8.0'} @@ -2473,15 +1409,6 @@ packages: tsconfig@7.0.0: resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} - tslib@1.14.1: - resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - tsort@0.0.1: - resolution: {integrity: sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==} - turbo-darwin-64@2.8.15: resolution: {integrity: sha512-EElCh+Ltxex9lXYrouV3hHjKP3HFP31G91KMghpNHR/V99CkFudRcHcnWaorPbzAZizH1m8o2JkLL8rptgb8WQ==} cpu: [x64] @@ -2516,50 +1443,23 @@ packages: resolution: {integrity: sha512-ERZf7pKOR155NKs/PZt1+83NrSEJfUL7+p9/TGZg/8xzDVMntXEFQlX4CsNJQTyu4h3j+dZYiQWOOlv5pssuHQ==} hasBin: true - tweetnacl@1.0.3: - resolution: {integrity: sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==} + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} type-fest@0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} - type-fest@0.21.3: - resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} - engines: {node: '>=10'} - - type-fest@0.7.1: - resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} - engines: {node: '>=8'} - - type-fest@5.4.4: - resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==} - engines: {node: '>=20'} - 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'} - typescript@5.9.3: resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} engines: {node: '>=14.17'} hasBin: true - uint8array-tools@0.0.7: - resolution: {integrity: sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ==} - engines: {node: '>=14.0.0'} - - uint8array-tools@0.0.8: - resolution: {integrity: sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==} - engines: {node: '>=14.0.0'} - - uint8array-tools@0.0.9: - resolution: {integrity: sha512-9vqDWmoSXOoi+K14zNaf6LBV51Q8MayF0/IiQs3GlygIKUYtog603e6virExkjjFosfJUBI4LhbQK1iq8IG11A==} - engines: {node: '>=14.0.0'} - ulidx@2.4.1: resolution: {integrity: sha512-xY7c8LPyzvhvew0Fn+Ek3wBC9STZAuDI/Y5andCKi9AX6/jvfaX45PhsDX8oxgPL0YFp0Jhr8qWMbS/p9375Xg==} engines: {node: '>=16'} @@ -2567,85 +1467,32 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} - undici@5.29.0: - resolution: {integrity: sha512-raqeBD6NQK4SkWhQzeYKd1KmIG6dllBOTt55Rmkt4HtI9mwdWtJljnrXjAFUBLTSN67HWrOIZ3EPF4kjUw80Bg==} - engines: {node: '>=14.0'} - - universalify@0.1.2: - resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} - engines: {node: '>= 4.0.0'} - unpipe@1.0.0: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} - utf-8-validate@6.0.6: - resolution: {integrity: sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==} - engines: {node: '>=6.14.2'} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} - uuid@11.1.0: - resolution: {integrity: sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==} - hasBin: true - - uuid@8.3.2: - resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} - hasBin: true - v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} - valibot@1.2.0: - resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} - peerDependencies: - typescript: '>=5' - peerDependenciesMeta: - typescript: - optional: true - - varuint-bitcoin@2.0.0: - resolution: {integrity: sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==} - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} - webidl-conversions@3.0.1: - resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true - webrtc-adapter@9.0.3: - resolution: {integrity: sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==} - engines: {node: '>=6.0.0', npm: '>=3.10.0'} - - whatwg-url@5.0.0: - resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} - - which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} - engines: {node: '>= 0.4'} - - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - - widest-line@3.1.0: - resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} - engines: {node: '>=8'} - - wif@5.0.0: - resolution: {integrity: sha512-iFzrC/9ne740qFbNjTZ2FciSRJlHIXoxqk/Y5EnE08QOXu1WjJyCCswwDTYbohAOEnlCtLaAAQBhyaLRFh2hMA==} - - workerpool@6.5.1: - resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2662,53 +1509,10 @@ packages: utf-8-validate: optional: true - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - ws@8.19.0: - resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} - 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 - xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yargs-parser@20.2.9: - resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} - engines: {node: '>=10'} - - yargs-unparser@2.0.0: - resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} - engines: {node: '>=10'} - - yargs@16.2.0: - resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} - engines: {node: '>=10'} - yn@3.1.1: resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} engines: {node: '>=6'} @@ -2720,48 +1524,34 @@ packages: zod@3.25.76: resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} - zustand@5.0.11: - resolution: {integrity: sha512-fdZY+dk7zn/vbWNCYmzZULHRrss0jx5pPFiOuMZ/5HJN6Yv3u+1Wswy/4MpZEkEGhtNH+pwxZB8OKgUBPzYAGg==} - engines: {node: '>=12.20.0'} - peerDependencies: - '@types/react': '>=18.0.0' - immer: '>=9.0.6' - react: '>=18.0.0' - use-sync-external-store: '>=1.2.0' - peerDependenciesMeta: - '@types/react': - optional: true - immer: - optional: true - react: - optional: true - use-sync-external-store: - optional: true - - zxing-wasm@2.2.4: - resolution: {integrity: sha512-1gq5zs4wuNTs5umWLypzNNeuJoluFvwmvjiiT3L9z/TMlVveeJRWy7h90xyUqCe+Qq0zL0w7o5zkdDMWDr9aZA==} - peerDependencies: - '@types/emscripten': '>=1.39.6' - snapshots: - '@babel/runtime@7.28.6': {} - '@cspotcode/source-map-support@0.8.1': dependencies: '@jridgewell/trace-mapping': 0.3.9 - '@emnapi/runtime@1.8.1': + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': dependencies: - tslib: 2.8.1 - optional: true + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 - '@ethereumjs/rlp@5.0.2': {} + '@eslint-community/regexpp@4.12.2': {} - '@ethereumjs/util@9.1.0': + '@eslint/eslintrc@2.1.4': dependencies: - '@ethereumjs/rlp': 5.0.2 - ethereum-cryptography: 2.2.1 + ajv: 6.14.0 + debug: 4.3.4 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} '@ethersproject/abi@5.7.0': dependencies: @@ -3024,7 +1814,7 @@ snapshots: dependencies: '@ethersproject/logger': 5.8.0 - '@ethersproject/providers@5.7.2(bufferutil@4.1.0)(utf-8-validate@6.0.6)': + '@ethersproject/providers@5.7.2(bufferutil@4.1.0)': dependencies: '@ethersproject/abstract-provider': 5.8.0 '@ethersproject/abstract-signer': 5.8.0 @@ -3045,7 +1835,7 @@ snapshots: '@ethersproject/transactions': 5.8.0 '@ethersproject/web': 5.8.0 bech32: 1.1.4 - ws: 7.4.6(bufferutil@4.1.0)(utf-8-validate@6.0.6) + ws: 7.4.6(bufferutil@4.1.0) transitivePeerDependencies: - bufferutil - utf-8-validate @@ -3109,15 +1899,6 @@ snapshots: '@ethersproject/sha2': 5.8.0 '@ethersproject/strings': 5.8.0 - '@ethersproject/solidity@5.8.0': - dependencies: - '@ethersproject/bignumber': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.8.0 - '@ethersproject/logger': 5.8.0 - '@ethersproject/sha2': 5.8.0 - '@ethersproject/strings': 5.8.0 - '@ethersproject/strings@5.7.0': dependencies: '@ethersproject/bytes': 5.8.0 @@ -3210,104 +1991,17 @@ snapshots: '@ethersproject/properties': 5.8.0 '@ethersproject/strings': 5.8.0 - '@fastify/busboy@2.1.1': {} - - '@img/colour@1.1.0': - optional: true - - '@img/sharp-darwin-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-arm64': 1.2.4 - optional: true - - '@img/sharp-darwin-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-darwin-x64': 1.2.4 - optional: true - - '@img/sharp-libvips-darwin-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-darwin-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-arm@1.2.4': - optional: true - - '@img/sharp-libvips-linux-ppc64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-riscv64@1.2.4': - optional: true - - '@img/sharp-libvips-linux-s390x@1.2.4': - optional: true - - '@img/sharp-libvips-linux-x64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-arm64@1.2.4': - optional: true - - '@img/sharp-libvips-linuxmusl-x64@1.2.4': - optional: true - - '@img/sharp-linux-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm64': 1.2.4 - optional: true - - '@img/sharp-linux-arm@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-arm': 1.2.4 - optional: true - - '@img/sharp-linux-ppc64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-ppc64': 1.2.4 - optional: true - - '@img/sharp-linux-riscv64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-riscv64': 1.2.4 - optional: true - - '@img/sharp-linux-s390x@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-s390x': 1.2.4 - optional: true - - '@img/sharp-linux-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linux-x64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-arm64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 - optional: true - - '@img/sharp-linuxmusl-x64@0.34.5': - optionalDependencies: - '@img/sharp-libvips-linuxmusl-x64': 1.2.4 - optional: true - - '@img/sharp-wasm32@0.34.5': + '@humanwhocodes/config-array@0.13.0': dependencies: - '@emnapi/runtime': 1.8.1 - optional: true + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.3.4 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color - '@img/sharp-win32-arm64@0.34.5': - optional: true + '@humanwhocodes/module-importer@1.0.1': {} - '@img/sharp-win32-ia32@0.34.5': - optional: true - - '@img/sharp-win32-x64@0.34.5': - optional: true + '@humanwhocodes/object-schema@2.0.3': {} '@jridgewell/resolve-uri@3.1.2': {} @@ -3318,273 +2012,19 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@mapbox/node-pre-gyp@1.0.11': + '@nodelib/fs.scandir@2.1.5': dependencies: - detect-libc: 2.1.2 - https-proxy-agent: 5.0.1 - make-dir: 3.1.0 - node-fetch: 2.7.0 - nopt: 5.0.0 - npmlog: 5.0.1 - rimraf: 3.0.2 - semver: 7.7.4 - tar: 6.2.1 - transitivePeerDependencies: - - encoding - - supports-color + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 - '@next/env@16.1.6': {} + '@nodelib/fs.stat@2.0.5': {} - '@next/swc-darwin-arm64@16.1.6': - optional: true - - '@next/swc-darwin-x64@16.1.6': - optional: true - - '@next/swc-linux-arm64-gnu@16.1.6': - optional: true - - '@next/swc-linux-arm64-musl@16.1.6': - optional: true - - '@next/swc-linux-x64-gnu@16.1.6': - optional: true - - '@next/swc-linux-x64-musl@16.1.6': - optional: true - - '@next/swc-win32-arm64-msvc@16.1.6': - optional: true - - '@next/swc-win32-x64-msvc@16.1.6': - optional: true - - '@noble/curves@1.4.2': + '@nodelib/fs.walk@1.2.8': dependencies: - '@noble/hashes': 1.4.0 + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 - '@noble/curves@1.8.2': - dependencies: - '@noble/hashes': 1.7.2 - - '@noble/curves@1.9.7': - dependencies: - '@noble/hashes': 1.8.0 - - '@noble/curves@2.0.1': - dependencies: - '@noble/hashes': 2.0.1 - - '@noble/hashes@1.2.0': {} - - '@noble/hashes@1.4.0': {} - - '@noble/hashes@1.7.2': {} - - '@noble/hashes@1.8.0': {} - - '@noble/hashes@2.0.1': {} - - '@noble/secp256k1@1.7.1': {} - - '@nomicfoundation/edr-darwin-arm64@0.12.0-next.23': {} - - '@nomicfoundation/edr-darwin-x64@0.12.0-next.23': {} - - '@nomicfoundation/edr-linux-arm64-gnu@0.12.0-next.23': {} - - '@nomicfoundation/edr-linux-arm64-musl@0.12.0-next.23': {} - - '@nomicfoundation/edr-linux-x64-gnu@0.12.0-next.23': {} - - '@nomicfoundation/edr-linux-x64-musl@0.12.0-next.23': {} - - '@nomicfoundation/edr-win32-x64-msvc@0.12.0-next.23': {} - - '@nomicfoundation/edr@0.12.0-next.23': - dependencies: - '@nomicfoundation/edr-darwin-arm64': 0.12.0-next.23 - '@nomicfoundation/edr-darwin-x64': 0.12.0-next.23 - '@nomicfoundation/edr-linux-arm64-gnu': 0.12.0-next.23 - '@nomicfoundation/edr-linux-arm64-musl': 0.12.0-next.23 - '@nomicfoundation/edr-linux-x64-gnu': 0.12.0-next.23 - '@nomicfoundation/edr-linux-x64-musl': 0.12.0-next.23 - '@nomicfoundation/edr-win32-x64-msvc': 0.12.0-next.23 - - '@nomicfoundation/solidity-analyzer-darwin-arm64@0.1.2': - optional: true - - '@nomicfoundation/solidity-analyzer-darwin-x64@0.1.2': - optional: true - - '@nomicfoundation/solidity-analyzer-linux-arm64-gnu@0.1.2': - optional: true - - '@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.2': - optional: true - - '@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.2': - optional: true - - '@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.2': - optional: true - - '@nomicfoundation/solidity-analyzer-win32-x64-msvc@0.1.2': - optional: true - - '@nomicfoundation/solidity-analyzer@0.1.2': - optionalDependencies: - '@nomicfoundation/solidity-analyzer-darwin-arm64': 0.1.2 - '@nomicfoundation/solidity-analyzer-darwin-x64': 0.1.2 - '@nomicfoundation/solidity-analyzer-linux-arm64-gnu': 0.1.2 - '@nomicfoundation/solidity-analyzer-linux-arm64-musl': 0.1.2 - '@nomicfoundation/solidity-analyzer-linux-x64-gnu': 0.1.2 - '@nomicfoundation/solidity-analyzer-linux-x64-musl': 0.1.2 - '@nomicfoundation/solidity-analyzer-win32-x64-msvc': 0.1.2 - - '@openzeppelin/contracts@3.4.1-solc-0.7-2': {} - - '@openzeppelin/contracts@3.4.2-solc-0.7': {} - - '@openzeppelin/contracts@4.7.0': {} - - '@openzeppelin/contracts@5.0.2': {} - - '@scure/base@1.1.9': {} - - '@scure/base@1.2.6': {} - - '@scure/base@2.0.0': {} - - '@scure/bip32@1.1.5': - dependencies: - '@noble/hashes': 1.2.0 - '@noble/secp256k1': 1.7.1 - '@scure/base': 1.1.9 - - '@scure/bip32@1.4.0': - dependencies: - '@noble/curves': 1.4.2 - '@noble/hashes': 1.4.0 - '@scure/base': 1.1.9 - - '@scure/bip32@2.0.1': - dependencies: - '@noble/curves': 2.0.1 - '@noble/hashes': 2.0.1 - '@scure/base': 2.0.0 - - '@scure/bip39@1.1.1': - dependencies: - '@noble/hashes': 1.2.0 - '@scure/base': 1.1.9 - - '@scure/bip39@1.3.0': - dependencies: - '@noble/hashes': 1.4.0 - '@scure/base': 1.1.9 - - '@scure/bip39@2.0.1': - dependencies: - '@noble/hashes': 2.0.1 - '@scure/base': 2.0.0 - - '@sentry/core@5.30.0': - dependencies: - '@sentry/hub': 5.30.0 - '@sentry/minimal': 5.30.0 - '@sentry/types': 5.30.0 - '@sentry/utils': 5.30.0 - tslib: 1.14.1 - - '@sentry/hub@5.30.0': - dependencies: - '@sentry/types': 5.30.0 - '@sentry/utils': 5.30.0 - tslib: 1.14.1 - - '@sentry/minimal@5.30.0': - dependencies: - '@sentry/hub': 5.30.0 - '@sentry/types': 5.30.0 - tslib: 1.14.1 - - '@sentry/node@5.30.0': - dependencies: - '@sentry/core': 5.30.0 - '@sentry/hub': 5.30.0 - '@sentry/tracing': 5.30.0 - '@sentry/types': 5.30.0 - '@sentry/utils': 5.30.0 - cookie: 0.4.2 - https-proxy-agent: 5.0.1 - lru_map: 0.3.3 - tslib: 1.14.1 - transitivePeerDependencies: - - supports-color - - '@sentry/tracing@5.30.0': - dependencies: - '@sentry/hub': 5.30.0 - '@sentry/minimal': 5.30.0 - '@sentry/types': 5.30.0 - '@sentry/utils': 5.30.0 - tslib: 1.14.1 - - '@sentry/types@5.30.0': {} - - '@sentry/utils@5.30.0': - dependencies: - '@sentry/types': 5.30.0 - tslib: 1.14.1 - - '@solana/buffer-layout@4.0.1': - dependencies: - buffer: 6.0.3 - - '@solana/codecs-core@2.3.0(typescript@5.9.3)': - dependencies: - '@solana/errors': 2.3.0(typescript@5.9.3) - typescript: 5.9.3 - - '@solana/codecs-numbers@2.3.0(typescript@5.9.3)': - dependencies: - '@solana/codecs-core': 2.3.0(typescript@5.9.3) - '@solana/errors': 2.3.0(typescript@5.9.3) - typescript: 5.9.3 - - '@solana/errors@2.3.0(typescript@5.9.3)': - dependencies: - chalk: 5.6.2 - commander: 14.0.3 - typescript: 5.9.3 - - '@solana/web3.js@1.98.4(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@6.0.6)': - dependencies: - '@babel/runtime': 7.28.6 - '@noble/curves': 1.9.7 - '@noble/hashes': 1.8.0 - '@solana/buffer-layout': 4.0.1 - '@solana/codecs-numbers': 2.3.0(typescript@5.9.3) - agentkeepalive: 4.6.0 - bn.js: 5.2.3 - borsh: 0.7.0 - bs58: 4.0.1 - buffer: 6.0.3 - fast-stable-stringify: 1.0.0 - jayson: 4.3.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) - node-fetch: 2.7.0 - rpc-websockets: 9.3.5 - superstruct: 2.0.2 - transitivePeerDependencies: - - bufferutil - - encoding - - typescript - - utf-8-validate - - '@swc/helpers@0.5.15': - dependencies: - tslib: 2.8.1 + '@scarf/scarf@1.4.0': {} '@tsconfig/node10@1.0.12': {} @@ -3594,14 +2034,6 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@types/amqplib@0.10.8': - dependencies: - '@types/node': 20.19.37 - - '@types/bcrypt@5.0.2': - dependencies: - '@types/node': 20.19.37 - '@types/body-parser@1.19.6': dependencies: '@types/connect': 3.4.38 @@ -3619,8 +2051,6 @@ snapshots: dependencies: '@types/node': 20.19.37 - '@types/emscripten@1.41.5': {} - '@types/express-serve-static-core@5.1.1': dependencies: '@types/node': 20.19.37 @@ -3636,15 +2066,6 @@ snapshots: '@types/http-errors@2.0.5': {} - '@types/jsonwebtoken@9.0.10': - dependencies: - '@types/ms': 2.1.0 - '@types/node': 20.19.37 - - '@types/ms@2.1.0': {} - - '@types/node@12.20.55': {} - '@types/node@20.19.37': dependencies: undici-types: 6.21.0 @@ -3653,14 +2074,6 @@ snapshots: '@types/range-parser@1.2.7': {} - '@types/react-dom@19.2.3(@types/react@19.2.14)': - dependencies: - '@types/react': 19.2.14 - - '@types/react@19.2.14': - dependencies: - csstype: 3.2.3 - '@types/send@1.2.1': dependencies: '@types/node': 20.19.37 @@ -3674,188 +2087,117 @@ snapshots: '@types/strip-json-comments@0.0.30': {} - '@types/uuid@10.0.0': {} - - '@types/ws@7.4.7': + '@types/swagger-ui-express@4.1.8': dependencies: - '@types/node': 20.19.37 + '@types/express': 5.0.6 + '@types/serve-static': 2.2.0 - '@types/ws@8.18.1': + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': dependencies: - '@types/node': 20.19.37 - - '@uniswap/lib@4.0.1-alpha': {} - - '@uniswap/permit2-sdk@1.4.0(bufferutil@4.1.0)(utf-8-validate@6.0.6)': - dependencies: - ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@6.0.6) - tiny-invariant: 1.3.3 + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 7.18.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 transitivePeerDependencies: - - bufferutil - - utf-8-validate + - supports-color - '@uniswap/router-sdk@2.7.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6))': + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: - '@ethersproject/abi': 5.8.0 - '@uniswap/sdk-core': 7.12.1 - '@uniswap/swap-router-contracts': 1.3.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) - '@uniswap/v2-sdk': 4.19.1 - '@uniswap/v3-sdk': 3.29.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) - '@uniswap/v4-sdk': 1.29.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.3.4 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.9.3 transitivePeerDependencies: - - hardhat + - supports-color - '@uniswap/sdk-core@7.12.1': + '@typescript-eslint/scope-manager@7.18.0': dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/bytes': 5.8.0 - '@ethersproject/keccak256': 5.7.0 - '@ethersproject/strings': 5.7.0 - big.js: 5.2.2 - decimal.js-light: 2.5.1 - jsbi: 3.2.5 - tiny-invariant: 1.3.3 - toformat: 2.0.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 - '@uniswap/swap-router-contracts@1.3.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6))': + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: - '@openzeppelin/contracts': 3.4.2-solc-0.7 - '@uniswap/v2-core': 1.0.1 - '@uniswap/v3-core': 1.0.0 - '@uniswap/v3-periphery': 1.4.4 - dotenv: 14.3.2 - hardhat-watcher: 2.5.0(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.3.4 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 transitivePeerDependencies: - - hardhat + - supports-color - '@uniswap/universal-router-sdk@4.34.0(bufferutil@4.1.0)(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6))(utf-8-validate@6.0.6)': + '@typescript-eslint/types@7.18.0': {} + + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.3)': dependencies: - '@openzeppelin/contracts': 4.7.0 - '@uniswap/permit2-sdk': 1.4.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) - '@uniswap/router-sdk': 2.7.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) - '@uniswap/sdk-core': 7.12.1 - '@uniswap/universal-router': 2.1.0 - '@uniswap/v2-core': 1.0.1 - '@uniswap/v2-sdk': 4.19.1 - '@uniswap/v3-core': 1.0.0 - '@uniswap/v3-sdk': 3.29.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) - '@uniswap/v4-sdk': 1.29.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) - bignumber.js: 9.1.2 - ethers: 5.7.2(bufferutil@4.1.0)(utf-8-validate@6.0.6) + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.9 + semver: 7.7.4 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 transitivePeerDependencies: - - bufferutil - - hardhat - - utf-8-validate + - supports-color - '@uniswap/universal-router@2.1.0': + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.9.3)': dependencies: - '@openzeppelin/contracts': 5.0.2 - '@uniswap/v2-core': 1.0.1 - '@uniswap/v3-core': 1.0.0 - - '@uniswap/v2-core@1.0.1': {} - - '@uniswap/v2-sdk@4.19.1': - dependencies: - '@ethersproject/address': 5.8.0 - '@ethersproject/solidity': 5.8.0 - '@uniswap/sdk-core': 7.12.1 - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - - '@uniswap/v3-core@1.0.0': {} - - '@uniswap/v3-periphery@1.4.4': - dependencies: - '@openzeppelin/contracts': 3.4.2-solc-0.7 - '@uniswap/lib': 4.0.1-alpha - '@uniswap/v2-core': 1.0.1 - '@uniswap/v3-core': 1.0.0 - base64-sol: 1.0.1 - - '@uniswap/v3-sdk@3.29.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6))': - dependencies: - '@ethersproject/abi': 5.8.0 - '@ethersproject/solidity': 5.8.0 - '@uniswap/sdk-core': 7.12.1 - '@uniswap/swap-router-contracts': 1.3.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) - '@uniswap/v3-periphery': 1.4.4 - '@uniswap/v3-staker': 1.0.0 - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.3) + eslint: 8.57.1 transitivePeerDependencies: - - hardhat + - supports-color + - typescript - '@uniswap/v3-staker@1.0.0': + '@typescript-eslint/visitor-keys@7.18.0': dependencies: - '@openzeppelin/contracts': 3.4.1-solc-0.7-2 - '@uniswap/v3-core': 1.0.0 - '@uniswap/v3-periphery': 1.4.4 + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 - '@uniswap/v4-sdk@1.29.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6))': - dependencies: - '@ethersproject/solidity': 5.8.0 - '@uniswap/sdk-core': 7.12.1 - '@uniswap/v3-sdk': 3.29.1(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)) - tiny-invariant: 1.3.3 - tiny-warning: 1.0.3 - transitivePeerDependencies: - - hardhat - - '@yudiel/react-qr-scanner@2.5.1(@types/emscripten@1.41.5)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)': - dependencies: - barcode-detector: 3.0.8(@types/emscripten@1.41.5) - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - webrtc-adapter: 9.0.3 - transitivePeerDependencies: - - '@types/emscripten' - - abbrev@1.1.1: {} + '@ungap/structured-clone@1.3.0': {} accepts@1.3.8: dependencies: mime-types: 2.1.35 negotiator: 0.6.3 + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + acorn-walk@8.3.5: dependencies: acorn: 8.16.0 acorn@8.16.0: {} - adm-zip@0.4.16: {} - aes-js@3.0.0: {} - agent-base@6.0.2: + ajv@6.14.0: dependencies: - debug: 4.4.3(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - - agentkeepalive@4.6.0: - dependencies: - humanize-ms: 1.2.1 - - aggregate-error@3.1.0: - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 - - amqplib@1.0.3: - dependencies: - buffer-more-ints: 1.0.0 - - ansi-align@3.0.1: - dependencies: - string-width: 4.2.3 - - ansi-colors@4.1.3: {} - - ansi-escapes@4.3.2: - dependencies: - type-fest: 0.21.3 + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 ansi-regex@5.0.1: {} @@ -3868,88 +2210,20 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.1 - aproba@2.1.0: {} - - are-we-there-yet@2.0.0: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - arg@4.1.3: {} argparse@2.0.1: {} array-flatten@1.1.1: {} - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 + array-union@2.1.0: {} balanced-match@1.0.2: {} - barcode-detector@3.0.8(@types/emscripten@1.41.5): - dependencies: - zxing-wasm: 2.2.4(@types/emscripten@1.41.5) - transitivePeerDependencies: - - '@types/emscripten' - - base-x@3.0.11: - dependencies: - safe-buffer: 5.2.1 - - base-x@5.0.1: {} - - base64-js@1.5.1: {} - - base64-sol@1.0.1: {} - - baseline-browser-mapping@2.10.0: {} - - bcrypt@5.1.1: - dependencies: - '@mapbox/node-pre-gyp': 1.0.11 - node-addon-api: 5.1.0 - transitivePeerDependencies: - - encoding - - supports-color - bech32@1.1.4: {} - bech32@2.0.0: {} - - big.js@5.2.2: {} - - bignumber.js@9.1.2: {} - binary-extensions@2.3.0: {} - bip174@3.0.0: - dependencies: - uint8array-tools: 0.0.9 - varuint-bitcoin: 2.0.0 - - bip32@5.0.1(typescript@5.9.3): - dependencies: - '@noble/hashes': 1.8.0 - '@scure/base': 1.2.6 - uint8array-tools: 0.0.8 - valibot: 1.2.0(typescript@5.9.3) - wif: 5.0.0 - transitivePeerDependencies: - - typescript - - bitcoinjs-lib@7.0.1(typescript@5.9.3): - dependencies: - '@noble/hashes': 1.8.0 - bech32: 2.0.0 - bip174: 3.0.0 - bs58check: 4.0.0 - uint8array-tools: 0.0.9 - valibot: 1.2.0(typescript@5.9.3) - varuint-bitcoin: 2.0.0 - transitivePeerDependencies: - - typescript - bn.js@4.12.3: {} bn.js@5.2.3: {} @@ -3971,29 +2245,12 @@ snapshots: transitivePeerDependencies: - supports-color - borsh@0.7.0: - dependencies: - bn.js: 5.2.3 - bs58: 4.0.1 - text-encoding-utf-8: 1.0.2 - - boxen@5.1.2: - dependencies: - ansi-align: 3.0.1 - camelcase: 6.3.0 - chalk: 4.1.2 - cli-boxes: 2.2.1 - string-width: 4.2.3 - type-fest: 0.20.2 - widest-line: 3.1.0 - wrap-ansi: 7.0.0 - brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.2: + brace-expansion@2.1.0: dependencies: balanced-match: 1.0.2 @@ -4003,32 +2260,8 @@ snapshots: brorand@1.1.0: {} - browser-stdout@1.3.1: {} - - bs58@4.0.1: - dependencies: - base-x: 3.0.11 - - bs58@6.0.0: - dependencies: - base-x: 5.0.1 - - bs58check@4.0.0: - dependencies: - '@noble/hashes': 1.8.0 - bs58: 6.0.0 - - buffer-equal-constant-time@1.0.1: {} - buffer-from@1.1.2: {} - buffer-more-ints@1.0.0: {} - - buffer@6.0.3: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - bufferutil@4.1.0: dependencies: node-gyp-build: 4.8.4 @@ -4041,29 +2274,18 @@ snapshots: es-errors: 1.3.0 function-bind: 1.1.2 - call-bind@1.0.8: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - call-bound@1.0.4: dependencies: call-bind-apply-helpers: 1.0.2 get-intrinsic: 1.3.0 - camelcase@6.3.0: {} - - caniuse-lite@1.0.30001777: {} + callsites@3.1.0: {} chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.6.2: {} - chokidar@3.6.0: dependencies: anymatch: 3.1.3 @@ -4076,56 +2298,18 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - chokidar@4.0.3: - dependencies: - readdirp: 4.1.2 - - chownr@2.0.0: {} - - ci-info@2.0.0: {} - - cipher-base@1.0.7: - dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - to-buffer: 1.2.2 - - clean-stack@2.2.0: {} - - cli-boxes@2.2.1: {} - - client-only@0.0.1: {} - - cliui@7.0.4: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} - color-support@1.1.3: {} - colorette@2.0.19: {} - command-exists@1.2.9: {} - commander@10.0.1: {} - commander@14.0.3: {} - - commander@2.20.3: {} - - commander@8.3.0: {} - concat-map@0.0.1: {} - console-control-strings@1.1.0: {} - content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -4141,37 +2325,20 @@ snapshots: cookie-signature@1.0.7: {} - cookie@0.4.2: {} - cookie@0.7.2: {} - core-util-is@1.0.3: {} - cors@2.8.6: dependencies: object-assign: 4.1.1 vary: 1.1.2 - create-hash@1.2.0: - dependencies: - cipher-base: 1.0.7 - inherits: 2.0.4 - md5.js: 1.3.5 - ripemd160: 2.0.3 - sha.js: 2.4.12 - - create-hmac@1.1.7: - dependencies: - cipher-base: 1.0.7 - create-hash: 1.2.0 - inherits: 2.0.4 - ripemd160: 2.0.3 - safe-buffer: 5.2.1 - sha.js: 2.4.12 - create-require@1.1.1: {} - csstype@3.2.3: {} + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 debug@2.6.9: dependencies: @@ -4181,37 +2348,21 @@ snapshots: dependencies: ms: 2.1.2 - debug@4.4.3(supports-color@8.1.1): - dependencies: - ms: 2.1.3 - optionalDependencies: - supports-color: 8.1.1 - - decamelize@4.0.0: {} - - decimal.js-light@2.5.1: {} - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - delay@5.0.0: {} - - delegates@1.0.0: {} + deep-is@0.1.4: {} depd@2.0.0: {} destroy@1.2.0: {} - detect-libc@2.1.2: {} - diff@4.0.4: {} - diff@5.2.2: {} + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 - dotenv@14.3.2: {} + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 dotenv@16.6.1: {} @@ -4225,23 +2376,6 @@ snapshots: dependencies: xtend: 4.0.2 - ecdsa-sig-formatter@1.0.11: - dependencies: - safe-buffer: 5.2.1 - - ecpair@3.0.1(typescript@5.9.3): - dependencies: - uint8array-tools: 0.0.8 - valibot: 1.2.0(typescript@5.9.3) - wif: 5.0.0 - transitivePeerDependencies: - - typescript - - ed25519-hd-key@1.3.0: - dependencies: - create-hmac: 1.1.7 - tweetnacl: 1.0.3 - ee-first@1.1.1: {} elliptic@6.5.4: @@ -4264,17 +2398,8 @@ snapshots: minimalistic-assert: 1.0.1 minimalistic-crypto-utils: 1.0.1 - emoji-regex@8.0.0: {} - encodeurl@2.0.0: {} - enquirer@2.4.1: - dependencies: - ansi-colors: 4.1.3 - strip-ansi: 6.0.1 - - env-paths@2.2.1: {} - es-define-property@1.0.1: {} es-errors@1.3.0: {} @@ -4283,37 +2408,85 @@ snapshots: dependencies: es-errors: 1.3.0 - es6-promise@4.2.8: {} - - es6-promisify@5.0.0: - dependencies: - es6-promise: 4.2.8 - escalade@3.2.0: {} escape-html@1.0.3: {} escape-string-regexp@4.0.0: {} + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.3.4 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.1 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + esm@3.2.25: {} + espree@9.6.1: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + etag@1.8.1: {} - ethereum-cryptography@1.2.0: - dependencies: - '@noble/hashes': 1.2.0 - '@noble/secp256k1': 1.7.1 - '@scure/bip32': 1.1.5 - '@scure/bip39': 1.1.1 - - ethereum-cryptography@2.2.1: - dependencies: - '@noble/curves': 1.4.2 - '@noble/hashes': 1.4.0 - '@scure/bip32': 1.4.0 - '@scure/bip39': 1.3.0 - - ethers@5.7.2(bufferutil@4.1.0)(utf-8-validate@6.0.6): + ethers@5.7.2(bufferutil@4.1.0): dependencies: '@ethersproject/abi': 5.7.0 '@ethersproject/abstract-provider': 5.7.0 @@ -4333,7 +2506,7 @@ snapshots: '@ethersproject/networks': 5.7.1 '@ethersproject/pbkdf2': 5.7.0 '@ethersproject/properties': 5.7.0 - '@ethersproject/providers': 5.7.2(bufferutil@4.1.0)(utf-8-validate@6.0.6) + '@ethersproject/providers': 5.7.2(bufferutil@4.1.0) '@ethersproject/random': 5.7.0 '@ethersproject/rlp': 5.7.0 '@ethersproject/sha2': 5.7.0 @@ -4349,12 +2522,6 @@ snapshots: - bufferutil - utf-8-validate - eventemitter3@5.0.4: {} - - express-rate-limit@7.5.1(express@4.22.1): - dependencies: - express: 4.22.1 - express@4.22.1: dependencies: accepts: 1.3.8 @@ -4391,13 +2558,27 @@ snapshots: transitivePeerDependencies: - supports-color - eyes@0.1.8: {} + fast-deep-equal@3.1.3: {} - fast-stable-stringify@1.0.0: {} + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 fill-range@7.1.1: dependencies: @@ -4420,32 +2601,18 @@ snapshots: locate-path: 6.0.0 path-exists: 4.0.0 - flat@5.0.2: {} - - follow-redirects@1.15.11(debug@4.4.3): - optionalDependencies: - debug: 4.4.3(supports-color@8.1.1) - - for-each@0.3.5: + flat-cache@3.2.0: dependencies: - is-callable: 1.2.7 + flatted: 3.4.2 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.4.2: {} forwarded@0.2.0: {} - fp-ts@1.19.3: {} - fresh@0.5.2: {} - fs-extra@7.0.1: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 4.0.0 - universalify: 0.1.2 - - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fs.realpath@1.0.0: {} fsevents@2.3.3: @@ -4453,20 +2620,6 @@ snapshots: function-bind@1.1.2: {} - gauge@3.0.2: - dependencies: - aproba: 2.1.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - object-assign: 4.1.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - - get-caller-file@2.0.5: {} - get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4493,6 +2646,10 @@ snapshots: dependencies: is-glob: 4.0.3 + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + glob@7.2.3: dependencies: fs.realpath: 1.0.0 @@ -4502,93 +2659,27 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 - glob@8.1.0: + globals@13.24.0: dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 5.1.9 - once: 1.4.0 + type-fest: 0.20.2 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 gopd@1.2.0: {} - graceful-fs@4.2.11: {} - - hardhat-watcher@2.5.0(hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6)): - dependencies: - chokidar: 3.6.0 - hardhat: 2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6) - - hardhat@2.28.6(bufferutil@4.1.0)(ts-node@10.9.2(@types/node@20.19.37)(typescript@5.9.3))(typescript@5.9.3)(utf-8-validate@6.0.6): - dependencies: - '@ethereumjs/util': 9.1.0 - '@ethersproject/abi': 5.8.0 - '@nomicfoundation/edr': 0.12.0-next.23 - '@nomicfoundation/solidity-analyzer': 0.1.2 - '@sentry/node': 5.30.0 - adm-zip: 0.4.16 - aggregate-error: 3.1.0 - ansi-escapes: 4.3.2 - boxen: 5.1.2 - chokidar: 4.0.3 - ci-info: 2.0.0 - debug: 4.4.3(supports-color@8.1.1) - enquirer: 2.4.1 - env-paths: 2.2.1 - ethereum-cryptography: 1.2.0 - find-up: 5.0.0 - fp-ts: 1.19.3 - fs-extra: 7.0.1 - immutable: 4.3.8 - io-ts: 1.10.4 - json-stream-stringify: 3.1.6 - keccak: 3.0.4 - lodash: 4.17.23 - micro-eth-signer: 0.14.0 - mnemonist: 0.38.5 - mocha: 10.8.2 - p-map: 4.0.0 - picocolors: 1.1.1 - raw-body: 2.5.3 - resolve: 1.17.0 - semver: 6.3.1 - solc: 0.8.26(debug@4.4.3) - source-map-support: 0.5.21 - stacktrace-parser: 0.1.11 - tinyglobby: 0.2.15 - tsort: 0.0.1 - undici: 5.29.0 - uuid: 8.3.2 - ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6) - optionalDependencies: - ts-node: 10.9.2(@types/node@20.19.37)(typescript@5.9.3) - typescript: 5.9.3 - transitivePeerDependencies: - - bufferutil - - supports-color - - utf-8-validate + graphemer@1.4.0: {} has-flag@4.0.0: {} - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - has-symbols@1.1.0: {} - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - has-unicode@2.0.1: {} - - hash-base@3.1.2: - dependencies: - inherits: 2.0.4 - readable-stream: 2.3.8 - safe-buffer: 5.2.1 - to-buffer: 1.2.2 - hash.js@1.1.7: dependencies: inherits: 2.0.4 @@ -4598,8 +2689,6 @@ snapshots: dependencies: function-bind: 1.1.2 - he@1.2.0: {} - helmet@8.1.0: {} hmac-drbg@1.0.1: @@ -4616,26 +2705,18 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - https-proxy-agent@5.0.1: - dependencies: - agent-base: 6.0.2 - debug: 4.4.3(supports-color@8.1.1) - transitivePeerDependencies: - - supports-color - - humanize-ms@1.2.1: - dependencies: - ms: 2.1.3 - iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 - ieee754@1.2.1: {} + ignore@5.3.2: {} - immutable@4.3.8: {} + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 - indent-string@4.0.0: {} + imurmurhash@0.1.4: {} inflight@1.0.6: dependencies: @@ -4646,65 +2727,27 @@ snapshots: interpret@2.2.0: {} - io-ts@1.10.4: - dependencies: - fp-ts: 1.19.3 - ipaddr.js@1.9.1: {} is-binary-path@2.1.0: dependencies: binary-extensions: 2.3.0 - is-callable@1.2.7: {} - is-core-module@2.16.1: dependencies: hasown: 2.0.2 is-extglob@2.1.1: {} - is-fullwidth-code-point@3.0.0: {} - is-glob@4.0.3: dependencies: is-extglob: 2.1.1 is-number@7.0.0: {} - is-plain-obj@2.1.0: {} + is-path-inside@3.0.3: {} - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.20 - - is-unicode-supported@0.1.0: {} - - isarray@1.0.0: {} - - isarray@2.0.5: {} - - isomorphic-ws@4.0.1(ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6)): - dependencies: - ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6) - - jayson@4.3.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): - dependencies: - '@types/connect': 3.4.38 - '@types/node': 12.20.55 - '@types/ws': 7.4.7 - commander: 2.20.3 - delay: 5.0.0 - es6-promisify: 5.0.0 - eyes: 0.1.8 - isomorphic-ws: 4.0.1(ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6)) - json-stringify-safe: 5.0.1 - stream-json: 1.9.1 - uuid: 8.3.2 - ws: 7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6) - transitivePeerDependencies: - - bufferutil - - utf-8-validate + isexe@2.0.0: {} jose@6.2.2: {} @@ -4714,45 +2757,15 @@ snapshots: dependencies: argparse: 2.0.1 - jsbi@3.2.5: {} + json-buffer@3.0.1: {} - json-stream-stringify@3.1.6: {} + json-schema-traverse@0.4.1: {} - json-stringify-safe@5.0.1: {} + json-stable-stringify-without-jsonify@1.0.1: {} - jsonfile@4.0.0: - optionalDependencies: - graceful-fs: 4.2.11 - - jsonwebtoken@9.0.3: + keyv@4.5.4: dependencies: - jws: 4.0.1 - lodash.includes: 4.3.0 - lodash.isboolean: 3.0.3 - lodash.isinteger: 4.0.4 - lodash.isnumber: 3.0.3 - lodash.isplainobject: 4.0.6 - lodash.isstring: 4.0.1 - lodash.once: 4.1.1 - ms: 2.1.3 - semver: 7.7.4 - - jwa@2.0.1: - dependencies: - buffer-equal-constant-time: 1.0.1 - ecdsa-sig-formatter: 1.0.11 - safe-buffer: 5.2.1 - - jws@4.0.1: - dependencies: - jwa: 2.0.1 - safe-buffer: 5.2.1 - - keccak@3.0.4: - dependencies: - node-addon-api: 2.0.2 - node-gyp-build: 4.8.4 - readable-stream: 3.6.2 + json-buffer: 3.0.1 knex@3.1.0(pg@8.20.0): dependencies: @@ -4777,64 +2790,35 @@ snapshots: layerr@3.0.0: {} + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 - lodash.includes@4.3.0: {} - - lodash.isboolean@3.0.3: {} - - lodash.isinteger@4.0.4: {} - - lodash.isnumber@3.0.3: {} - - lodash.isplainobject@4.0.6: {} - - lodash.isstring@4.0.1: {} - - lodash.once@4.1.1: {} + lodash.merge@4.6.2: {} lodash@4.17.23: {} - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - - lru_map@0.3.3: {} - - make-dir@3.1.0: - dependencies: - semver: 6.3.1 - make-error@1.3.6: {} math-intrinsics@1.1.0: {} - md5.js@1.3.5: - dependencies: - hash-base: 3.1.2 - inherits: 2.0.4 - safe-buffer: 5.2.1 - media-typer@0.3.0: {} - memorystream@0.3.1: {} - merge-descriptors@1.0.3: {} + merge2@1.4.1: {} + methods@1.1.2: {} - micro-eth-signer@0.14.0: + micromatch@4.0.8: dependencies: - '@noble/curves': 1.8.2 - '@noble/hashes': 1.7.2 - micro-packed: 0.7.3 - - micro-packed@0.7.3: - dependencies: - '@scure/base': 1.2.6 + braces: 3.0.3 + picomatch: 2.3.1 mime-db@1.52.0: {} @@ -4852,115 +2836,33 @@ snapshots: dependencies: brace-expansion: 1.1.12 - minimatch@5.1.9: + minimatch@9.0.9: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 2.1.0 minimist@1.2.8: {} - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - mkdirp@1.0.4: {} - mnemonist@0.38.5: - dependencies: - obliterator: 2.0.5 - - mocha@10.8.2: - dependencies: - ansi-colors: 4.1.3 - browser-stdout: 1.3.1 - chokidar: 3.6.0 - debug: 4.4.3(supports-color@8.1.1) - diff: 5.2.2 - escape-string-regexp: 4.0.0 - find-up: 5.0.0 - glob: 8.1.0 - he: 1.2.0 - js-yaml: 4.1.1 - log-symbols: 4.1.0 - minimatch: 5.1.9 - ms: 2.1.3 - serialize-javascript: 6.0.2 - strip-json-comments: 3.1.1 - supports-color: 8.1.1 - workerpool: 6.5.1 - yargs: 16.2.0 - yargs-parser: 20.2.9 - yargs-unparser: 2.0.0 - ms@2.0.0: {} ms@2.1.2: {} ms@2.1.3: {} - nanoid@3.3.11: {} + natural-compare@1.4.0: {} negotiator@0.6.3: {} - next@16.1.6(react-dom@19.2.3(react@19.2.3))(react@19.2.3): - dependencies: - '@next/env': 16.1.6 - '@swc/helpers': 0.5.15 - baseline-browser-mapping: 2.10.0 - caniuse-lite: 1.0.30001777 - postcss: 8.4.31 - react: 19.2.3 - react-dom: 19.2.3(react@19.2.3) - styled-jsx: 5.1.6(react@19.2.3) - optionalDependencies: - '@next/swc-darwin-arm64': 16.1.6 - '@next/swc-darwin-x64': 16.1.6 - '@next/swc-linux-arm64-gnu': 16.1.6 - '@next/swc-linux-arm64-musl': 16.1.6 - '@next/swc-linux-x64-gnu': 16.1.6 - '@next/swc-linux-x64-musl': 16.1.6 - '@next/swc-win32-arm64-msvc': 16.1.6 - '@next/swc-win32-x64-msvc': 16.1.6 - sharp: 0.34.5 - transitivePeerDependencies: - - '@babel/core' - - babel-plugin-macros - - node-addon-api@2.0.2: {} - - node-addon-api@5.1.0: {} - - node-fetch@2.7.0: - dependencies: - whatwg-url: 5.0.0 - - node-gyp-build@4.8.4: {} - - nopt@5.0.0: - dependencies: - abbrev: 1.1.1 + node-gyp-build@4.8.4: + optional: true normalize-path@3.0.0: {} - npmlog@5.0.1: - dependencies: - are-we-there-yet: 2.0.0 - console-control-strings: 1.1.0 - gauge: 3.0.2 - set-blocking: 2.0.0 - object-assign@4.1.1: {} object-inspect@1.13.4: {} - obliterator@2.0.5: {} - on-finished@2.4.1: dependencies: ee-first: 1.1.1 @@ -4969,7 +2871,14 @@ snapshots: dependencies: wrappy: 1.0.2 - os-tmpdir@1.0.2: {} + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 p-limit@3.1.0: dependencies: @@ -4979,9 +2888,9 @@ snapshots: dependencies: p-limit: 3.1.0 - p-map@4.0.0: + parent-module@1.0.1: dependencies: - aggregate-error: 3.1.0 + callsites: 3.1.0 parseurl@1.3.3: {} @@ -4989,10 +2898,14 @@ snapshots: path-is-absolute@1.0.1: {} + path-key@3.1.1: {} + path-parse@1.0.7: {} path-to-regexp@0.1.12: {} + path-type@4.0.0: {} + pg-cloudflare@1.3.0: optional: true @@ -5030,20 +2943,8 @@ snapshots: dependencies: split2: 4.2.0 - picocolors@1.1.1: {} - picomatch@2.3.1: {} - picomatch@4.0.3: {} - - possible-typed-array-names@1.1.0: {} - - postcss@8.4.31: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - postgres-array@2.0.0: {} postgres-bytea@1.0.1: {} @@ -5054,24 +2955,20 @@ snapshots: dependencies: xtend: 4.0.2 - process-nextick-args@2.0.1: {} + prelude-ls@1.2.1: {} proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 ipaddr.js: 1.9.1 - qrcode.react@4.2.0(react@19.2.3): - dependencies: - react: 19.2.3 + punycode@2.3.1: {} qs@6.14.2: dependencies: side-channel: 1.1.0 - randombytes@2.1.0: - dependencies: - safe-buffer: 5.2.1 + queue-microtask@1.2.3: {} range-parser@1.2.1: {} @@ -5082,53 +2979,26 @@ snapshots: iconv-lite: 0.4.24 unpipe: 1.0.0 - react-dom@19.2.3(react@19.2.3): - dependencies: - react: 19.2.3 - scheduler: 0.27.0 - - react@19.2.3: {} - - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - readdirp@3.6.0: dependencies: picomatch: 2.3.1 - readdirp@4.1.2: {} - rechoir@0.8.0: dependencies: resolve: 1.22.11 - require-directory@2.1.1: {} + resolve-from@4.0.0: {} resolve-from@5.0.0: {} - resolve@1.17.0: - dependencies: - path-parse: 1.0.7 - resolve@1.22.11: dependencies: is-core-module: 2.16.1 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + reusify@1.1.0: {} + rimraf@2.7.1: dependencies: glob: 7.2.3 @@ -5137,40 +3007,16 @@ snapshots: dependencies: glob: 7.2.3 - ripemd160@2.0.3: + run-parallel@1.2.0: dependencies: - hash-base: 3.1.2 - inherits: 2.0.4 - - rpc-websockets@9.3.5: - dependencies: - '@swc/helpers': 0.5.15 - '@types/uuid': 10.0.0 - '@types/ws': 8.18.1 - buffer: 6.0.3 - eventemitter3: 5.0.4 - uuid: 11.1.0 - ws: 8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6) - optionalDependencies: - bufferutil: 4.1.0 - utf-8-validate: 6.0.6 - - safe-buffer@5.1.2: {} + queue-microtask: 1.2.3 safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} - scheduler@0.27.0: {} - scrypt-js@3.0.1: {} - sdp@3.2.1: {} - - semver@5.7.2: {} - - semver@6.3.1: {} - semver@7.7.4: {} send@0.19.2: @@ -5191,10 +3037,6 @@ snapshots: transitivePeerDependencies: - supports-color - serialize-javascript@6.0.2: - dependencies: - randombytes: 2.1.0 - serve-static@1.16.3: dependencies: encodeurl: 2.0.0 @@ -5204,56 +3046,13 @@ snapshots: transitivePeerDependencies: - supports-color - set-blocking@2.0.0: {} - - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - setprototypeof@1.2.0: {} - sha.js@2.4.12: + shebang-command@2.0.0: dependencies: - inherits: 2.0.4 - safe-buffer: 5.2.1 - to-buffer: 1.2.2 + shebang-regex: 3.0.0 - sharp@0.34.5: - dependencies: - '@img/colour': 1.1.0 - detect-libc: 2.1.2 - semver: 7.7.4 - 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 - optional: true + shebang-regex@3.0.0: {} side-channel-list@1.0.0: dependencies: @@ -5283,21 +3082,7 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 - signal-exit@3.0.7: {} - - solc@0.8.26(debug@4.4.3): - dependencies: - command-exists: 1.2.9 - commander: 8.3.0 - follow-redirects: 1.15.11(debug@4.4.3) - js-sha3: 0.8.0 - memorystream: 0.3.1 - semver: 5.7.2 - tmp: 0.0.33 - transitivePeerDependencies: - - debug - - source-map-js@1.2.1: {} + slash@3.0.0: {} source-map-support@0.5.21: dependencies: @@ -5308,28 +3093,8 @@ snapshots: split2@4.2.0: {} - stacktrace-parser@0.1.11: - dependencies: - type-fest: 0.7.1 - statuses@2.0.2: {} - stream-chain@2.2.5: {} - - stream-json@1.9.1: - dependencies: - stream-chain: 2.2.5 - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -5340,75 +3105,39 @@ snapshots: strip-json-comments@3.1.1: {} - styled-jsx@5.1.6(react@19.2.3): - dependencies: - client-only: 0.0.1 - react: 19.2.3 - - superstruct@2.0.2: {} - supports-color@7.2.0: dependencies: has-flag: 4.0.0 - supports-color@8.1.1: - dependencies: - has-flag: 4.0.0 - supports-preserve-symlinks-flag@1.0.0: {} - tagged-tag@1.0.0: {} - - tar@6.2.1: + swagger-ui-dist@5.32.1: dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 + '@scarf/scarf': 1.4.0 + + swagger-ui-express@5.0.1(express@4.22.1): + dependencies: + express: 4.22.1 + swagger-ui-dist: 5.32.1 tarn@3.0.2: {} - text-encoding-utf-8@1.0.2: {} + text-table@0.2.0: {} tildify@2.0.0: {} - tiny-invariant@1.3.3: {} - - tiny-secp256k1@2.2.4: - dependencies: - uint8array-tools: 0.0.7 - - tiny-warning@1.0.3: {} - - tinyglobby@0.2.15: - dependencies: - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - - tmp@0.0.33: - dependencies: - os-tmpdir: 1.0.2 - - to-buffer@1.2.2: - dependencies: - isarray: 2.0.5 - safe-buffer: 5.2.1 - typed-array-buffer: 1.0.3 - to-regex-range@5.0.1: dependencies: is-number: 7.0.0 - toformat@2.0.0: {} - toidentifier@1.0.1: {} - tr46@0.0.3: {} - tree-kill@1.2.2: {} + ts-api-utils@1.4.3(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + ts-node-dev@2.0.0(@types/node@20.19.37)(typescript@5.9.3): dependencies: chokidar: 3.6.0 @@ -5452,12 +3181,6 @@ snapshots: strip-bom: 3.0.0 strip-json-comments: 2.0.1 - tslib@1.14.1: {} - - tslib@2.8.1: {} - - tsort@0.0.1: {} - turbo-darwin-64@2.8.15: optional: true @@ -5485,171 +3208,53 @@ snapshots: turbo-windows-64: 2.8.15 turbo-windows-arm64: 2.8.15 - tweetnacl@1.0.3: {} + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 type-fest@0.20.2: {} - type-fest@0.21.3: {} - - type-fest@0.7.1: {} - - type-fest@5.4.4: - dependencies: - tagged-tag: 1.0.0 - 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 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - typescript@5.9.3: {} - uint8array-tools@0.0.7: {} - - uint8array-tools@0.0.8: {} - - uint8array-tools@0.0.9: {} - ulidx@2.4.1: dependencies: layerr: 3.0.0 undici-types@6.21.0: {} - undici@5.29.0: - dependencies: - '@fastify/busboy': 2.1.1 - - universalify@0.1.2: {} - unpipe@1.0.0: {} - utf-8-validate@6.0.6: + uri-js@4.4.1: dependencies: - node-gyp-build: 4.8.4 - optional: true - - util-deprecate@1.0.2: {} + punycode: 2.3.1 utils-merge@1.0.1: {} - uuid@11.1.0: {} - - uuid@8.3.2: {} - v8-compile-cache-lib@3.0.1: {} - valibot@1.2.0(typescript@5.9.3): - optionalDependencies: - typescript: 5.9.3 - - varuint-bitcoin@2.0.0: - dependencies: - uint8array-tools: 0.0.8 - vary@1.1.2: {} - webidl-conversions@3.0.1: {} - - webrtc-adapter@9.0.3: + which@2.0.2: dependencies: - sdp: 3.2.1 + isexe: 2.0.0 - whatwg-url@5.0.0: - dependencies: - tr46: 0.0.3 - webidl-conversions: 3.0.1 - - which-typed-array@1.1.20: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - - wide-align@1.1.5: - dependencies: - string-width: 4.2.3 - - widest-line@3.1.0: - dependencies: - string-width: 4.2.3 - - wif@5.0.0: - dependencies: - bs58check: 4.0.0 - - workerpool@6.5.1: {} - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 + word-wrap@1.2.5: {} wrappy@1.0.2: {} - ws@7.4.6(bufferutil@4.1.0)(utf-8-validate@6.0.6): + ws@7.4.6(bufferutil@4.1.0): optionalDependencies: bufferutil: 4.1.0 - utf-8-validate: 6.0.6 - - ws@7.5.10(bufferutil@4.1.0)(utf-8-validate@6.0.6): - optionalDependencies: - bufferutil: 4.1.0 - utf-8-validate: 6.0.6 - - ws@8.19.0(bufferutil@4.1.0)(utf-8-validate@6.0.6): - optionalDependencies: - bufferutil: 4.1.0 - utf-8-validate: 6.0.6 xtend@4.0.2: {} - y18n@5.0.8: {} - - yallist@4.0.0: {} - - yargs-parser@20.2.9: {} - - yargs-unparser@2.0.0: - dependencies: - camelcase: 6.3.0 - decamelize: 4.0.0 - flat: 5.0.2 - is-plain-obj: 2.1.0 - - yargs@16.2.0: - dependencies: - cliui: 7.0.4 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 20.2.9 - yn@3.1.1: {} yocto-queue@0.1.0: {} zod@3.25.76: {} - - zustand@5.0.11(@types/react@19.2.14)(react@19.2.3): - optionalDependencies: - '@types/react': 19.2.14 - react: 19.2.3 - - zxing-wasm@2.2.4(@types/emscripten@1.41.5): - dependencies: - '@types/emscripten': 1.41.5 - type-fest: 5.4.4 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index e9b0dad..8ab3e17 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,2 @@ packages: - 'apps/*' - - 'packages/*' diff --git a/scripts/bitok-entrypoint.sh b/scripts/bitok-entrypoint.sh deleted file mode 100644 index 6a26478..0000000 --- a/scripts/bitok-entrypoint.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh -set -e - -# Read Vault root token from shared volume (written by vault-init) -TOKEN_FILE="/vault/file/root-token" -if [ -f "$TOKEN_FILE" ]; then - export VAULT_TOKEN=$(cat "$TOKEN_FILE" | tr -d '\n\r ') - echo "[bitok-entrypoint] Loaded VAULT_TOKEN from $TOKEN_FILE" -else - echo "[bitok-entrypoint] WARNING: $TOKEN_FILE not found, using VAULT_TOKEN from env" -fi - -# Start BITOK auth service -exec granian --interface asgi ${APP_MODULE:-src.main:app} \ - --host ${APP_HOST:-0.0.0.0} \ - --port ${APP_PORT:-8000} \ - --workers ${APP_WORKERS:-1} \ - --loop uvloop diff --git a/scripts/vault-init.sh b/scripts/vault-init.sh deleted file mode 100644 index 78ee235..0000000 --- a/scripts/vault-init.sh +++ /dev/null @@ -1,128 +0,0 @@ -#!/bin/sh -set -e - -export VAULT_ADDR=http://vault:8200 -INIT_FILE="/vault/file/init-keys.json" -TOKEN_FILE="/vault/file/root-token" - -echo "[vault-init] Waiting for Vault to respond..." -until wget -qO- ${VAULT_ADDR}/v1/sys/seal-status > /dev/null 2>&1; do - sleep 1 -done - -# Check if Vault is initialized (use HTTP API — vault CLI may suppress output when sealed) -STATUS_JSON=$(wget -qO- ${VAULT_ADDR}/v1/sys/seal-status 2>/dev/null || echo '{}') -INITIALIZED=$(echo "$STATUS_JSON" | grep -o '"initialized":[a-z]*' | cut -d: -f2) -echo "[vault-init] Vault initialized=$INITIALIZED" - -FIRST_RUN="false" - -if [ "$INITIALIZED" != "true" ]; then - echo "[vault-init] First run — initializing Vault..." - FIRST_RUN="true" - - # Init with 1 key share for dev simplicity - vault operator init -key-shares=1 -key-threshold=1 -format=json > "$INIT_FILE" - - UNSEAL_KEY=$(tr -d ' \n' < "$INIT_FILE" | grep -o '"unseal_keys_b64":\["[^"]*"' | cut -d'"' -f4) - ROOT_TOKEN=$(tr -d ' \n' < "$INIT_FILE" | grep -o '"root_token":"[^"]*"' | cut -d'"' -f4) - - echo "[vault-init] Unsealing with key: ${UNSEAL_KEY:0:8}..." - vault operator unseal "$UNSEAL_KEY" - export VAULT_TOKEN="$ROOT_TOKEN" - -else - echo "[vault-init] Vault already initialized." - - SEALED=$(echo "$STATUS_JSON" | grep -o '"sealed":[a-z]*' | cut -d: -f2) - echo "[vault-init] Vault sealed=$SEALED" - - if [ "$SEALED" = "true" ]; then - echo "[vault-init] Vault is sealed, unsealing..." - if [ ! -f "$INIT_FILE" ]; then - echo "[vault-init] ERROR: init-keys.json not found. Cannot unseal." - exit 1 - fi - UNSEAL_KEY=$(tr -d ' \n' < "$INIT_FILE" | grep -o '"unseal_keys_b64":\["[^"]*"' | cut -d'"' -f4) - vault operator unseal "$UNSEAL_KEY" - echo "[vault-init] Vault unsealed." - else - echo "[vault-init] Vault already unsealed." - fi - - # Load root token - if [ -f "$INIT_FILE" ]; then - ROOT_TOKEN=$(tr -d ' \n' < "$INIT_FILE" | grep -o '"root_token":"[^"]*"' | cut -d'"' -f4) - export VAULT_TOKEN="$ROOT_TOKEN" - fi -fi - -# ── Write root-token file for other containers ── -echo "$ROOT_TOKEN" > "$TOKEN_FILE" - -# ── Ensure secrets engines exist ── -# Enable kv mount (wallet) — ignore error if already enabled -vault secrets enable -path=kv -version=2 kv 2>/dev/null || true - -# Enable secrets mount (BITOK) — ignore error if already enabled -vault secrets enable -path=secrets -version=2 kv 2>/dev/null || true - -# ── Wallet infrastructure secrets ── -echo "[vault-init] Writing wallet secrets..." -vault kv put kv/cryptowallet \ - db_host=postgres \ - db_port=5432 \ - db_user=postgres \ - db_password=postgres \ - db_name=cryptowallet_devphase3 \ - relay_api_key="" - -# ── BITOK database secret ── -echo "[vault-init] Writing BITOK secrets..." -vault kv put secrets/database \ - HOST=postgres \ - PORT=5432 \ - USER=postgres \ - PASSWORD=postgres \ - NAME=bitok_dev - -# ── BITOK RabbitMQ secret ── -vault kv put secrets/rabbitmq \ - HOST=rabbitmq \ - PORT=5672 \ - USER=guest \ - PASSWORD=guest \ - VHOST=/ - -# ── BITOK CSRF secret ── -vault kv put secrets/csrf \ - KEY=dev-csrf-secret-key-minimum-32-characters-long - -# ── BITOK JWT RS256 key pair (only generate on first run) ── -# Check if JWT keys already exist -JWT_EXISTS=$(vault kv get -format=json secrets/jwt/kid 2>/dev/null && echo "yes" || echo "no") - -if [ "$JWT_EXISTS" = "no" ]; then - echo "[vault-init] Generating RSA-2048 key pair for JWT..." - apk add --no-cache openssl > /dev/null 2>&1 || true - openssl genrsa -out /tmp/jwt_private.pem 2048 2>/dev/null - openssl rsa -in /tmp/jwt_private.pem -pubout -out /tmp/jwt_public.pem 2>/dev/null - - PRIVATE_KEY=$(cat /tmp/jwt_private.pem) - PUBLIC_KEY=$(cat /tmp/jwt_public.pem) - - vault kv put secrets/jwt/kid \ - active=kid-dev-001 \ - previous="" - - vault kv put secrets/jwt/kids/kid-dev-001 \ - private_key="$PRIVATE_KEY" \ - public_key="$PUBLIC_KEY" - - rm -f /tmp/jwt_private.pem /tmp/jwt_public.pem - echo "[vault-init] JWT keys generated." -else - echo "[vault-init] JWT keys already exist, skipping generation." -fi - -echo "[vault-init] All secrets ready (wallet + BITOK). Done." diff --git a/start.bat b/start.bat deleted file mode 100644 index 1ea8225..0000000 --- a/start.bat +++ /dev/null @@ -1,222 +0,0 @@ -@echo off -setlocal enabledelayedexpansion -cd /d "%~dp0" - -echo. -echo ========================================== -echo CryptoWallet + BITOK Auth - Local Dev -echo ========================================== -echo. - -:: ── 1. Check pnpm ──────────────────────────────────────────────────────────── -where pnpm >nul 2>&1 -if errorlevel 1 ( - echo [ERROR] pnpm not found. Install with: npm install -g pnpm - pause - exit /b 1 -) - -:: ── 2. Check Docker ────────────────────────────────────────────────────────── -where docker >nul 2>&1 -if errorlevel 1 ( - echo [ERROR] Docker not found. Install Docker Desktop and ensure it is running. - pause - exit /b 1 -) - -:: ── 3. Stop local PostgreSQL (if any) so Docker gets port 5432 ───────────────── -echo [1/8] Preparing environment... -net stop postgresql-x64-16 2>nul -timeout /t 2 /nobreak >nul - -:: ── 4. Wait for Docker Engine ───────────────────────────────────────────────── -echo Waiting for Docker Engine... -set /a docker_attempts=0 -:waitdocker -set /a docker_attempts+=1 -if !docker_attempts! gtr 30 ( - echo [ERROR] Docker Engine did not respond after 60 seconds. - echo Make sure Docker Desktop is fully started ^(whale icon in tray^). - pause - exit /b 1 -) -docker info >nul 2>&1 -if errorlevel 1 ( - timeout /t 2 /nobreak >nul - goto waitdocker -) -echo Docker ready. - -:: ── 5. Start infrastructure via Docker Compose ──────────────────────────────── -echo [2/8] Starting infrastructure (PostgreSQL, Vault, RabbitMQ, KeyDB)... -docker compose up -d postgres vault vault-init rabbitmq keydb 2>&1 -if errorlevel 1 ( - echo Retrying with docker-compose... - docker-compose up -d postgres vault vault-init rabbitmq keydb 2>&1 -) -if errorlevel 1 ( - echo. - echo [ERROR] Could not start Docker Compose. - echo. - echo Common fixes: - echo 1. Wait 30-60 sec after opening Docker Desktop - echo 2. Restart Docker Desktop ^(right-click tray icon -^> Restart^) - echo 3. Run: docker compose up -d postgres vault vault-init rabbitmq keydb - echo. - pause - exit /b 1 -) - -:: Wait for PostgreSQL to be ready -set /a pg_attempts=0 -:waitpg -set /a pg_attempts+=1 -if !pg_attempts! gtr 30 ( - echo [ERROR] PostgreSQL not responding after 60 seconds. - pause - exit /b 1 -) -timeout /t 2 /nobreak >nul -docker exec cryptowallet-db pg_isready -U postgres -q 2>nul -if errorlevel 1 goto waitpg -echo PostgreSQL is ready. - -:: Wait for RabbitMQ to be ready -echo Waiting for RabbitMQ... -set /a rmq_attempts=0 -:waitrmq -set /a rmq_attempts+=1 -if !rmq_attempts! gtr 30 ( - echo [WARN] RabbitMQ slow to start, continuing anyway... - goto rmq_done -) -timeout /t 2 /nobreak >nul -docker exec cryptowallet-rabbitmq rabbitmq-diagnostics check_port_connectivity >nul 2>&1 -if errorlevel 1 goto waitrmq -:rmq_done -echo RabbitMQ is ready. - -:: ── 6. Create databases ────────────────────────────────────────────────────── -echo [3/8] Ensuring databases... -docker exec cryptowallet-db psql -U postgres -c "CREATE DATABASE cryptowallet_devphase3" 2>nul -if errorlevel 1 ( - echo cryptowallet_devphase3 may already exist, continuing... -) -docker exec cryptowallet-db psql -U postgres -c "CREATE DATABASE bitok_dev" 2>nul -if errorlevel 1 ( - echo bitok_dev may already exist, continuing... -) -echo Databases ready. - -:: ── 7. Wait for vault-init to finish ───────────────────────────────────────── -echo [4/8] Waiting for Vault initialization... -set /a vault_attempts=0 -:waitvault -set /a vault_attempts+=1 -if !vault_attempts! gtr 30 ( - echo [WARN] vault-init taking long, continuing... - goto vault_done -) -timeout /t 2 /nobreak >nul -docker inspect cryptowallet-vault-init --format="{{.State.Status}}" 2>nul | findstr /C:"exited" >nul 2>&1 -if errorlevel 1 goto waitvault -:vault_done -echo Vault initialized. - -:: ── 8. Update .env ─────────────────────────────────────────────────────────── -echo [5/8] Updating .env... -if not exist .env ( - copy .env.example .env >nul -) -powershell -NoProfile -Command "(Get-Content .env) -replace '^DB_NAME=.*', 'DB_NAME=cryptowallet_devphase3' -replace '^DATABASE_URL=.*', 'DATABASE_URL=postgresql://postgres:postgres@localhost:5432/cryptowallet_devphase3' | Set-Content .env" -echo .env updated. - -:: ── 9. Install deps and run migrations ──────────────────────────────────────── -echo [6/8] Installing dependencies... -call pnpm install -if errorlevel 1 ( - echo [ERROR] pnpm install failed. - pause - exit /b 1 -) -echo Dependencies installed. - -echo Running database migrations... -cd apps\api -call pnpm migrate -if errorlevel 1 ( - echo Migration failed - resetting DB and retrying... - call pnpm db:reset - call pnpm migrate -) -if errorlevel 1 ( - echo [ERROR] Migrations failed - check output above. - pause - exit /b 1 -) -cd ..\.. -echo Migrations done. - -:: ── 10. Initialize BITOK database tables ────────────────────────────────────── -echo [7/8] Initializing BITOK database... -if exist BITOK\sql\tables.sql ( - docker exec -i cryptowallet-db psql -U postgres -d bitok_dev < BITOK\sql\tables.sql 2>nul - echo BITOK tables initialized. -) else ( - echo BITOK/sql/tables.sql not found, skipping... -) - -:: ── 11. Start BITOK auth service (Docker) ───────────────────────────────────── -echo Starting BITOK auth service... -docker compose up -d bitok-auth 2>&1 -if errorlevel 1 ( - echo [WARN] BITOK auth container failed to start. Check: docker logs cryptowallet-bitok -) - -:: Wait briefly for BITOK to be responsive -set /a bitok_attempts=0 -:waitbitok -set /a bitok_attempts+=1 -if !bitok_attempts! gtr 15 ( - echo [WARN] BITOK slow to start, continuing... - goto bitok_done -) -timeout /t 2 /nobreak >nul -powershell -NoProfile -Command "try { $r = Invoke-WebRequest -Uri http://localhost:8000/ping -TimeoutSec 2 -UseBasicParsing; exit 0 } catch { exit 1 }" >nul 2>&1 -if errorlevel 1 goto waitbitok -:bitok_done -echo BITOK auth service ready. - -:: ── 12. Stop old dev servers and clear lock ───────────────────────────────────── -echo [8/8] Starting wallet servers... -powershell -NoProfile -Command "Get-NetTCPConnection -LocalPort 3000,3001 -ErrorAction SilentlyContinue | ForEach-Object { Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }" -if exist apps\web\.next\dev\lock del apps\web\.next\dev\lock 2>nul -timeout /t 1 /nobreak >nul - -:: Start wallet API and Web -start "CryptoWallet API [port 3001]" cmd /k "cd /d %~dp0apps\api && pnpm dev" -timeout /t 2 /nobreak >nul -start "CryptoWallet Web [port 3000]" cmd /k "cd /d %~dp0apps\web && pnpm dev" - -:: ── Done ───────────────────────────────────────────────────────────────────── -echo. -echo ========================================== -echo All services started! -echo. -echo Web: http://localhost:3000 -echo API: http://localhost:3001 -echo BITOK: http://localhost:8000 -echo RabbitMQ: http://localhost:15672 (guest/guest) -echo DB: localhost:5432 -echo wallet DB: cryptowallet_devphase3 -echo BITOK DB: bitok_dev -echo user: postgres pass: postgres -echo ========================================== -echo. - -timeout /t 3 /nobreak >nul -start http://localhost:3000 - -echo Press any key to close this launcher... -pause >nul -endlocal diff --git a/start.sh b/start.sh new file mode 100644 index 0000000..2df610c --- /dev/null +++ b/start.sh @@ -0,0 +1,54 @@ +#!/bin/bash +set -euo pipefail + +cd "$(dirname "$0")" + +echo "==========================================" +echo " CryptoWallet API - Production Deploy" +echo "==========================================" + +# 1. Docker check +command -v docker >/dev/null || { echo "[ERROR] Docker not installed"; exit 1; } +docker compose version >/dev/null 2>&1 || { echo "[ERROR] docker compose plugin missing"; exit 1; } + +# 2. .env check +if [ ! -f .env ]; then + cp .env.example .env + echo "[INFO] Created .env from .env.example" + echo "[INFO] Fill in VAULT_ROLE_ID, VAULT_SECRET_ID and re-run." + exit 1 +fi + +# 3. Build & start +echo "[INFO] Building image..." +docker compose build --pull api + +echo "[INFO] Starting services..." +docker compose up -d + +# 4. Wait for health +echo "[INFO] Waiting for API to be healthy..." +for i in $(seq 1 60); do + if curl -sf http://localhost:3001/api/health >/dev/null 2>&1; then + echo "[OK] API healthy" + break + fi + if [ "$i" = "60" ]; then + echo "[ERROR] API did not become healthy in 120s" + docker compose logs --tail=50 api + exit 1 + fi + sleep 2 +done + +echo "" +echo "==========================================" +echo " Deploy complete" +echo "" +echo " API: http://localhost:3001" +echo " Health: http://localhost:3001/api/health" +echo " Docs: http://localhost:3001/api/docs" +echo "" +echo " Logs: docker compose logs -f api" +echo " Stop: docker compose down" +echo "==========================================" diff --git a/turbo.json b/turbo.json deleted file mode 100644 index ca398ed..0000000 --- a/turbo.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "$schema": "https://turbo.build/schema.json", - "tasks": { - "build": { - "dependsOn": ["^build"], - "outputs": [".next/**", "dist/**"] - }, - "dev": { - "cache": false, - "persistent": true - }, - "lint": {}, - "typecheck": {} - } -} diff --git a/vault/vault.hcl b/vault/vault.hcl deleted file mode 100644 index d348ce3..0000000 --- a/vault/vault.hcl +++ /dev/null @@ -1,12 +0,0 @@ -ui = true - -storage "file" { - path = "/vault/file" -} - -listener "tcp" { - address = "0.0.0.0:8200" - tls_disable = 1 -} - -disable_mlock = true