# Aitherboard A dockerized 4chan-style imageboard built on Nostr protocol. Aitherboard is a single-page application (SPA) that provides a decentralized discussion board experience with real-time updates, multiple authentication methods, and full Nostr protocol integration. ## Table of Contents - [Overview](#overview) - [Technology Stack](#technology-stack) - [Architecture](#architecture) - [Features](#features) - [Authentication](#authentication) - [Content Types](#content-types) - [Configuration](#configuration) - [Docker Deployment](#docker-deployment) - [Health Check](#health-check) - [Development](#development) - [Implementation Phases](#implementation-phases) ## Overview Aitherboard is a decentralized imageboard that uses the Nostr protocol for content distribution. It supports: - **Kind 11 threads** (NIP-7D) - Discussion threads with topics - **Kind 1111 comments** (NIP-22) - Threaded comments - **Kind 9735 zap receipts** (NIP-57) - Lightning payments displayed as comments - **Kind 1 feed** - Twitter-like feed with replies and zaps - **Kind 7 reactions** - Upvote/downvote system - **Real-time updates** - Live feed of new content - **Multiple auth methods** - NIP-07, nsec, NIP-46 bunker, anonymous - **Full markdown rendering** - All content supports markdown - **NIP-21 link parsing** - Clickable profile badges and event cards ## Technology Stack - **Frontend**: Svelte 5 (with runes: `$state`, `$derived`, `$effect`) + TypeScript + Vite - **Styling**: Tailwind CSS (4chan-style minimal design) - **Nostr Library**: [`applesauce-core`](https://github.com/hzrd149/applesauce) - **Markdown**: `marked` + `DOMPurify` for sanitization - **Storage**: IndexedDB (via `idb` or `localforage`) - **Deployment**: Docker + Apache httpd (static serving on port 9876) - **PWA**: Progressive Web App support ## Architecture ### Architecture Layers ``` ┌─────────────────────────────────────────────────────────┐ │ UI Components Layer │ │ (LandingPage, ThreadList, ThreadView, Profile, Feed) │ └───────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────▼─────────────────────────────────┐ │ Feature Modules Layer │ │ (Threads, Comments, Zaps, Reactions, Profiles, Feed) │ └───────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────▼─────────────────────────────────┐ │ Core Services Layer │ │ (Nostr Client, Relay Pool, Auth, Cache, Security) │ └───────────────────────┬─────────────────────────────────┘ │ ┌───────────────────────▼─────────────────────────────────┐ │ Applesauce Library │ │ (Nostr Protocol Implementation) │ └─────────────────────────────────────────────────────────┘ ``` ### Core Services Layer #### 1. Nostr Client Service (`src/lib/services/nostr/`) - `applesauce-client.ts` - Applesauce client initialization - `relay-pool.ts` - Relay connection management - `event-store.ts` - In-memory event cache with deduplication - `subscription-manager.ts` - Active subscription tracking - `auth-handler.ts` - NIP-42 AUTH challenge handling - `config.ts` - Centralized configuration with env var validation #### 2. Authentication Service (`src/lib/services/auth/`) - `nip07-signer.ts` - NIP-07 browser extension signer - `nsec-signer.ts` - Direct nsec/hex key signer (with NIP-49 encryption) - `bunker-signer.ts` - NIP-46 remote signer (bunker) - `anonymous-signer.ts` - Anonymous session key generation - `session-manager.ts` - Session persistence and key management - `profile-fetcher.ts` - Kind 0 metadata fetching #### 3. Cache Service (`src/lib/services/cache/`) - `indexeddb-store.ts` - IndexedDB wrapper - `event-cache.ts` - Event caching and retrieval - `profile-cache.ts` - Profile metadata caching - `search-index.ts` - Full-text search index #### 4. Security Utilities (`src/lib/services/security/`) - `key-management.ts` - Key encryption/decryption (NIP-49) - `bech32-utils.ts` - NIP-19 bech32 encoding/decoding - `event-validator.ts` - Event signature and structure validation - `sanitizer.ts` - HTML/content sanitization ### Feature Modules Layer #### 5. Thread Module (`src/lib/modules/threads/`) **Components**: - `ThreadList.svelte` - Main thread listing - `ThreadCard.svelte` - Thread preview card (landing page) - `ThreadView.svelte` - Full thread display - `CreateThreadForm.svelte` - Thread creation **Features**: - Topic organization (max 3 `t` tags per thread) - "General" category for threads without topics - 30-day timeout (configurable) - Thread statistics (comments, zaps, activity) - Sorting: newest, most active, most upvoted - Plaintext preview extraction for landing page (no markdown/images) **Landing Page Thread Display**: - Title - Relative time of thread creation - Relative time of latest response - Profile badge - Vote stats - First 250 chars of plaintext (no markdown/images) #### 6. Comment Module (`src/lib/modules/comments/`) **Components**: - `Comment.svelte` - Individual comment - `CommentThread.svelte` - Threaded comment list - `CommentForm.svelte` - Reply form **Threading Style**: - Flat list (no indentation) - Gray blurb showing parent preview - Click blurb to highlight/scroll to parent - Parse NIP-22 tags: `K`, `E`, `e`, `k`, `p`, `P` #### 7. Zap Module (`src/lib/modules/zaps/`) **Components**: - `ZapButton.svelte` - Zap action button - `ZapReceipt.svelte` - Zap receipt display **Features**: - Create kind 9734 zap requests - Send to recipient's lnurl callback (wallet) - Display kind 9735 zap receipts (≥ configured threshold) - Zap receipts displayed with ⚡ emoji - Configurable minimum sat threshold (default: 1, must be 0 or positive) **Important**: Zap requests (9734) go to wallets, NOT relays. Only receipts (9735) are published to relays. #### 8. Reaction Module (`src/lib/modules/reactions/`) **Components**: - `ReactionButtons.svelte` - Upvote/downvote buttons **Rules**: - Only `+` and `-` content allowed - One vote per user per event - Clicking same button twice deletes the reaction #### 9. Profile Module (`src/lib/modules/profiles/`) **Components**: - `ProfilePage.svelte` - User profile display - `ProfileBadge.svelte` - Clickable profile badge - `RecentlyActive.svelte` - Recently active users list **Features**: - Display kind 0 metadata (name, about, picture, NIP-05) - Show kind 1 feed from default relays + `wss://relay.damus.io` + `wss://aggr.nostr.land` - Allow zapping profile owner - Allow replying to kind 1 notes with kind 1 replies (NIP-10) or zaps - Recently active users tracking (15min/1hr/24hr windows) #### 10. Kind 1 Feed Module (`src/lib/modules/feed/`) **Components**: - `Kind1FeedPage.svelte` - Main feed page - `Kind1Post.svelte` - Individual kind 1 post - `Kind1Reply.svelte` - Kind 1 reply display - `ZapReceiptReply.svelte` - Zap receipt as reply (with ⚡) - `CreateKind1Form.svelte` - Create new kind 1 events - `ReplyToKind1Form.svelte` - Reply to kind 1 events **Features**: - **"View feed" button** on landing page opens feed page - Fetch kind 1 events from default relays - **Create new kind 1 events** with markdown editor - **Reply to kind 1 with kind 1** (NIP-10 threading) - **Reply to kind 1 with zap** (display receipt as reply with ⚡ emoji) - **Reply to kind 1 replies** with their own kind 1 - **Reply to zap receipts** with: - Zap receipts (zap the zapper) - Kind 1111 comments - All content rendered as markdown - Flat threading display (no indentation, similar to comment threading) - Real-time updates - Infinite scroll **Interaction Flow**: 1. User clicks "View feed" → Opens feed page at `/feed` 2. Feed displays kind 1 events from default relays 3. User can create new kind 1 events 4. User can reply to kind 1 events with kind 1 or zap 5. Zap receipts displayed as replies with ⚡ emoji 6. User can reply to kind 1 replies with kind 1 7. User can reply to zap receipts with zap receipts or kind 1111 comments ## Features ### Authentication Methods 1. **NIP-07 Extension**: Browser extension (e.g., Alby, nos2x) 2. **Nsec Login**: Direct bech32 nsec or hex private key - Optional NIP-49 encryption (ncryptsec) with password - Stored encrypted in localStorage 3. **NIP-46 Bunker**: Remote signer via bunker connection string - `bunker://` URI scheme - WebSocket connection to remote signer 4. **Anonymous**: Temporary nsec for session - Generated on first visit - Pattern-based avatar - Handle: `Aitherite{random}` - Only allows zap requests (no comments) ### Content Display - **Threads (Kind 11)**: Discussion threads with topics - **Comments (Kind 1111)**: Threaded comments with NIP-22 tags - **Zap Receipts (Kind 9735)**: Lightning payments displayed as comments with ⚡ - **Kind 1 Feed**: Twitter-like feed with replies and zaps - **Reactions (Kind 7)**: Upvote/downvote system - **Markdown Rendering**: All content supports full markdown - **NIP-21 Links**: - `nostr:npub...` → Clickable profile badge - `nostr:nevent/note/naddr...` → Event card (250 char preview) → Event viewer ### Thread Organization - Threads sorted by `t` tags (topics) - Max 3 topics per thread - Threads without topics appear under "General" - Sorting: newest, most active, most upvoted - 30-day timeout (configurable, checkbox to show older threads) ### Thread Creation - Form to create new threads - Topic selection (multi-select, max 3) - Suggested topics (same as Discussion creation form in `jumble`) - Publish to: - Default relays - `wss://thecitadel.nostr1.com` - User's kind 10002 outbox relays - Display all target relays with deselection option - Publication status modal: - Success/failure per relay - Error messages (hyperlink https:// URLs) - Auto-close after 30s or manual close ### Commenting and Zapping - Response-threading: Flat list with gray blurb showing parent - Click gray blurb to highlight/scroll to parent - Respond to comments or zap receipts with: - `kind 1111` comments - `kind 9734` zap requests - Zap requests sent to wallets, receipts displayed from relays ### Reactions - Upvote/downvote buttons (kind 7) - One vote per user per event - Clicking twice deletes the vote - Display vote counts ### Advanced Features - Full-text search across threads/comments (keyboard shortcut `/`) - Local caching (IndexedDB) for offline access - Thread statistics (comment counts, zap totals, activity) - Real-time updates (live indicators for new comments) - Relay health monitoring (connection status, latency, auto-retry) - User blocking/muting - Content warnings (NSFW/sensitive content) - Thread locking (prevent replies to old/closed threads) - Keyboard shortcuts (j/k, r, z, etc.) - Thread bumping (active threads rise to top) - Recently active users list - User profile pages with kind 1 feeds ## Configuration ### Environment Variables All configuration via Docker environment variables: ```bash # Default relays (comma-separated, overrides hardcoded defaults) VITE_DEFAULT_RELAYS=wss://theforest.nostr1.com,wss://nostr21.com,wss://nostr.land,wss://nostr.wine,wss://nostr.sovbit.host # Minimum zap threshold in sats (must be 0 or positive integer, default: 1) VITE_ZAP_THRESHOLD=1 # Server port (overrides default 9876, must be 1-65535) PORT=9876 # Other configuration VITE_THREAD_TIMEOUT_DAYS=30 VITE_PWA_ENABLED=true ``` **Validation**: - `VITE_ZAP_THRESHOLD`: Must be 0 or positive integer. Invalid/negative values default to 1. - `VITE_DEFAULT_RELAYS`: Comma-separated list of relay URLs. Empty/invalid falls back to hardcoded defaults. - `PORT`: Must be valid port number (1-65535). Invalid values default to 9876. ### Default Relays ``` wss://theforest.nostr1.com wss://nostr21.com wss://nostr.land wss://nostr.wine wss://nostr.sovbit.host ``` ### Profile Relays (additional) ``` wss://relay.damus.io wss://aggr.nostr.land ``` ## Docker Deployment ### Dockerfile ```dockerfile # Multi-stage build FROM node:20-alpine AS builder WORKDIR /app COPY package*.json ./ RUN npm ci COPY . . # Build with environment variables (build-time) ARG VITE_DEFAULT_RELAYS ARG VITE_ZAP_THRESHOLD ARG VITE_THREAD_TIMEOUT_DAYS ARG VITE_PWA_ENABLED ENV VITE_DEFAULT_RELAYS=${VITE_DEFAULT_RELAYS} ENV VITE_ZAP_THRESHOLD=${VITE_ZAP_THRESHOLD} ENV VITE_THREAD_TIMEOUT_DAYS=${VITE_THREAD_TIMEOUT_DAYS} ENV VITE_PWA_ENABLED=${VITE_PWA_ENABLED} RUN npm run build FROM httpd:alpine COPY --from=builder /app/dist /usr/local/apache2/htdocs/ COPY httpd.conf.template /usr/local/apache2/conf/httpd.conf.template COPY docker-entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/docker-entrypoint.sh # Port is configurable via PORT env var (runtime) ARG PORT=9876 ENV PORT=${PORT} EXPOSE ${PORT} ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] ``` ### Apache Configuration (`httpd.conf.template`) ```apache Listen ${PORT} ServerName localhost Options Indexes FollowSymLinks AllowOverride All Require all granted # Health check endpoint - must be before SPA routing Header set Content-Type "application/json" Header set Cache-Control "public, max-age=5" # Rewrite /healthz to /healthz.json (must be before SPA catch-all rule) RewriteEngine On RewriteBase / RewriteRule ^healthz$ /healthz.json [L] # SPA routing (after healthz) RewriteRule ^index\.html$ - [L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.html [L] # PWA support Header set Service-Worker-Allowed "/" ``` ### Entrypoint Script (`docker-entrypoint.sh`) ```bash #!/bin/sh set -e # Validate and set PORT (must be 1-65535) PORT=${PORT:-9876} if ! [ "$PORT" -ge 1 ] 2>/dev/null || ! [ "$PORT" -le 65535 ] 2>/dev/null; then echo "Warning: Invalid PORT '$PORT', using default 9876" PORT=9876 fi # Replace PORT in httpd.conf template envsubst '${PORT}' < /usr/local/apache2/conf/httpd.conf.template > /usr/local/apache2/conf/httpd.conf # Start Apache exec httpd -D FOREGROUND ``` ### Docker Compose ```yaml services: aitherboard: build: context: . args: VITE_DEFAULT_RELAYS: ${VITE_DEFAULT_RELAYS:-wss://theforest.nostr1.com,wss://nostr21.com,wss://nostr.land,wss://nostr.wine,wss://nostr.sovbit.host} VITE_ZAP_THRESHOLD: ${VITE_ZAP_THRESHOLD:-1} VITE_THREAD_TIMEOUT_DAYS: ${VITE_THREAD_TIMEOUT_DAYS:-30} VITE_PWA_ENABLED: ${VITE_PWA_ENABLED:-true} ports: - "${PORT:-9876}:${PORT:-9876}" environment: - PORT=${PORT:-9876} ``` ### Usage ```bash # Build with custom relays docker build --build-arg VITE_DEFAULT_RELAYS="wss://relay1.com,wss://relay2.com" . # Run with custom port docker run -e PORT=8080 -p 8080:8080 aitherboard # Run with custom zap threshold (must be 0 or positive) docker run -e VITE_ZAP_THRESHOLD=5 aitherboard # Using docker-compose docker-compose up -d ``` ## Health Check ### `/healthz` Endpoint A health check endpoint for monitoring systems (Kubernetes, Docker healthchecks, load balancers, etc.). **Implementation**: Static JSON file generated at build time. **Build Script** (`scripts/generate-healthz.js`): ```javascript import { writeFileSync } from 'fs' import { readFileSync } from 'fs' import { execSync } from 'child_process' import { fileURLToPath } from 'url' import { dirname, join } from 'path' const __filename = fileURLToPath(import.meta.url) const __dirname = dirname(__filename) const packageJson = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf8')) const buildTime = new Date().toISOString() let gitCommit = 'unknown' try { gitCommit = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim() } catch (e) { // Git not available or not a git repo } const healthz = { status: 'ok', service: 'aitherboard', version: packageJson.version || '0.0.0', buildTime, gitCommit, timestamp: new Date().toISOString() } writeFileSync( join(__dirname, '../public/healthz.json'), JSON.stringify(healthz, null, 2) ) ``` **Vite Integration**: ```typescript import { defineConfig } from 'vite' import { svelte } from '@sveltejs/vite-plugin-svelte' import generateHealthz from './scripts/generate-healthz.js' export default defineConfig({ plugins: [ svelte(), { name: 'generate-healthz', buildStart() { generateHealthz() } } ] }) ``` **Response Example**: ```json { "status": "ok", "service": "aitherboard", "version": "1.0.0", "buildTime": "2024-01-15T10:30:00.000Z", "gitCommit": "a1b2c3d", "timestamp": "2024-01-15T12:45:30.123Z" } ``` **Monitoring Integration**: - Kubernetes: `http://service:9876/healthz` - Docker: `HEALTHCHECK --interval=30s --timeout=3s CMD curl -f http://localhost:9876/healthz || exit 1` - Load balancers, Prometheus, etc. ## Development ### Project Structure ``` aitherboard/ ├── src/ │ ├── lib/ │ │ ├── services/ # Core services │ │ │ ├── nostr/ # Nostr client, relay pool │ │ │ ├── auth/ # Authentication │ │ │ ├── cache/ # IndexedDB caching │ │ │ └── security/ # Security utilities │ │ ├── modules/ # Feature modules │ │ │ ├── threads/ # Thread management │ │ │ ├── comments/ # Comment threading │ │ │ ├── zaps/ # Zap handling │ │ │ ├── reactions/ # Upvote/downvote │ │ │ ├── profiles/ # User profiles │ │ │ └── feed/ # Kind 1 feed │ │ └── components/ # UI components │ │ ├── content/ # Markdown, NIP-21 links │ │ ├── interactions/# Buttons, forms │ │ ├── layout/ # Pages, navigation │ │ └── modals/ # Dialogs, overlays │ └── routes/ # SvelteKit routes ├── public/ # Static assets ├── scripts/ # Build scripts ├── Dockerfile ├── docker-compose.yml ├── httpd.conf.template └── docker-entrypoint.sh ``` ### Key Nostr Event Structures #### Kind 11 (Thread) ```typescript { kind: 11, content: string, // Markdown content tags: [ ['title', string], ['t', topic1], ['t', topic2], // max 3 topics ... ] } ``` #### Kind 1111 (Comment) ```typescript { kind: 1111, content: string, // Markdown content tags: [ ['K', '11'], // Root thread kind ['E', rootEventId], // Root thread event ['e', parentEventId], // Direct parent (if replying) ['k', '1111'], // Parent kind ['p', authorPubkey], // Author being replied to ['P', rootAuthorPubkey], // Root thread author ... ] } ``` #### Kind 9734 (Zap Request) ```typescript { kind: 9734, content: string, // Zap message tags: [ ['p', recipientPubkey], ['e', eventId], // Optional: event being zapped ['relays', ...relayUrls], ['amount', millisats], ... ] } ``` #### Kind 9735 (Zap Receipt) ```typescript { kind: 9735, content: string, // Bolt11 invoice tags: [ ['p', recipientPubkey], ['e', eventId], // Optional: event being zapped ['bolt11', invoice], ['description', zapRequestJson], ['preimage', preimage], // Optional ... ] } ``` #### Kind 7 (Reaction) ```typescript { kind: 7, content: '+' | '-', // Only + or - allowed tags: [ ['e', eventId], // Event being reacted to ... ] } ``` #### Kind 1 (Note/Feed Post) ```typescript { kind: 1, content: string, // Markdown content tags: [ ['e', eventId], // Optional: event being replied to ['p', pubkey], // Optional: pubkey being replied to ['root', rootEventId], // Optional: root of thread ['reply', replyEventId], // Optional: direct parent ... ] } ``` ## Implementation Phases ### Phase 1: Foundation 1. Project setup (Vite, Svelte 5, TypeScript, Tailwind) 2. Docker configuration 3. Basic Nostr client integration (applesauce) 4. Relay connection and AUTH handling ### Phase 2: Authentication 1. NIP-07 extension login 2. Anonymous key generation 3. Nsec login (with NIP-49) 4. NIP-46 bunker login 5. Session management ### Phase 3: Core Features 1. Thread display (kind 11) 2. Comment display (kind 1111) 3. Zap receipt display (kind 9735) 4. Markdown rendering 5. NIP-21 link parsing ### Phase 4: Interactions 1. Thread creation form 2. Comment form 3. Zap functionality 4. Reaction voting 5. Publication status ### Phase 5: Advanced Features 1. User profiles 2. Kind 1 feed page 3. Recently active users 4. Full-text search 5. IndexedDB caching 6. Real-time updates 7. Relay health monitoring ### Phase 6: Polish 1. Responsive design 2. PWA support 3. Keyboard shortcuts 4. Error handling 5. Loading states 6. Performance optimization ## Security ### Key Security Principles 1. **Never Store Plaintext Keys** - Use NIP-49 encryption for stored keys - Store encrypted key in localStorage (never plaintext) - Anonymous keys: Generate fresh on session start, discard on close 2. **Validate All Inputs** - Validate bech32 strings (NIP-19) - Validate event signatures - Sanitize all HTML content (DOMPurify) 3. **Secure Key Handling** - NIP-07: Use browser extension (no key storage) - Nsec: Encrypt with NIP-49 before storage - Bunker: No local key storage - Anonymous: Session-only, never persisted 4. **Content Security** - Sanitize markdown output - Validate NIP-21 URIs - Escape user content properly ## License [Add your license here] ## Contributing [Add contribution guidelines here] ## Links - **Nostr Protocol**: https://nostr.com - **Applesauce Library**: https://github.com/hzrd149/applesauce - **NIP-7D (Kind 11 Threads)**: [Nostr Improvement Proposals] - **NIP-22 (Kind 1111 Comments)**: [Nostr Improvement Proposals] - **NIP-57 (Zaps)**: [Nostr Improvement Proposals] --- **Note**: This is a comprehensive implementation plan. Refer to the codebase for the actual implementation details.