Skip to content

Latest commit

 

History

History
343 lines (294 loc) · 17.6 KB

File metadata and controls

343 lines (294 loc) · 17.6 KB

CryptoLens — Web3 Dashboard

CryptoLens Logo

Explore tokens, charts, favorites and profiles — backed by Supabase and Redis.

Next.js · Supabase · Redis Cloud · WalletConnect

Live Demo

Table of Contents

Overview

CryptoLens is a dApp to explore tokens, manage favorites, and visualize market metrics with secure authentication (Google + Wallet via SIWE/personal_sign), user profiles in Supabase, and centralized caching in Redis. Built with Next.js (Pages Router), React, RainbowKit/wagmi, Supabase, and Tailwind/Shadcn. Live demo: https://cryptolens.casaislabs.com/

Features

  • Token exploration with price, 24h change, market cap, and volume.
  • User favorites persisted with Supabase RLS.
  • Advanced filters and virtualized grid for performance.
  • Sign in with Google and wallet (SIWE + fallback personal_sign) always enabled.
  • User profiles with social links and linked wallet status.
  • Centralized price cache in Redis with cron; CoinGecko primary with CoinMarketCap fallback.

Tech Stack

  • Next.js + React
  • next-auth (Google + Credentials for wallet)
  • @supabase/supabase-js (API + RLS)
  • wagmi + @rainbow-me/rainbowkit (WalletConnect)
  • ioredis + node-cron (price cache)
  • tailwindcss + @/components/ui/* (Shadcn)
  • lucide-react, sonner

Architecture

flowchart LR
  Client[Next.js Client] -->|OAuth| NextAuth
  Client -->|Wallet| WalletConnect
  NextAuth --> Supabase
  API[/pages/api/*/] --> Redis[(Redis Cache)]
  API --> CoinGecko
  API --> CoinMarketCap
  Supabase --> DB[(Postgres + RLS)]
Loading
sequenceDiagram
  participant U as User
  participant C as Client (Next.js)
  participant A as NextAuth
  participant W as Wallet (RainbowKit/WC)
  participant S as Supabase
  U->>C: Click "Sign-In with Wallet"
  C->>W: Request signature (SIWE/personal_sign)
  W-->>C: Signature returned
  C->>A: Verify signature, create session
  A->>S: Call RPC with JWS (RLS enforced)
  S-->>A: Authorized response
  A-->>C: Session established
Loading

Project Structure

  • pages/ — Pages Router, APIs (api/*), views (index, dashboard, login, profile, token/[id]).
  • components/ — Header, filters, token cards, wallet connection.
  • lib/ — Supabase, JWT, price cache, utilities.
  • public/favicon.svg, logo.svg.
  • create_supabase_tables.sql — Schema, RPC, and RLS policies.

Environment (.env)

Create a .env file at the project root:

# Environment
NODE_ENV=development
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=put_a_strong_random_secret_here

# Supabase
SUPABASE_URL=https://<your-project>.supabase.co
SUPABASE_KEY=<anon_public_key>
SUPABASE_JWT_SECRET=<supabase_jwt_secret>

# WalletConnect (RainbowKit)
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=<walletconnect_project_id>

# Google OAuth
GOOGLE_CLIENT_ID=<google_client_id>
GOOGLE_CLIENT_SECRET=<google_client_secret>

# CoinMarketCap (fallback)
COINMARKETCAP_API_KEY=<cmc_api_key>

# Redis (price cache)
REDIS_HOST=<redis_host>
REDIS_PORT=<redis_port>
REDIS_USERNAME=<redis_username>
REDIS_PASSWORD=<redis_password>

# Keepalive endpoint (external cron)
KEEPALIVE_SECRET=<strong_random_keepalive_secret>

# Logging
LOG_LEVEL=info

How to obtain each field

  • Supabase:
    • SUPABASE_URL: Supabase Dashboard → Settings → API → Project URL.
    • SUPABASE_KEY (anon): Supabase Dashboard → Settings → API → anon public key.
    • SUPABASE_JWT_SECRET: Supabase Dashboard → Settings → API → JWT Secret (keep distinct from NEXTAUTH_SECRET; do not reuse).
  • Google OAuth:
    • Google Cloud Console → APIs & Services → OAuth consent screen (External type, basic info).
    • Create credentials: Credentials → Create Credentials → OAuth client ID → Web application.
    • Authorized settings:
      • Authorized redirect URIs: http://localhost:3000/api/auth/callback/google and production domain https://your-domain/api/auth/callback/google.
      • Authorized JavaScript origins: http://localhost:3000 and your production domain.
    • Copy GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET.
  • WalletConnect:
  • CoinMarketCap:
    • Create an account at https://pro.coinmarketcap.com and generate an API Key.
    • Note rate limits and plan restrictions; this is used as a fallback when CoinGecko fails.
  • Redis (Upstash or other provider):
    • Provision a database at Upstash (or Aiven/Redis Cloud).
    • Copy host, port, username, and password.
    • Note: if your provider requires TLS, adjust ioredis connection options accordingly (many dev environments work without TLS; verify production requirements).
    • Recommendation: Redis Cloud (managed, production-grade): https://cloud.redis.io/
  • Keepalive:
    • Generate a long random value for KEEPALIVE_SECRET.
    • Your VPS cron should call GET /api/keepalive with header x-keepalive-secret: <KEEPALIVE_SECRET>.
    • Use a secret different from NEXTAUTH_SECRET and SUPABASE_JWT_SECRET.

Supabase Setup (Tables, RPC, RLS)

  1. Open your Supabase project → SQL Editor.
  2. Open the local file create_supabase_tables.sql.
  3. Copy and paste its entire content into the Supabase SQL Editor.
  4. Execute section by section (tables profiles and favorites, indexes, FK, transactional RPC replace_user_favorites, verification tasks, and RLS policies).
  5. Verify at the end using the “VERIFICATION” section.

Notes:

  • RLS policies rely on the sub claim from JWT: the code generates a JWS from the NextAuth token to operate under RLS.
  • Wallet link/unlink updates profiles.wallet_address and validates duplicates.

Development

  • Install deps: npm install.
  • Start dev: npm run dev → visit http://localhost:3000.
  • Build: npm run build then npm run start.
  • Lint: npm run lint.

Scripts

  • npm run dev — Start Next.js dev server
  • npm run build — Build production bundle
  • npm run start — Start production server
  • npm run lint — Run ESLint

Logging

  • Pino-only logging is used across server APIs; Sentry was removed.
  • View logs in development in the terminal; in production via your platform's stdout/logs.
  • Control verbosity with LOG_LEVEL (debug, info, warn, error). Default is info.
  • Per-request correlation via requestId from middleware; appears on API logs.
  • Core logger: lib/logger.js with robust Error serialization and child loggers per request.
  • Instrumented APIs: pages/api/tokens/all.js, pages/api/auth/[...nextauth].js, and pages/api/keepalive.js.
  • Optional: install pino-pretty locally for human-readable dev output.

API Reference

  • GET /api/tokens/all — Returns the aggregated top-token price snapshots.
  • GET /api/fetchTokens?ids=favorites|id1,id2,... — Returns filtered price snapshots by favorites or explicit IDs.
  • GET /api/fetchTokens?ids=all — Deprecated; use GET /api/tokens/all.
  • GET /api/token/[id] — Returns detailed token data and 7-day chart (with fallbacks).
  • POST /api/updateFavorites — Upserts favorites under RLS.
  • POST /api/updateProfile — Updates profile fields (validated links).
  • POST /api/wallet — Wallet challenge, link/unlink, status.
  • GET /api/keepalive — Internal operational endpoint used by external cron to verify Redis and Supabase reachability.

Authentication Flow

  • Google: standard OAuth via NextAuth.
  • Wallet: SIWE (recommended) with fallback personal_sign.
  • Wallet provider is always active (the environment flag was removed).

Price Cache

  • lib/priceCache.js pre-warms prices and stores in Redis.
  • Cron every 5 minutes; fallback to CoinMarketCap when CoinGecko fails.

Keepalive Endpoint

  • GET /api/keepalive is an internal operational endpoint used by external cron jobs (for example, from a VPS).
  • Required header: x-keepalive-secret matching KEEPALIVE_SECRET.
  • It performs a lightweight Redis read (prices:all) and a minimal Supabase query (profiles limit 1).
  • Success response includes ok, Redis status, and Supabase status.

Deployment

  • Vercel or similar: configure all .env variables in your platform settings.
  • Set NEXTAUTH_URL to your real domain.
  • Use distinct secrets for NEXTAUTH_SECRET and SUPABASE_JWT_SECRET to follow best security practices.

Production Checklist

  • Secrets: Generate a unique NEXTAUTH_SECRET; keep different from SUPABASE_JWT_SECRET.
  • Supabase: Run create_supabase_tables.sql; verify RLS policies and RPC.
  • OAuth: Confirm Google redirect URIs and consent screen publishing.
  • Wallet: SIWE domain matches your production host; enable WalletConnect project in production.
  • Redis: Use https://cloud.redis.io/; verify TLS and credentials; set appropriate TTLs in lib/priceCache.js.
  • Rate limits: Configure upstream API rate limits and enable backoff logic.
  • Observability: Pino JSON logs; integrate platform log dashboards; optional pino-pretty in development.
  • Security headers: Globally enforced in middleware.js (CSP, HSTS in prod, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy). Review CSP for any external domains needed.
  • Next.js Images: Review next.config.mjs images.remotePatterns for allowed domains.
  • Backups: Store .env.production securely; rotate keys periodically.

Troubleshooting

  • “Missing Supabase env” → Ensure SUPABASE_URL and SUPABASE_KEY.
  • “HMAC/JWT secret required” → Define NEXTAUTH_SECRET (and optionally SUPABASE_JWT_SECRET).
  • “Google OAuth redirect error” → Check authorized URIs.
  • “WalletConnect does not open” → Verify NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID.
  • “CMC data fallback 403/401” → Confirm COINMARKETCAP_API_KEY and rate limits.
  • “Redis connection error” → Verify host/port/user/password and TLS requirements.
  • “Domain mismatch (SIWE)” → The SIWE domain must match NEXTAUTH_URL/host:
    • Dev: http://localhost:3000.
    • Prod: your actual domain.
  • “403 on mutating API from another origin” → CSRF checks Origin. Send from same-origin or avoid cookies for cross-domain.
  • “CORS preflight blocked cross-domain” → CORS is restricted to same-origin; ensure frontend and API share domain/port.

License

This project is intended as a base for a Web3 dashboard. Adapt and extend as needed.

Generate a strong NEXTAUTH_SECRET

  • Do not reuse Supabase's JWT Secret. Use a unique value.
  • Recommended commands to generate a 64-byte random secret:
    • PowerShell (Windows): [Convert]::ToBase64String([Security.Cryptography.RandomNumberGenerator]::GetBytes(64))
    • Node.js: node -e "console.log(require('crypto').randomBytes(64).toString('base64'))"
    • OpenSSL (if available): openssl rand -base64 64
  • Store the generated string in NEXTAUTH_SECRET in .env.

Technical Details

  • Architecture: Next.js Pages Router serving UI and API routes. Client UI in pages/*.js and components/*. Server-only logic in pages/api/* and lib/*.
  • Authentication: NextAuth with Google provider and a Credentials provider for wallet. Wallet sign-in supports siwe and personal_sign. Challenge cookie is signed via HMAC; verification runs in pages/api/auth/[...nextauth].js.
  • JWT → Supabase: lib/jwt.js builds a JWS from the NextAuth token to call Supabase under RLS. Policies use claim sub for per-user access.
  • Supabase Schema: create_supabase_tables.sql creates profiles and favorites, adds indexes and FK, and defines transactional RPC replace_user_favorites(p_user_id, p_token_ids).
  • RLS Policies: allow each user to operate only on their own rows in profiles and favorites using (auth.jwt() ->> 'sub') = user_id.
  • Data Flow:
    • Favorites: UI → pages/api/updateFavorites.js → Supabase RPC/insert under RLS → persisted per user.
    • Tokens list: UI → pages/api/tokens/all.js → Redis cache (lib/priceCache.js) → CoinGecko primary, CMC fallback.
    • Filtered/favorites tokens: UI → pages/api/fetchTokens.js (ids=favorites|id1,id2,...) → Redis cache (lib/priceCache.js) with provider fallback.
    • Token details + chart: UI → pages/api/token/[id].js → CoinGecko market data and chart; fallback to CMC quotes/latest if needed.
  • Price Cache & Cron:
    • Redis keys: prices:all (TTL 300s). Functions: getAllPrices, refreshAllPrices.
    • Cron: every 5 minutes (*/5 * * * *) to refresh aggregated prices.
  • External Providers:
    • CoinGecko: primary market/price source.
    • CoinMarketCap: fallback via listings/latest and quotes/latest using COINMARKETCAP_API_KEY.
    • WalletConnect (RainbowKit): configured in lib/web3Config.js with NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID.
  • Rate Limiting & Resilience:
    • pages/api/token/[id].js includes basic rate-limit and retries/backoffs around upstream APIs, with graceful fallbacks.
    • pages/api/fetchTokens.js filters and validates data; if cache/network fails, falls back to direct upstream fetch.
  • Performance & UX:
    • Virtualized grid: components/VirtualizedTokenGrid.jsx for large lists.
    • Charts: components/TokenChart.jsx with Recharts and custom tooltips.
    • UI components: Shadcn (components/ui/*), Tailwind utility classes, Skeletons, and Sonner toasts.
    • Accessibility: improved headings, focus, alt text (e.g., CryptoLens Logo).
  • Security Notes:
    • Global security headers via middleware.js: CSP (strict; dev permits unsafe-eval), HSTS (prod), X-Content-Type-Options=nosniff, X-Frame-Options=DENY, Referrer-Policy=strict-origin-when-cross-origin, and a restrictive Permissions-Policy.
    • CSRF protection: mutating API methods (POST/PUT/PATCH/DELETE) require same-origin Origin. Cross-domain requests with cookies are rejected with 403.
    • CORS: same-origin only; preflight OPTIONS handled with restricted headers and credentials.
    • Payload limits: ~1MB enforced in middleware for mutating API requests.
    • Cookies: wallet challenge cookie uses SameSite=Strict, HttpOnly, and Secure in production.
    • Distinct secrets: NEXTAUTH_SECRET should be different from SUPABASE_JWT_SECRET.
    • Server-only modules: lib/supabase.js, lib/profile.js, lib/priceCache.js throw if imported in browser.
    • Cookies: challenge cookie is HMAC-protected and time-bound.
  • API Endpoints (summary):
    • GET /api/tokens/all — returns aggregated top-token price snapshots.
    • GET /api/fetchTokens?ids=favorites|id1,id2,... — returns filtered price snapshots.
    • GET /api/fetchTokens?ids=all — deprecated; use GET /api/tokens/all.
    • GET /api/token/[id] — returns detailed token data and 7-day chart (with fallbacks).
    • POST /api/updateFavorites — upserts favorites under RLS.
    • POST /api/updateProfile — updates profile fields (validated links).
    • POST /api/wallet — wallet challenge, link/unlink, status.
    • GET /api/keepalive — internal operational endpoint used by external cron to verify Redis and Supabase reachability.
  • Configuration:
    • Next.js config in next.config.mjs and ESLint in eslint.config.mjs.
    • Absolute imports via jsconfig.json.
    • Styles: styles/globals.css.

Visual Guide

The following images illustrate key parts of the CryptoLens experience and architecture. All assets live under public/ and are referenced with repository-relative paths so they render correctly on GitHub.

  • Landing & Branding

    • Home hero with logo and primary CTA to get started.
    • Landing & Branding — home hero with logo and CTA
  • Login & Authentication

    • Sign-in interface allowing authentication via Google or Web3 wallet connection.
    • Login & Authentication — Google and Web3 wallet sign-in
  • Dashboard Overview

    • Displays a grid of top tokens and a Favorites section (if none are selected).
    • Dashboard Overview — tokens grid and favorites section
  • Favorites / Watchlist

    • List of favorited tokens for quick access to their details and main metrics.
    • Favorites / Watchlist — quick access to favorited tokens
  • Filters & Sorting

    • Advanced filtering and sorting panel by price, volume, market cap, etc.
    • Filters & Sorting — advanced filtering and sorting panel
  • Token Detail & Chart

    • Detailed view of a token (e.g., Lido Staked Ether) showing price, market cap, 24h volume, and a 7-day price chart.
    • Token Detail & Chart — price, market cap, volume, 7-day chart
  • Profile & Wallet Linking

    • User profile view to manage username, bio, social links, and wallet connection status.
    • Profile & Wallet Linking — edit profile, social links, wallet status