From ee855ff04500e01c69e5fa581d259f5a3b457c5f Mon Sep 17 00:00:00 2001 From: HamzaAhmad6292 Date: Fri, 27 Feb 2026 21:49:44 +0500 Subject: [PATCH] Add product docs and landing page with / and /terminal routing --- ARCHITECTURE.md | 121 +++++++++++++ IMPLEMENTATION.md | 200 +++++++++++++++++++++ MVP.md | 145 +++++++++++++++ REQUIREMENTS.docx | Bin 0 -> 15868 bytes REQUIREMENTS.md | 228 +++++++++++++++++++++++ index.html | 3 + package-lock.json | 259 +++++++++++++++++++++++++++ package.json | 5 +- scripts/md-to-docx.cjs | 283 +++++++++++++++++++++++++++++ src/App.tsx | 125 +++++-------- src/index.css | 56 ++++++ src/main.tsx | 13 +- src/pages/LandingPage.tsx | 368 ++++++++++++++++++++++++++++++++++++++ 13 files changed, 1722 insertions(+), 84 deletions(-) create mode 100644 ARCHITECTURE.md create mode 100644 IMPLEMENTATION.md create mode 100644 MVP.md create mode 100644 REQUIREMENTS.docx create mode 100644 REQUIREMENTS.md create mode 100644 scripts/md-to-docx.cjs create mode 100644 src/pages/LandingPage.tsx diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..cac08e9 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,121 @@ +# BlockForecast — Architecture + +**Repo:** Everything runs from this single repository. There is **no other repo** for this app. + +**Make.com:** We expose an HTTP webhook that **Make.com** (or any automation tool) can call to push token data into BlockForecast. Make.com is not part of this repo—it’s an optional external service that sends POST requests to our server. If you don’t use Make.com, the app still works (tokens come from Birdeye + our scanner). + +--- + +## High-level architecture + +``` +┌─────────────────────────────────────────────────────────────────────────────────┐ +│ BROWSER │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │ +│ │ React (Vite) │ │ Phantom │ │ DexScreener │ │ Google Gemini (AI) │ │ +│ │ Frontend │ │ Wallet │ │ (iframe │ │ (client-side analyze │ │ +│ │ │ │ inject │ │ chart) │ │ + TTS voice alerts) │ │ +│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ └──────────┬─────────────┘ │ +│ │ │ │ │ │ +│ │ HTTP/WS │ sign tx │ embed only │ API key │ +└─────────┼─────────────────┼─────────────────┼─────────────────────┼───────────────┘ + │ │ │ │ + ▼ │ │ ▼ +┌──────────────────────────┴──────────────────────────────────────────────────────┐ +│ BLOCKFORECAST SERVER (Node.js / Express) │ +│ ┌─────────────────────────────────────────────────────────────────────────────┐ │ +│ │ REST API │ WebSocket │ Auth │ Risk engine │ Static (Vite/dist) │ │ +│ └─────────────────────────────────────────────────────────────────────────────┘ │ +│ │ │ │ │ +│ ▼ ▼ ▼ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ SQLite │ │ broadcast │ │ Gemini API │ │ +│ │ blockforecast│ │ TOKEN_UPDATE│ │ (server AI │ │ +│ │ .db │ │ to clients │ │ analyze) │ │ +│ └─────────────┘ └─────────────┘ └──────┬──────┘ │ +└─────────────────────────────────────────────────────────────┼────────────────────┘ + │ + ┌────────────────────────────────────────────────────┼────────────────────┐ + │ EXTERNAL SERVICES │ │ + ▼ ▼ ▼ ▼ ▼ +┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ +│ Birdeye │ │ Bitquery │ │ Helius RPC │ │ Google Gemini │ │ Make.com │ +│ new_listing + │ │ GraphQL │ │ (or Solana │ │ (AI analysis │ │ (optional) │ +│ price API │ │ Pump.fun bonding│ │ public RPC) │ │ server-side) │ │ POST webhook │ +│ │ │ transfers │ │ wallet balance │ │ │ │ /api/webhook/ │ +└─────────────────┘ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ make → server │ + └─────────────────┘ +``` + +--- + +## Mermaid diagram (for docs / Confluence / GitHub) + +Use this in any tool that renders Mermaid (e.g. GitHub, GitLab, Notion, Confluence). + +```mermaid +flowchart TB + subgraph Browser["Browser"] + FE[React Frontend\nVite] + Phantom[Phantom Wallet] + DexEmbed[DexScreener\niframe chart] + GeminiClient[Gemini client\nanalyze + TTS] + end + + subgraph BlockForecast["BlockForecast Server (Node.js)"] + API[REST API] + WS[WebSocket] + Auth[Auth] + Engine[Risk scoring engine] + Static[Static / Vite] + DB[(SQLite\nblockforecast.db)] + GeminiServer[Gemini API\nserver-side AI] + end + + subgraph External["External services"] + Birdeye[Birdeye\nnew_listing + price] + Bitquery[Bitquery\nPump.fun + transfers] + Helius[Helius RPC\nor Solana RPC] + GeminiAPI[Google Gemini] + Make[Make.com\noptional webhook] + end + + FE -->|HTTP / WS| API + FE -->|HTTP / WS| WS + Phantom -->|sign tx| FE + DexEmbed -->|embed only| FE + GeminiClient -->|API key| GeminiAPI + + API --> DB + API --> Auth + Engine --> DB + WS -->|TOKEN_UPDATE| FE + API --> GeminiServer + GeminiServer --> GeminiAPI + + BlockForecast -->|new listings, price| Birdeye + BlockForecast -->|bonding, transfers| Bitquery + BlockForecast -->|balance, RPC| Helius + Make -->|POST token payload| API +``` + +--- + +## Data flow (short) + +| Flow | Description | +|------|-------------| +| **Token pipeline** | Server calls Birdeye (new listings) + Bitquery (bonding, transfers) → runs risk engine → writes to SQLite → broadcasts `TOKEN_UPDATE` over WebSocket. Frontend gets feed via REST and live updates via WS. | +| **Make.com (optional)** | A Make.com scenario (or any HTTP client) POSTs a token payload to `POST /api/webhook/make`. Server merges with DB, runs risk engine, upserts token, broadcasts. No other repo; Make.com is external. | +| **Ingest** | Same as Make.com but generic: `POST /api/ingest` for internal or other tools to push token data. | +| **Auth** | Password hash in SQLite `config` table. (MVP will add wallet connect + password.) | +| **AI** | Server: `GET /api/ai/analyze/:mint` uses Gemini. Client: `gemini.ts` can call Gemini for richer analyze + voice alerts. | +| **Charts** | Frontend embeds DexScreener iframe (no backend call). | + +--- + +## Summary + +- **One repo:** This BlockForecast repo. No other repo for this app. +- **Make.com:** Optional. It’s an external automation service that can POST to our webhook (`/api/webhook/make`) to push token data in. We don’t host or clone Make.com. +- **External APIs we use:** Birdeye, Bitquery, Helius (or public Solana RPC), Google Gemini. DexScreener is embed-only in the frontend. diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md new file mode 100644 index 0000000..61107f2 --- /dev/null +++ b/IMPLEMENTATION.md @@ -0,0 +1,200 @@ +# BlockForecast — Current Implementation + +This document describes what the application does today: backend, frontend, data flow, and known gaps. + +--- + +## 1. Overview & architecture + +- **Single process:** One Node.js server (`server.ts`) runs the app. It serves both the API and the React frontend (Vite in dev, static `dist` in production). +- **Port:** Server listens on `http://localhost:3000` (or `0.0.0.0:3000`). +- **Stack:** Node.js, Express, SQLite (better-sqlite3), React, Vite, TypeScript, Tailwind CSS v4, Motion (animations), WebSockets (ws). + +--- + +## 2. Backend (`server.ts`) + +### 2.1 Database (SQLite — `blockforecast.db`) + +**Tables:** + +| Table | Purpose | +|-----------|---------| +| `config` | Key-value store. Used for a single key: `password` (bcrypt hash for app auth). | +| `tokens` | One row per token (mint = primary key). Holds rug/whale scores, list bucket, status, wow_signal, reason, market data, risk flags, AI analysis JSON, image_url, etc. | +| `wallets` | User-managed wallets: address, name, optional private_key, balance, isPhantom. | +| `trades` | Recorded trades: mint, wallet_address, type (BUY/SELL), amount, price, exit_price, pnl, jito_tip, status, timestamp. | + +On startup, the server runs a small migration that adds missing columns to `tokens` if they don’t exist (e.g. `sentiment_score`, `social_volume_24h`, `ai_analysis_json`, `image_url`, `fdv`, `price_usd`). + +### 2.2 Scoring engine (`calculateScores`) + +- **Input:** A flat object with token fields (e.g. from Birdeye or DB). +- **Output:** `rug_score` (0–100), `status`, `list_bucket`, `wow_signal`, `alert`, `rugged_at`, `hide_after`, `last_event_at`. + +**Rug logic:** + +- **Hard rug (score 100):** If any of `exit_trap_flag`, `fake_renounce_flag`, `dynamic_tax_trap_flag` is true → status "☠️ Rug", bucket "graveyard", alert, hide_after in 5 minutes. +- **Otherwise:** Score is built from: + - `mev_bundle_sniped_pct` > 30 → +35 + - `contract_upgradeable_flag` → +30 + - `wash_trading_ratio` > 50 → +25 + - `sybil_age_cluster_flag` → +20 + - `drip_dump_risk_score` > 60 → +20 + - `volume_integrity_score` < 40 → +15 + - `puppetmaster_index` > 75 → +25 + - `whale_score` ≥ 70 → +10, ≥ 85 → +15 + Then capped at 100. Status/bucket: ≥85 → Rug/graveyard; ≥50 → High Risk/live; else Safe or Whale Active/watchlist. +- **Wow signal:** One of "Stealth Distribution", "Upgradeable Detonator", "Artificial Volume", "Liquidity Drip Dump", "Puppetmaster Detected", or "None". + +### 2.3 Live streaming (scanner) + +- **Function:** `streamLiveTokens()`. +- **When:** Runs once on server start, then every **5 seconds** via `setInterval`. +- **Requires:** `BIRDEYE_API_KEY` in env. If missing, logs "Waiting for Birdeye API Key..." and returns. + +**Flow:** + +1. GET Birdeye trending: `https://public-api.birdeye.so/defi/token_trending?sort_by=rank&sort_type=asc&offset=0&limit=50` (Solana chain). +2. For each token: + - **Bucket:** liquidity < $10k → graveyard; volume24hUSD > $500k → live; else watchlist. + - **Whale score:** High volume → 75–100 random; else 0–40 random. + - Build token payload (mint, name, image_url, marketcap, fdv, price, volume, reason, etc.); some fields (e.g. smart_money_inflow, sentiment_score) are random placeholders. + - Run `calculateScores(tokenData)` and merge. Graveyard/live bucket can override scoring bucket. + - Send event to **Axiom** (if configured): `event_type: 'token_scanned'`. + - **Upsert** into `tokens` (INSERT or UPDATE by mint). + - **Broadcast** to all WebSocket clients: `{ type: 'TOKEN_UPDATE', data: updatedToken }`. +3. Flush Axiom. + +So the “backend scanner” is **Birdeye trending API + scoring + DB + WebSocket push**. No direct Solana trade/pool feed. + +### 2.4 WebSocket + +- **Upgrade:** Same HTTP server; upgrade on request (no path filter). +- **Usage:** Clients connect to `ws://host` (or `wss://` in production). Server keeps a set of clients and broadcasts with `broadcast(data)`. +- **Messages:** Only `TOKEN_UPDATE` is sent: `{ type: 'TOKEN_UPDATE', data: }` after each token upsert in `streamLiveTokens`. + +### 2.5 REST API + +| Method | Route | Description | +|--------|--------|-------------| +| GET | `/api/auth/status` | Returns `{ setup: boolean }` (whether a password exists in config). | +| POST | `/api/auth/setup` | Body: `{ password }`. Stores bcrypt hash in config. 400 if password already set. | +| POST | `/api/auth/login` | Body: `{ password }`. Returns `{ success: boolean }`. | +| GET | `/api/feed` | Query: `bucket` (optional). Returns tokens: all or filtered by `list_bucket`; for graveyard, only where `hide_after` is null or in the future. Sorted by `last_event_at` DESC, limit 100. | +| GET | `/api/token/:mint` | Returns one token row or 404. | +| GET | `/api/wallets` | Returns all wallets. For non-Phantom wallets, fetches live SOL balance from Solana (Helius RPC) and updates DB. | +| POST | `/api/wallets` | Body: address, name, optional private_key, isPhantom, balance. INSERT OR REPLACE. | +| POST | `/api/trade` | Body: mint, wallet_address, type (BUY/SELL), amount, jito_tip. Validates wallet exists; PnL is mock `(random - 0.4) * amount` for sells. Inserts into `trades`, logs to Axiom, returns success. | +| GET | `/api/stats` | Returns `{ total_trades, total_volume, recent_trades }` (recent_trades = last 50 trades). | +| POST | `/api/ai/save-analysis/:mint` | Body: `{ analysis }`. Saves JSON and timestamp on `tokens`. | +| GET | `/api/ai/analyze/:mint` | Returns cached AI analysis for mint if present and newer than 1 hour; else 404. | + +**Missing route:** There is **no** `GET /api/trades/:mint`. The frontend `TradeTerminal` calls it; the backend does not implement it (will 404). + +### 2.6 Observability + +- **Axiom:** Client initialized with `AXIOM_TOKEN` and optional `AXIOM_ORG_ID`. If token is missing, a mock token is used (events may not be sent). Events: token_scanned (during stream), trade_executed (on POST /api/trade). Dataset from `AXIOM_DATASET` or default `blockforecast`. + +### 2.7 Serving the frontend + +- **Development:** Vite middleware in middleware mode; all non-API requests go to Vite (SPA + HMR). +- **Production:** Serves `dist` with `express.static`; `GET *` falls back to `dist/index.html` for SPA routing. + +--- + +## 3. Frontend + +### 3.1 Entry and global state (`App.tsx`) + +- **Views:** `HOME` | `TERMINAL` | `PERFORMANCE`. Single view state; no URL routing. +- **Auth:** On load, GET `/api/auth/status` → `isAuthSetup`. Login/setup via POST to `/api/auth/login` or `/api/auth/setup`. On success, view switches to TERMINAL. Logout clears auth and goes to HOME. +- **Feed:** When view is TERMINAL, fetches `/api/feed?bucket=` and opens a WebSocket to the same host. On `TOKEN_UPDATE`, updates or inserts token in local state; if it’s the selected token, updates that too. Feed is refetched when bucket changes. +- **Bucket:** `live` | `watchlist` | `graveyard`; stored in state and used in Navbar and feed request. +- **Selected token / details:** Clicking a token sets `selectedToken` and opens the details sidebar. AI analysis is loaded when selection changes: first GET `/api/ai/analyze/:mint`; if 404, calls Gemini `analyzeToken` and POSTs to `/api/ai/save-analysis/:mint`. +- **Voice alerts:** Optional. When `tokens[0]` has `alert` true and voice is enabled, calls Gemini TTS and plays audio. +- **Performance:** When view is PERFORMANCE, GET `/api/stats` and pass `stats` to PerformanceView. + +### 3.2 Views + +| View | File | Description | +|------|------|-------------| +| **HOME** | `HomeView.tsx` | Landing: logo, tagline, feature pills. If not authenticated: password form (setup or login). If authenticated: “Launch Terminal” → switches to TERMINAL. | +| **TERMINAL** | Rendered in `App.tsx` | Navbar + optional wallet sidebar + TokenFeed + TokenDetailsSidebar + footer (network label, voice alerts toggle, TPS placeholder). | +| **PERFORMANCE** | `PerformanceView.tsx` | Nav bar (BlockForecast, live/watchlist/graveyard/Performance). Stats cards: Total Trades, Total Volume (from API), **Rugs Avoided** (hardcoded 14 / $1,240). Trade History table from `stats.recent_trades`. “Smart Money Leaderboard” is **static placeholder** list (Alpha_Sniper, etc.). | + +### 3.3 Components + +| Component | Role | +|-----------|------| +| **Navbar** | Logo → HOME; bucket tabs (live, watchlist, graveyard) + Performance; theme toggle; search (filters feed client-side); wallet toggle (mobile); logout. | +| **TokenFeed** | Lists tokens (filtered by search). Each row: image/icon, name, mint slice, Risk Score (rug_score), Smart Inflow, Insider %, StatusPill, wow_signal, time. Click → set selected token and show details. Refresh button refetches feed for current bucket. | +| **StatusPill** | Maps status string to pill style (Safe=green, High Risk=orange, Rug=red, Whale=blue). | +| **TokenDetailsSidebar** | When a token is selected: name, mint, StatusPill; Mkt Cap, Insider %; TradingViewChart; “BlockForecast Predictive Analysis” (Gemini: assessment, prediction, authenticity); NeuralMap; Sentiment / Smart Money bars; Top Holders (from token.holders or holders_json); PuppetmasterGraph; TradeTerminal; Intelligence Report (reason + upgradeable note); “View on Solscan” (no link). Empty state when no selection. | +| **WalletManager** | Lists wallets from GET `/api/wallets`. Generate new (Solana keypair via solana service, POST wallet); import by private key; connect Phantom (injected wallet). Select wallet for trading. | +| **TradeTerminal** | Amount, slippage, priority fee, Jito tip, TP/SL (SL tightens when rug_score > 50). Calls POST `/api/trade` and GET `/api/trades/:mint` (latter not implemented). Polls trades every 5s. | +| **TradingViewChart** | Receives `mint`; implementation not fully inspected here — used for price/chart in sidebar. | +| **NeuralMap** | Receives `fundingSourceJson`; used in sidebar. | +| **PuppetmasterGraph** | Receives `fundingSourceJson`; used in sidebar. | + +### 3.4 Services + +| Service | File | Description | +|---------|------|-------------| +| **Gemini** | `gemini.ts` | **analyzeToken(token):** Builds prompt with name, mint, rug/whale score; calls Gemini (model `gemini-3-flash-preview`) with Google Search tool; returns parsed JSON (assessment, prediction, authenticity). **generateVoiceAlert(text):** TTS with `gemini-2.5-flash-preview-tts`, voice Charon; returns base64 audio. Uses `GEMINI_API_KEY` or `VITE_GEMINI_API_KEY`. | +| **Solana** | `solana.ts` | Uses public RPC `https://api.mainnet-beta.solana.com`. **getBalance(address)**, **createWallet()** (keypair + bs58 private key), **deriveAddress(privateKey)**. Used by WalletManager for generate/import; backend uses Helius for wallet balance in `/api/wallets`. | + +### 3.5 Styling + +- **Tailwind v4** with `@tailwindcss/vite`. Theme in `index.css`: fonts (Inter, JetBrains Mono), CSS variables (bg, card-bg, border, text, accent, danger, success, warning, info). +- **Components:** `.luxury-card`, `.glass`, `.micro-label`, `.data-value`, `.status-pill`, `.scanline`, custom scrollbar. Dark-first; light mode toggled in Navbar. + +--- + +## 4. Environment variables + +| Variable | Used by | Purpose | +|----------|---------|---------| +| `GEMINI_API_KEY` | Server (Vite injects for client), gemini.ts | Gemini API for analyze + TTS. | +| `VITE_GEMINI_API_KEY` | Frontend (optional) | Override for Gemini in browser. | +| `BIRDEYE_API_KEY` | server.ts | Birdeye trending API; required for live stream. | +| `HELIUS_API_KEY` | server.ts | Solana RPC (Helius). If missing, public mainnet RPC. | +| `AXIOM_TOKEN` | server.ts | Axiom ingest. | +| `AXIOM_ORG_ID` | server.ts | Optional Axiom org. | +| `AXIOM_DATASET` | server.ts | Axiom dataset name (default `blockforecast`). | +| `APP_URL` | .env.example only | Not used in current code. | +| `NODE_ENV` | server.ts | Production vs dev (static vs Vite). | + +--- + +## 5. Data flow summary + +1. **Token pipeline:** Birdeye (trending) → `streamLiveTokens` → scoring → DB upsert → WebSocket broadcast → React state (feed + selected token). +2. **User flow:** HOME (auth) → TERMINAL (feed by bucket, search, select token) → sidebar (detail, AI analysis, trade panel). PERFORMANCE shows stats + trade history + placeholder leaderboard. +3. **Trading:** User picks wallet in WalletManager; in TradeTerminal submits BUY/SELL → POST `/api/trade` → trades table + Axiom. No on-chain execution; PnL is mock. + +--- + +## 6. Known gaps / notes + +- **GET /api/trades/:mint** is called by TradeTerminal but **not implemented** on the backend (404). Trade history in PerformanceView comes from `/api/stats` (all recent trades), not per-mint. +- **PerformanceView:** “Rugs Avoided” (14) and “Est. $1,240 Saved” are **hardcoded**. “Smart Money Leaderboard” is **static placeholder** data. +- **TPS** in the footer is static (2,450). +- **Solscan link** in the sidebar has no `href` (button only). +- **Whale score** in the live stream is partly random (75–100 or 0–40) based on Birdeye volume bucket; other risk flags in scoring are not populated from Birdeye (would need another data source). +- **Newly launched tokens:** Data source is Birdeye “trending,” not a dedicated “new launches” feed. +- **On-chain execution:** Trades are recorded in DB only; no Jupiter/Raydium or wallet signing. + +--- + +## 7. File map (implementation) + +| Area | Files | +|------|--------| +| Backend | `server.ts` | +| Frontend entry | `main.tsx`, `App.tsx`, `index.html` | +| Views | `src/views/HomeView.tsx`, `src/views/PerformanceView.tsx` | +| Components | `src/components/Navbar.tsx`, `TokenFeed.tsx`, `TokenDetailsSidebar.tsx`, `WalletManager.tsx`, `TradeTerminal.tsx`, `StatusPill.tsx`, `TradingViewChart.tsx`, `NeuralMap.tsx`, `PuppetmasterGraph.tsx` | +| Services | `src/services/gemini.ts`, `src/services/solana.ts` | +| Styles | `src/index.css`, `vite.config.ts` (Tailwind plugin) | +| Config | `package.json`, `tsconfig.json`, `.env` / `.env.example` | diff --git a/MVP.md b/MVP.md new file mode 100644 index 0000000..67f8461 --- /dev/null +++ b/MVP.md @@ -0,0 +1,145 @@ +# BlockForecast — MVP Scope + +**Purpose:** Align on what we’re building before development. For a semi-technical reviewer. No API details here—just product scope, flows, and how risk scoring works. + +--- + +## 1. What We’re Building + +BlockForecast lets users **discover** Solana tokens, **see risk** at a glance, **get AI insights** on authenticity and outlook, and **trade** in Demo (fake balance) or Live (real wallet) mode. + +The MVP = a proper **landing page**, **wallet + password** access, and the **terminal + trading** experience. + +--- + +## 2. Who It’s For + +Traders and analysts on Solana who want one place to discover, assess, and trade—and who prefer **wallet + password** over email signup. + +--- + +## 3. Pages in the MVP + +The app will have three main surfaces: + +| Page | What it is | +|------|------------| +| **Landing (Home)** | Public face: hero, value prop, feature highlights, CTA to connect. Not just a password box. | +| **Terminal** | Main workspace: token feed (Live / Watchlist / Graveyard), token detail sidebar (chart, AI, holders, trade panel), wallet sidebar. Nav to Performance and logout. | +| **Performance** | User’s stats (trades, volume, rugs avoided), trade history table, optional Smart Money / leaderboard area. | + +After login, user moves between **Terminal** and **Performance** via nav. Logout sends them back to **Landing**. + +--- + +## 4. Landing Page + +First screen = a real landing: + +- **Hero:** Headline + subhead + primary CTA (“Connect Wallet & Enter” or similar). +- **Value:** Short bullets—real-time feed, risk scores, AI analysis, Demo & Live trading. +- **How it works (optional):** Connect wallet → Set password → Browse feed → Trade. +- **Feature highlights:** Risk at a glance, AI authenticity, Demo/Live, Performance. Icons or short copy. +- **Footer:** Terms, Privacy, links, copyright. + +Entry to the app is only via this page’s CTA → wallet connect + password flow. + +--- + +## 5. Login, Access & Logout + +**First time:** CTA → connect wallet → **set password** (tied to that wallet) → into Terminal. + +**Returning:** CTA → connect wallet → **enter password** → into Terminal. + +**Logout:** Clear session, return to **Landing**. No standalone password-only screen as the main entry. + +--- + +## 6. Terminal (Core Experience) + +**Feed:** Tokens by bucket (Live / Watchlist / Graveyard). Each row: image, name, mint snippet, risk score, status (Safe / High Risk / Rug / Whale Active), smart money, insider %, wow signal, time. Search, refresh. Click row → open detail. + +**Token detail (sidebar):** Name, mint (copy), status pill. Market cap, price, insider %. Price chart. AI block: assessment bullets, prediction (Bullish/Bearish/Rug), authenticity (website, X, CA posted, verified, summary). NeuralMap + Puppetmaster if in scope. Sentiment & smart money bars. Top holders. **Trade panel** (amount, slippage, Jito, TP/SL, Buy/Sell—behavior depends on Demo vs Live). Short intelligence report + upgradeable warning. “View on Solscan” link. + +**Wallets:** List, add (generate / import key / connect Phantom), select active. Demo = simulated balance; Live = real SOL. + +--- + +## 7. Demo vs Live Trading + +**Demo:** Simulated balance and execution; no on-chain tx. Clearly labeled “Demo” (badge/toggle). Trades recorded in-app only. + +**Live:** Real wallet, real SOL, real swap (user signs in wallet). Clearly labeled “Live”. Same trade panel, different behavior. + +Terminal has a **Demo / Live** selector so the mode is always obvious. + +--- + +## 8. Performance View + +Summary cards: total trades, total volume (SOL), rugs avoided (and optional “Est. $ saved”). Trade history table: asset, type, amount, time (e.g. last 50). Optional: Smart Money / leaderboard (can be placeholder). + +--- + +## 9. Risk Scoring (Weightage) + +Every token gets a **rug score** (0–100) and a **status** (Safe / High Risk / Rug / Whale Active). The UI also shows a **wow signal**—the main risk factor that drove the score. + +**Hard overrides (instant rug = 100)** +If any of these are true, the score is set to **100**, status = **Rug**, bucket = **Graveyard**, wow = **Exit Trap Risk**: + +- Exit trap flag, or liquidity < $10k +- Fake renounce flag +- Dynamic tax trap flag + +No other factors are added in that case. + +**Weighted scoring (when none of the above)** +Otherwise we add points for each condition below. The first condition that applies also sets the **wow signal** label. Total is capped at 100. + +| Condition | Points | Wow signal (if first) | +|-----------|--------|------------------------| +| MEV bundle sniped % > 30 | +35 | Stealth Distribution | +| Contract upgradeable | +30 | Upgradeable Detonator | +| Wash trading ratio > 50 | +25 | Artificial Volume | +| Drip dump risk score > 60 | +20 | Liquidity Drip Dump | +| Sybil / age cluster flag | +20 | Puppetmaster Detected | +| Volume integrity score < 40 | +15 | — | +| Puppetmaster index > 75 | +25 | — | +| Whale score ≥ 85 | +15 | — | +| Whale score ≥ 70 (and < 85) | +10 | — | + +**Status from score** +- Score ≥ 85 → **Rug** +- Score ≥ 50 → **High Risk** +- Else, whale score ≥ 50 → **Whale Active** +- Else → **Safe** + +**Bucket (feed grouping)** +- Score ≥ 85 → **Graveyard** +- Score ≥ 50 and wow ≠ None → **Watchlist** +- Else → **Live** + +--- + +## 10. Out of Scope for This Doc + +- API or endpoints +- Infrastructure and deployment + +--- + +## 11. Checklist for Review + +| Area | In MVP | +|------|--------| +| Landing page (hero, value, CTA) | ✓ | +| Access (wallet + password; logout → landing) | ✓ | +| Terminal (feed, detail, wallets, trade panel) | ✓ | +| Wallets (list, add, select) | ✓ | +| Demo & Live trading (clearly labeled) | ✓ | +| Performance (stats, history, optional leaderboard) | ✓ | +| Risk scoring (weightage as above) | ✓ | + +If you want to add, drop, or change anything (e.g. auth flow, landing sections, scoring weights, or what’s in the token detail), we update this and then start build. diff --git a/REQUIREMENTS.docx b/REQUIREMENTS.docx new file mode 100644 index 0000000000000000000000000000000000000000..63b5532a02bc5dde13a2c389f7c62d0279a0087d GIT binary patch literal 15868 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 50). +- **Quick amount buttons:** e.g. 0.1, 0.5, 1 SOL. +- **Actions:** + - **Buy** and **Sell** buttons. + - In **Demo:** backend records the trade only; update local state; no signing. + - In **Live:** build swap (e.g. Jupiter quote + swap); request wallet signature; on success, backend records trade and updates stats. +- **Validation:** Require wallet selected; in Live, require sufficient balance and network. +- **Recent activity:** List of recent trades for **this token** (and optionally current wallet); poll or WebSocket. +- **Jito badge:** Show “JITO ENABLED” or similar when Jito tip is used (e.g. live only). + +### 3.6 AI Analysis + +- **Trigger:** When user selects a token, fetch AI analysis (cached from backend if available and fresh). +- **If cache miss:** Call backend or client-side Gemini (per product setup); persist result via backend. +- **Response shape:** Assessment (array of strings), prediction (string), authenticity (website, twitter, is_authentic, ca_posted, social_summary). +- **UI:** Loading state, error state with retry, display assessment and prediction; authenticity block when present. + +### 3.7 Voice Alerts (Optional) + +- **Toggle:** “Neural Voice Alerts” in footer or settings (on/off). +- **Trigger:** When a new high-risk token appears (e.g. alert flag true) and voice is on, play TTS (e.g. “New high risk token detected: …”). +- **Implementation:** Backend or client TTS (e.g. Gemini TTS); play in browser. + +### 3.8 Performance View + +- **Stats cards:** + - Total Trades + - Total Volume (SOL) + - Rugs Avoided (count and optional “Est. $ saved”) +- **Trade history table:** Columns: Asset (mint truncated), Type (BUY/SELL), Amount (SOL), Time; last 50 trades (or paginated). +- **Smart Money Leaderboard:** Section for top performers or signals; can be placeholder (static list) or wired to real data later. + +### 3.9 Real-Time Updates + +- **WebSocket:** Connect to same host as API (`ws://` or `wss://`). +- **Message type:** `TOKEN_UPDATE` with full token object. +- **Behavior:** On message, update feed list (upsert by mint); if current selected token is updated, refresh sidebar. +- **Reconnect:** Handle disconnect and reconnect gracefully. + +### 3.10 Sync / Ingest (Optional for Frontend) + +- **Sync real-world data:** Button or trigger that calls backend “sync” (e.g. from DexScreener or external source); on success, refetch feed. +- **Simulate ingest:** Optional dev/QA button to send a test token to the backend for demos. + +--- + +## 4. Data Models (Reference) + +### 4.1 Token (feed and detail) + +- `mint` (string, primary key) +- `token_name`, `image_url` +- `rug_score`, `whale_score` (0–100) +- `status`, `list_bucket`, `wow_signal`, `reason` +- `marketcap_usd`, `price_usd`, `fdv` +- `smart_money_inflow`, `insider_holding_pct`, `sentiment_score`, `social_volume_24h`, `smart_money_buy_count` +- `bonding_progress`, `coin_age_min` +- `contract_upgradeable_flag`, `alert` +- `funding_source_json`, `holders_json` (or `holders` array) +- `last_event_at`, `ai_analysis_json`, `ai_analysis_at` + +### 4.2 Wallet + +- `address`, `name`, `private_key` (optional, never expose to UI except “reveal”), `balance`, `isPhantom` + +### 4.3 Trade + +- `id`, `mint`, `wallet_address`, `type` (BUY/SELL), `amount`, `price`, `exit_price`, `pnl`, `jito_tip`, `status`, `timestamp` +- Optional: `demo: boolean` or inferred from wallet/session. + +--- + +## 5. Non-Functional Requirements + +- **Responsive:** Usable on desktop and tablet; main layout: feed + sidebar(s); mobile: collapsible sidebars, same features. +- **Theme:** Dark mode default; light mode toggle; persist preference if possible. +- **Accessibility:** Semantic HTML, labels, keyboard navigation where applicable. +- **Performance:** Lazy-load heavy components (e.g. charts); virtualize long feed lists if needed. +- **Security:** Never log or expose private keys; in Live mode, signing only in wallet (e.g. Phantom or injected); backend must not store raw keys in logs. + +--- + +## 6. Environment and Configuration + +Frontend may need (or backend may inject): + +- `VITE_GEMINI_API_KEY` or equivalent for client-side AI (if used). +- API base URL (same origin is default). +- Optional: feature flags for “Demo only” deployment (hide Live mode). + +Backend configuration (env) is out of scope for this document. + +--- + +## 7. Summary: Demo vs Live Checklist + +- **Demo trading:** Simulated balance, simulated execution, DB-only trades, clear “Demo” labeling, no wallet signing. +- **Live trading:** Real wallet (Phantom/imported), real balance, real DEX execution (e.g. Jupiter), backend records real trades; clear “Live” labeling and risk disclaimers. +- **Single UI:** Same Trade Terminal component; behavior and API calls differ by mode; backend may use a `demo` flag or wallet type to decide execution path. + +This document defines the **requirements of the entire BlockForecast application** for use when building or rebuilding the frontend, including all features and demo and live trading. diff --git a/index.html b/index.html index d8cc7d2..0e551df 100644 --- a/index.html +++ b/index.html @@ -4,6 +4,9 @@ BlockForecast + + +
diff --git a/package-lock.json b/package-lock.json index 1905b66..ef58c45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,6 +31,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-force-graph-2d": "^1.29.1", + "react-router-dom": "^7.13.1", "recharts": "^3.7.0", "tailwind-merge": "^3.5.0", "vite": "^6.2.0", @@ -40,6 +41,7 @@ "@types/express": "^4.17.21", "@types/node": "^22.14.0", "autoprefixer": "^10.4.21", + "docx": "^9.6.0", "tailwindcss": "^4.1.14", "tsx": "^4.21.0", "typescript": "~5.8.2", @@ -3002,6 +3004,13 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "license": "MIT" + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -3562,6 +3571,60 @@ "node": ">=8" } }, + "node_modules/docx": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/docx/-/docx-9.6.0.tgz", + "integrity": "sha512-y6EaJJMDvt4P7wgGQB9KsZf4wsRkQMJfkc9LlNufRshggI5BT35hGNkXBCAeEoI3MLMwApKguxzjdqqVcBCqNA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "^25.2.3", + "hash.js": "^1.1.7", + "jszip": "^3.10.1", + "nanoid": "^5.1.3", + "xml": "^1.0.1", + "xml-js": "^1.6.8" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/docx/node_modules/@types/node": { + "version": "25.3.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.2.tgz", + "integrity": "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/docx/node_modules/nanoid": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.6.tgz", + "integrity": "sha512-c7+7RQ+dMB5dPwwCp4ee1/iV/q2P6aK1mTZcfr1BTuVlyW9hJYiMPybJCcnBlQtuSmTIWNeazm/zqNoZSSElBg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.js" + }, + "engines": { + "node": "^18 || >=20" + } + }, + "node_modules/docx/node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, "node_modules/dotenv": { "version": "17.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz", @@ -4296,6 +4359,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -4382,6 +4456,13 @@ ], "license": "BSD-3-Clause" }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true, + "license": "MIT" + }, "node_modules/immer": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", @@ -4440,6 +4521,13 @@ "node": ">=8" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -4592,6 +4680,52 @@ "node": ">=6" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "license": "(MIT OR GPL-3.0-or-later)", + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, + "node_modules/jszip/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/jszip/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/jszip/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, "node_modules/jwa": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", @@ -4625,6 +4759,16 @@ "node": ">=12" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/lightningcss": { "version": "1.31.1", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.31.1.tgz", @@ -5006,6 +5150,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "dev": true, + "license": "ISC" + }, "node_modules/minimatch": { "version": "9.0.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", @@ -5266,6 +5417,13 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true, + "license": "(MIT AND Zlib)" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -5402,6 +5560,13 @@ "node": ">=10" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -5612,6 +5777,57 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", + "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", + "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", + "license": "MIT", + "dependencies": { + "react-router": "7.13.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -5849,6 +6065,16 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/sax": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz", + "integrity": "sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/scheduler": { "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", @@ -5918,6 +6144,19 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, + "node_modules/setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", + "dev": true, + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -7211,6 +7450,26 @@ } } }, + "node_modules/xml": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz", + "integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==", + "dev": true, + "license": "MIT" + }, + "node_modules/xml-js": { + "version": "1.6.11", + "resolved": "https://registry.npmjs.org/xml-js/-/xml-js-1.6.11.tgz", + "integrity": "sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "sax": "^1.2.4" + }, + "bin": { + "xml-js": "bin/cli.js" + } + }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/package.json b/package.json index affdf7e..765ad1a 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "preview": "vite preview", "clean": "rm -rf dist", "lint": "tsc --noEmit", - "start": "node server.ts" + "start": "node server.ts", + "docx": "node scripts/md-to-docx.cjs" }, "dependencies": { "@google/genai": "^1.29.0", @@ -35,6 +36,7 @@ "react": "^19.0.0", "react-dom": "^19.0.0", "react-force-graph-2d": "^1.29.1", + "react-router-dom": "^7.13.1", "recharts": "^3.7.0", "tailwind-merge": "^3.5.0", "vite": "^6.2.0", @@ -44,6 +46,7 @@ "@types/express": "^4.17.21", "@types/node": "^22.14.0", "autoprefixer": "^10.4.21", + "docx": "^9.6.0", "tailwindcss": "^4.1.14", "tsx": "^4.21.0", "typescript": "~5.8.2", diff --git a/scripts/md-to-docx.cjs b/scripts/md-to-docx.cjs new file mode 100644 index 0000000..f11e875 --- /dev/null +++ b/scripts/md-to-docx.cjs @@ -0,0 +1,283 @@ +/** + * Converts REQUIREMENTS.md to REQUIREMENTS.docx with neutral styling (black text, no blue headings). + * Run: node scripts/md-to-docx.js + */ +const fs = require("fs"); +const path = require("path"); +const { + Document, + Packer, + Paragraph, + TextRun, + Table, + TableRow, + TableCell, + WidthType, + BorderStyle, + AlignmentType, +} = require("docx"); + +const BLACK = "000000"; +const DARK_GRAY = "404040"; + +function heading1(text) { + return new Paragraph({ + children: [new TextRun({ text, bold: true, size: 28, color: BLACK })], + spacing: { before: 320, after: 160 }, + }); +} + +function heading2(text) { + return new Paragraph({ + children: [new TextRun({ text, bold: true, size: 24, color: BLACK })], + spacing: { before: 240, after: 120 }, + }); +} + +function heading3(text) { + return new Paragraph({ + children: [new TextRun({ text, bold: true, size: 22, color: DARK_GRAY })], + spacing: { before: 200, after: 100 }, + }); +} + +function body(text, options = {}) { + const runs = parseInline(text); + return new Paragraph({ + children: runs.length ? runs : [new TextRun({ text, size: 22 })], + spacing: { after: 80 }, + ...options, + }); +} + +function parseInline(line) { + const runs = []; + let rest = line; + while (rest.length) { + const boldMatch = rest.match(/\*\*(.+?)\*\*/); + const codeMatch = rest.match(/`([^`]+)`/); + let match = boldMatch; + let type = "bold"; + if (codeMatch && (!boldMatch || codeMatch.index < boldMatch.index)) { + match = codeMatch; + type = "code"; + } + if (!match) { + if (rest.length) runs.push(new TextRun({ text: rest, size: 22, color: BLACK })); + break; + } + if (match.index > 0) { + runs.push(new TextRun({ text: rest.slice(0, match.index), size: 22, color: BLACK })); + } + if (type === "bold") { + runs.push(new TextRun({ text: match[1], bold: true, size: 22, color: BLACK })); + } else { + runs.push(new TextRun({ text: match[1], size: 22, color: DARK_GRAY })); + } + rest = rest.slice(match.index + match[0].length); + } + return runs; +} + +function bullet(text) { + const raw = text.replace(/^[-*]\s+/, ""); + const runs = parseInline(raw); + const content = runs.length ? runs : [new TextRun({ text: raw, size: 22, color: BLACK })]; + return new Paragraph({ + children: [new TextRun({ text: "• ", size: 22, color: BLACK }), ...content], + indent: { left: 360 }, + spacing: { after: 60 }, + }); +} + +function numberListItem(text) { + return new Paragraph({ + children: [new TextRun({ text, size: 22, color: BLACK })], + indent: { left: 360 }, + spacing: { after: 60 }, + }); +} + +function tableFromMarkdown(lines) { + const rows = []; + const alignSep = lines[1]; // |---|---| + const dataLines = lines.filter((l, i) => i !== 1 && l.trim().startsWith("|")); + for (const line of dataLines) { + const cells = line + .split("|") + .map((c) => c.trim()) + .filter((_, i, arr) => i > 0 && i < arr.length - 1); + rows.push( + new TableRow({ + children: cells.map( + (c) => + new TableCell({ + children: [ + new Paragraph({ + children: [new TextRun({ text: c, size: 20, color: BLACK })], + }), + ], + width: { size: 100 / cells.length, type: WidthType.PERCENTAGE }, + }) + ), + }) + ); + } + return new Table({ + width: { size: 100, type: WidthType.PERCENTAGE }, + borders: { + top: { style: BorderStyle.SINGLE, size: 4, color: "CCCCCC" }, + bottom: { style: BorderStyle.SINGLE, size: 4, color: "CCCCCC" }, + left: { style: BorderStyle.SINGLE, size: 4, color: "CCCCCC" }, + right: { style: BorderStyle.SINGLE, size: 4, color: "CCCCCC" }, + }, + rows, + }); +} + +function parseMd(content) { + const blocks = []; + const lines = content.split(/\r?\n/); + let i = 0; + let currentList = []; + let currentTable = []; + + function flushList() { + if (currentList.length) { + blocks.push({ type: "list", items: currentList }); + currentList = []; + } + } + function flushTable() { + if (currentTable.length) { + blocks.push({ type: "table", lines: currentTable }); + currentTable = []; + } + } + + while (i < lines.length) { + const line = lines[i]; + const trimmed = line.trim(); + + if (trimmed.startsWith("## ")) { + flushList(); + flushTable(); + blocks.push({ type: "h1", text: trimmed.slice(3).trim() }); + i++; + continue; + } + if (trimmed.startsWith("### ")) { + flushList(); + flushTable(); + blocks.push({ type: "h2", text: trimmed.slice(4).trim() }); + i++; + continue; + } + if (trimmed === "---") { + flushList(); + flushTable(); + blocks.push({ type: "hr" }); + i++; + continue; + } + if (trimmed.match(/^\|.+\|$/)) { + flushList(); + currentTable.push(line); + i++; + continue; + } + if (trimmed.startsWith("- ") || trimmed.startsWith("* ")) { + flushTable(); + currentList.push({ type: "bullet", text: trimmed }); + i++; + continue; + } + if (trimmed.match(/^\d+\.\s/)) { + flushTable(); + currentList.push({ type: "number", text: trimmed }); + i++; + continue; + } + if (trimmed.startsWith(" - ")) { + flushTable(); + currentList.push({ type: "bullet", level: 1, text: trimmed.trim() }); + i++; + continue; + } + + flushList(); + flushTable(); + if (trimmed) { + blocks.push({ type: "p", text: trimmed }); + } else { + blocks.push({ type: "spacing" }); + } + i++; + } + flushList(); + flushTable(); + return blocks; +} + +function buildDoc(blocks) { + const children = []; + for (const b of blocks) { + if (b.type === "h1") children.push(heading1(b.text)); + else if (b.type === "h2") children.push(heading2(b.text)); + else if (b.type === "hr") children.push(new Paragraph({ text: "", spacing: { after: 120 } })); + else if (b.type === "p") children.push(body(b.text)); + else if (b.type === "spacing") children.push(new Paragraph({ text: "", spacing: { after: 60 } })); + else if (b.type === "list") { + for (const it of b.items) { + if (it.type === "bullet") children.push(bullet(it.text)); + else if (it.type === "number") children.push(numberListItem(it.text)); + } + } else if (b.type === "table") { + children.push(tableFromMarkdown(b.lines)); + children.push(new Paragraph({ text: "", spacing: { after: 160 } })); + } + } + return children; +} + +async function main() { + const mdPath = path.join(__dirname, "..", "REQUIREMENTS.md"); + const outPath = path.join(__dirname, "..", "REQUIREMENTS.docx"); + const content = fs.readFileSync(mdPath, "utf8"); + const blocks = parseMd(content); + const children = buildDoc(blocks); + + const doc = new Document({ + styles: { + paragraphStyles: [ + { + id: "Normal", + name: "Normal", + run: { size: 22, color: BLACK }, + }, + ], + }, + sections: [ + { + properties: {}, + children: [ + new Paragraph({ + children: [new TextRun({ text: "BlockForecast — Product Requirements Document", bold: true, size: 32, color: BLACK })], + alignment: AlignmentType.CENTER, + spacing: { after: 240 }, + }), + ...children, + ], + }, + ], + }); + + const buf = await Packer.toBuffer(doc); + fs.writeFileSync(outPath, buf); + console.log("Wrote", outPath); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/src/App.tsx b/src/App.tsx index 2270d74..89b432c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,24 +1,20 @@ import React, { useState, useEffect } from 'react'; +import { useNavigate } from 'react-router-dom'; import { Activity, Shield, AlertTriangle, - Skull, Search, ExternalLink, - Info, RefreshCw, PlusCircle, - TrendingUp, ShieldCheck, Zap, - LayoutGrid, - List, ChevronRight, - ArrowUpRight, Wallet as WalletIcon, BarChart3, - LogOut + LogOut, + ArrowLeft } from 'lucide-react'; import { motion, AnimatePresence } from 'motion/react'; import { WalletManager, WalletType } from './components/WalletManager'; @@ -71,7 +67,8 @@ import { NeuralMap } from './components/NeuralMap'; import { analyzeToken, generateVoiceAlert } from './services/gemini'; export default function App() { - const [view, setView] = useState<'HOME' | 'TERMINAL' | 'PERFORMANCE'>('HOME'); + const navigate = useNavigate(); + const [view, setView] = useState<'TERMINAL' | 'PERFORMANCE'>('TERMINAL'); const [tokens, setTokens] = useState([]); const [bucket, setBucket] = useState('live'); const [loading, setLoading] = useState(true); @@ -124,6 +121,7 @@ export default function App() { setIsAuthenticated(true); setIsAuthSetup(true); setView('TERMINAL'); + setPasswordInput(''); } else { setAuthError(isAuthSetup ? 'Invalid password' : 'Setup failed'); } @@ -134,8 +132,8 @@ export default function App() { const handleLogout = () => { setIsAuthenticated(false); - setView('HOME'); setPasswordInput(''); + navigate('/'); }; const fetchStats = async () => { @@ -340,84 +338,51 @@ export default function App() { fetchWallets(); }, [walletRefreshTrigger]); - if (view === 'HOME') { + if (!isAuthenticated) { return (
-
- - navigate('/')} + className="absolute top-6 left-6 z-20 flex items-center gap-2 text-zinc-500 hover:text-white text-xs font-mono uppercase tracking-widest transition-colors" + > + + Back + + -
-
- +
+
+

+ {isAuthSetup ? 'Terminal access' : 'Set password'} +

+

+ {isAuthSetup ? 'Enter your password to continue' : 'Create a password for this terminal'} +

-

BlockForecast Pro

-

Production-Grade On-Chain Intelligence

-
- - {!isAuthenticated ? ( - -
-

- {isAuthSetup ? 'Terminal Access' : 'Initial Security Setup'} -

-

- {isAuthSetup ? 'Enter encrypted password to proceed' : 'Create a master password for this terminal'} -

+
+
+ + setPasswordInput(e.target.value)} + autoFocus + />
- - -
- - setPasswordInput(e.target.value)} - autoFocus - /> -
- {authError &&

{authError}

} - -
- - ) : ( -
- -

Session Active • Solana Mainnet

-
- )} - -
- {[ - { label: 'Real-Time Risk', icon: Shield }, - { label: 'Whale Tracking', icon: Activity }, - { label: 'Instant Execution', icon: Zap }, - ].map((f, i) => ( -
- - {f.label} -
- ))} +
@@ -430,7 +395,7 @@ export default function App() {